From f51a4001d8aa047e49cf2d92fe2748a697881853 Mon Sep 17 00:00:00 2001 From: vbenoit Date: Sun, 13 Oct 2024 19:05:17 +0000 Subject: [PATCH] =?UTF-8?q?changement=20d'interface=20pour=20le=20joueur?= =?UTF-8?q?=20et=20d=C3=A9but=20d'animation=20du=20ballon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../football-field.component.html | 4 +- .../football-field.component.ts | 186 ++++++++++-------- 2 files changed, 109 insertions(+), 81 deletions(-) diff --git a/src/app/football-field/football-field.component.html b/src/app/football-field/football-field.component.html index 5ac5173..e7b79e4 100644 --- a/src/app/football-field/football-field.component.html +++ b/src/app/football-field/football-field.component.html @@ -86,7 +86,7 @@ (mousemove)="onTimelineMouseMove($event)" (mouseup)="onTimelineMouseUp()"> -
Timeline du joueur sélectionné
    -
  • +
  • Étape {{ i + 1 }} : Départ ({{ step.startX }}, {{ step.startY }}), Arrivée ({{ step.endX }}, {{ step.endY }}), Durée : ({{ step.startTime }}, {{ step.endTime }}: {{ step.duration }})
diff --git a/src/app/football-field/football-field.component.ts b/src/app/football-field/football-field.component.ts index f4609e0..35599a7 100644 --- a/src/app/football-field/football-field.component.ts +++ b/src/app/football-field/football-field.component.ts @@ -60,7 +60,7 @@ interface Ball { progress: number; // Paramètre de progression sur la courbe steps: TimelineStep[]; // Stockage des étapes d'animation currentStepIndex: number; // Suivre l'étape actuelle dans la timeline - attachToPlayer: Circle | null; // Le joueur auquel le ballon est attaché + attachedToPlayer: Player | null; // Le joueur auquel le ballon est attaché } interface Player { @@ -94,9 +94,9 @@ export class FootballFieldComponent { //private lastY = 0; private startX = 0; private startY = 0; - private draggingPlayer: Circle | null = null; + private draggingPlayer: Player | null = null; private draggingElement: any = null; // Element (plot, piquet) en cours de déplacement sur le terrain - private attachedPlayer: Circle | null = null; + private attachedPlayer: Player | null = null; private magnetRadius: number = 35; // Distance à laquelle le ballon s'attache au joueur //private attachedToPlayer: boolean = false; // indique si le ballon est attache a un joueur private offsetX: number = 0; @@ -125,7 +125,7 @@ export class FootballFieldComponent { public playerCount: number = 8; // Nombre de joueurs par défaut public plotCount: number = 2; // Nombre de plots par défaut public piquetCount: number = 2; // Nombre de plots par défaut - public players: Circle[] = []; + public players: Player[] = []; public plots: Plot[] = []; public piquets: Piquet[] = []; /*public ball: Circle = { id: 0, @@ -148,15 +148,15 @@ export class FootballFieldComponent { progress: 0, steps: [], currentStepIndex: 0, - attachToPlayer: null }; - public selectedPlayer: Circle | null = null; + attachedToPlayer: null }; + public selectedPlayer: Player | null = null; public isDrawingLine: boolean = false; public loopAnimation: boolean = false; // Pour répéter l'animation public debugInfo: string = ""; // Variable pour les informations de debogage public interactionMode: 'move' | 'animate' = 'move'; // Mode d'interaction sélectionné public isTimelineDragging: boolean = false; - public draggedTimelinePlayer: Circle | null = null; + public draggedTimelinePlayer: Player | null = null; public draggedTimelineIndex: number = -1; // La largeur totale de la timeline en pixels. // Cela représente la taille visuelle de la timeline dans laquelle @@ -270,18 +270,27 @@ export class FootballFieldComponent { // Mettre à jour la position de tous les joueurs en suivant leur timeline this.players.forEach(player => { if (this.isAnimating) { - if (player.timeline.length > 0) { + if (player.steps.length > 0) { this.updatePlayerPosition(player); } } else if (this.isPlaying) { - player.timeline.forEach(step => { + player.steps.forEach(step => { if (currentTime >= step.startTime && currentTime <= step.endTime) { const progress = ((currentTime - step.startTime) / (step.endTime - step.startTime)); - player.x = step.startX + (step.endX - step.startX) * progress; - player.y = step.startY + (step.endY - step.startY) * progress; + player.design.x = step.startX + (step.endX - step.startX) * progress; + player.design.y = step.startY + (step.endY - step.startY) * progress; hasAnimation = true; // Indiquer qu'il y a encore de l'animation } }); + + // Déplacer le ballon si attaché à un joueur + if (this.ball.attachedToPlayer) { + const player = this.ball.attachedToPlayer; + // Le ballon suit le joueur avec un léger décalage (ajustable) + this.ball.design.x = player.design.x + player.design.radius + this.ball.design.radius + 5; // Décalage à droite du joueur + this.ball.design.y = player.design.y; // Même hauteur que le joueur + } + // Mettre à jour la position du trait de visualisation du temps this.updateTimeIndicator(currentTime); } @@ -330,12 +339,26 @@ export class FootballFieldComponent { } } - private updatePlayerPosition(player: Circle) { - if (player.timeline.length === 0 || player.currentStepIndex >= player.timeline.length) { + // Méthode pour attacher le ballon à un joueur + attachBallToPlayer(player: Player) { + this.ball.attachedToPlayer = player; + player.hasBall = true; + } + + // Méthode pour détacher le ballon (par exemple, quand on clique dessus) + detachBall() { + if (this.ball.attachedToPlayer) { + this.ball.attachedToPlayer.hasBall = false; + this.ball.attachedToPlayer = null; + } + } + + private updatePlayerPosition(player: Player) { + if (player.steps.length === 0 || player.currentStepIndex >= player.steps.length) { return; // Pas d'animation si la timeline est vide } - const step = player.timeline[player.currentStepIndex]; + const step = player.steps[player.currentStepIndex]; // Avancer le joueur sur la ligne avec un LERP (Linear Interpolation) player.progress += this.animationSpeed; // Temps actuel @@ -354,12 +377,12 @@ export class FootballFieldComponent { if (player.progress >= 1) { player.progress = 0; // Réinitialiser la progression - player.x = step.endX; - player.y = step.endY; + player.design.x = step.endX; + player.design.y = step.endY; player.currentStepIndex++; // Passer à l'étape suivante // Si on atteint la fin de la timeline, recommencer ou arrêter - if (player.currentStepIndex >= player.timeline.length) { + if (player.currentStepIndex >= player.steps.length) { if (this.loopAnimation) { player.currentStepIndex = 0; // Boucle l'animation } else { @@ -368,15 +391,22 @@ export class FootballFieldComponent { } } else { const t = player.progress; - player.x = (1 - t) * step.startX + t * step.endX; - player.y = (1 - t) * step.startY + t * step.endY; + player.design.x = (1 - t) * step.startX + t * step.endX; + player.design.y = (1 - t) * step.startY + t * step.endY; + + // Attacher le ballon si un joueur se rapproche suffisamment + const nearestPlayer = this.getNearestPlayerToBall(); + if (nearestPlayer && this.getDistance(this.ball.design, nearestPlayer.design) < this.magnetRadius) { + //this.attachedPlayer = nearestPlayer; + this.updateBallPositionOnPlayer(nearestPlayer); + } } } // Mettre à jour debugInfo avec les positions des joueurs et du ballon private updateDebugInfo() { const playerPositions = this.players.map((player, index) => - `Joueur ${index + 1}: (${player.x.toFixed(1)}, ${player.y.toFixed(1)})` + `Joueur ${index + 1}: (${player.design.x.toFixed(1)}, ${player.design.y.toFixed(1)})` ).join(', '); const ballPosition = `Ballon: (${this.ball.design.x.toFixed(1)}, ${this.ball.design.y.toFixed(1)})`; @@ -391,15 +421,15 @@ export class FootballFieldComponent { const x = i * (2 * radius) + radius; const y = radius; this.players.push({ id: i + 1, - x, y, - radius, + design: { x, y, radius }, isDragging: false, - isMoving:false, + isMoving: false, progress: 0, startX: 0, startY: 0, - timeline: [], - currentStepIndex: 0 }); + steps: [], + currentStepIndex: 0, + hasBall: false }); } this.drawField(); } @@ -412,9 +442,7 @@ export class FootballFieldComponent { const x = (this.canvas.nativeElement.width - radius) - (i) * (2 * radius); const y = radius; this.plots.push({ id: i + 1, - design: { - x, y, - radius }, + design: { x, y, radius }, isDragging: false }); } this.drawField(); @@ -428,8 +456,8 @@ export class FootballFieldComponent { const height = 100; const x = (i * width); const y = (this.canvas.nativeElement.height - height); - this.piquets.push({ id: i+1, - design: {x, y, width, height}, + this.piquets.push({ id: i + 1, + design: { x, y, width, height }, isDragging: false }); } this.drawField(); @@ -451,8 +479,8 @@ export class FootballFieldComponent { // Dessiner les cercles numéroe representant les joueurs private drawPlayers() { - this.players.forEach(circle => { - this.drawPlayer(circle); + this.players.forEach(player => { + this.drawPlayer(player); }); } @@ -509,12 +537,12 @@ export class FootballFieldComponent { } // Dessiner un joueur avec un numér - private drawPlayer(circle: Circle) { + private drawPlayer(player: Player) { const ctx = this.ctx; // Dessiner le cercle ctx.beginPath(); - ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2); + ctx.arc(player.design.x, player.design.y, player.design.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffff00'; ctx.fill(); ctx.strokeStyle = '#000000'; @@ -525,7 +553,7 @@ export class FootballFieldComponent { ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(circle.id.toString(), circle.x, circle.y); + ctx.fillText(player.id.toString(), player.design.x, player.design.y); } onCanvasMouseDown(event: MouseEvent) { @@ -534,7 +562,7 @@ export class FootballFieldComponent { console.log("[Mouse Down] animate elements"); this.selectedPlayer = null; for (const player of this.players) { - if (this.isInsideCircle(player, x, y)) { + if (this.isInsideCircle(player.design, x, y)) { // Sélectionner le joueur this.selectedPlayer = player; console.log("[Mouse Down|Animate] selecting player :", this.selectedPlayer); @@ -545,8 +573,8 @@ export class FootballFieldComponent { if (!this.selectedPlayer) { this.selectPlayer(x, y); } else if (!this.isDrawingLine) { - this.startX = this.selectedPlayer.x; - this.startY = this.selectedPlayer.y; + this.startX = this.selectedPlayer.design.x; + this.startY = this.selectedPlayer.design.y; this.isDrawingLine = true; console.log("[Mouse Down|Animate] drawing line - Start:(", this.startX,",", @@ -584,13 +612,13 @@ export class FootballFieldComponent { this.isDrawingLine = false; // Ajouter l'étape dans la timeline du joueur let prevStartTime:number = 0; - if (this.selectedPlayer.timeline && this.selectedPlayer.timeline.length > 0) { - prevStartTime = this.selectedPlayer.timeline[this.selectedPlayer.timeline.length - 1].endTime; + if (this.selectedPlayer.steps && this.selectedPlayer.steps.length > 0) { + prevStartTime = this.selectedPlayer.steps[this.selectedPlayer.steps.length - 1].endTime; } else { prevStartTime = 0; } - this.selectedPlayer.timeline.push({ + this.selectedPlayer.steps.push({ startTime: prevStartTime, endTime: prevStartTime + 1000, startX: this.startX, @@ -599,7 +627,7 @@ export class FootballFieldComponent { endY: this.endY, duration: 1000 // Durée d'animation arbitraire, peut être ajustée }); - console.log("timeline:", this.selectedPlayer.timeline); + console.log("timeline:", this.selectedPlayer.steps); //this.selectedPlayer.currentStepIndex = 0; // Réinitialiser au début de la timeline this.startAnimation(); } @@ -610,10 +638,10 @@ export class FootballFieldComponent { // Si on déplace un joueur alors que celui-ci a une animation, // on supprime toute sa timeline for (const player of this.players) { - if (this.isInsideCircle(player, x, y)) { + if (this.isInsideCircle(player.design, x, y)) { // Sélectionner le joueur this.selectedPlayer = player; - this.selectedPlayer.timeline = []; + this.selectedPlayer.steps = []; break; } } @@ -643,12 +671,12 @@ export class FootballFieldComponent { } this.draggingPlayer = this.players.find(player => { - return this.isInsideCircle(player, x, y); + return this.isInsideCircle(player.design, x, y); }); if (this.draggingPlayer) { this.draggingPlayer.isDragging = true; - this.offsetX = x - this.draggingPlayer.x; - this.offsetY = y - this.draggingPlayer.y; + this.offsetX = x - this.draggingPlayer.design.x; + this.offsetY = y - this.draggingPlayer.design.y; this.test = true; } } @@ -665,14 +693,14 @@ export class FootballFieldComponent { if (this.draggingPlayer && this.draggingPlayer.isDragging) { console.log("dragging player"); - this.draggingPlayer.x = x - this.offsetX; - this.draggingPlayer.y = y - this.offsetY; + this.draggingPlayer.design.x = x - this.offsetX; + this.draggingPlayer.design.y = y - this.offsetY; // Attacher le ballon si un joueur se rapproche suffisamment const nearestPlayer = this.getNearestPlayerToBall(); - if (nearestPlayer && this.getDistance(this.ball.design, nearestPlayer) < this.magnetRadius) { - this.attachedPlayer = nearestPlayer; - this.updateBallPositionOnPlayer(); + if (nearestPlayer && this.getDistance(this.ball.design, nearestPlayer.design) < this.magnetRadius) { + //this.attachedPlayer = nearestPlayer; + this.updateBallPositionOnPlayer(nearestPlayer); } // // Si le ballon est attaché au joueur deplacer le ballon avec lui en calculant @@ -714,12 +742,12 @@ export class FootballFieldComponent { } } - private updateBallPositionOnPlayer() { - const player = this.attachedPlayer; + private updateBallPositionOnPlayer(player: Player) { + //const player = this.attachedPlayer; if (!player) return; - const dx = this.ball.design.x - player.x; - const dy = this.ball.design.y - player.y; + const dx = this.ball.design.x - player.design.x; + const dy = this.ball.design.y - player.design.y; let angle = 0; if (this.test) { angle = Math.atan2(dy, dx); @@ -729,15 +757,15 @@ export class FootballFieldComponent { angle = this.prevAngle; } - this.ball.design.x = player.x + (player.radius + this.ball.design.radius) * Math.cos(angle); - this.ball.design.y = player.y + (player.radius + this.ball.design.radius) * Math.sin(angle); + this.ball.design.x = player.design.x + (player.design.radius + this.ball.design.radius) * Math.cos(angle); + this.ball.design.y = player.design.y + (player.design.radius + this.ball.design.radius) * Math.sin(angle); } private selectPlayer(x: number, y: number) { this.selectedPlayer = this.getNearestPlayer(x, y); if (this.selectedPlayer) { - this.selectedPlayer.startX = this.selectedPlayer.x; // Sauvegarder le point de départ - this.selectedPlayer.startY = this.selectedPlayer.y; + this.selectedPlayer.startX = this.selectedPlayer.design.x; // Sauvegarder le point de départ + this.selectedPlayer.startY = this.selectedPlayer.design.y; } } @@ -748,12 +776,12 @@ export class FootballFieldComponent { } } - private getNearestPlayerToBall(): Circle | null { - let nearestPlayer: Circle | null = null; + private getNearestPlayerToBall(): Player | null { + let nearestPlayer: Player | null = null; let minDistance = this.magnetRadius; this.players.forEach(player => { - const distance = this.getDistance(this.ball.design, player); + const distance = this.getDistance(this.ball.design, player.design); if (distance < minDistance) { minDistance = distance; nearestPlayer = player; @@ -762,13 +790,13 @@ export class FootballFieldComponent { return nearestPlayer; } - private getNearestPlayer(x: number, y: number): Circle | null { - let nearestPlayer: Circle | null = null; + private getNearestPlayer(x: number, y: number): Player | null { + let nearestPlayer: Player | null = null; let minDistance = Infinity; this.players.forEach(player => { - const distance = Math.sqrt((player.x - x) ** 2 + (player.y - y) ** 2); - if (distance < minDistance && distance <= player.radius) { + const distance = Math.sqrt((player.design.x - x) ** 2 + (player.design.y - y) ** 2); + if (distance < minDistance && distance <= player.design.radius) { minDistance = distance; nearestPlayer = player; } @@ -878,13 +906,13 @@ export class FootballFieldComponent { return (time / totalDuration) * timelineWidth; } - onTimelineMouseDown(event: MouseEvent, player: Circle) { + onTimelineMouseDown(event: MouseEvent, player: Player) { // Récupérer la position de la souris lors du clic const { x } = this.getMousePosOnTimeline(event); // Chercher si un bloc (une étape du joueur) a été cliqué - for (let i = 0; i < player.timeline.length; i++) { - const step = player.timeline[i]; + for (let i = 0; i < player.steps.length; i++) { + const step = player.steps[i]; //console.log("[OnTimelineMouseDown | Joueur",player.id,"] - Timeline[",i,"]:", step); // Calculer la position du bloc en pixels sur la timeline const blockStartX = this.calculateBlockPixelPosition(step.startTime); @@ -910,7 +938,7 @@ export class FootballFieldComponent { } } - onTimelineBlockMouseDown(event: MouseEvent, player: Circle, index: number) { + onTimelineBlockMouseDown(event: MouseEvent, player: Player, index: number) { console.log("[onTimelineBlockMouseDown] index:", index); this.isTimelineDragging = true; this.draggedTimelinePlayer = player; @@ -930,18 +958,18 @@ export class FootballFieldComponent { " - newStartX:", newStartX, " - newStartTime:", newStartTime, " - dragged Index:", this.draggedTimelineIndex, - " - timeline len:", this.draggedTimelinePlayer.timeline.length, + " - timeline len:", this.draggedTimelinePlayer.steps.length, ); // Mettre à jour l'étape avec la nouvelle position - const step = this.draggedTimelinePlayer.timeline[this.draggedTimelineIndex]; + const step = this.draggedTimelinePlayer.steps[this.draggedTimelineIndex]; let prevStep:TimelineStep = null; // Le bloc N ne peut pas dépasser le bloc N+1 si celui-ci existe // De même, le bloc N ne peut pas dépasser le bloc N-1 si celui-ci existe - if ((this.draggedTimelineIndex + 1) < this.draggedTimelinePlayer.timeline.length) { - const nextStep = this.draggedTimelinePlayer.timeline[this.draggedTimelineIndex + 1]; + if ((this.draggedTimelineIndex + 1) < this.draggedTimelinePlayer.steps.length) { + const nextStep = this.draggedTimelinePlayer.steps[this.draggedTimelineIndex + 1]; if ((this.draggedTimelineIndex - 1) >= 0) { - prevStep = this.draggedTimelinePlayer.timeline[this.draggedTimelineIndex - 1]; + prevStep = this.draggedTimelinePlayer.steps[this.draggedTimelineIndex - 1]; } if ((((newStartTime + step.duration) < nextStep.startTime) && prevStep === null) || @@ -952,8 +980,8 @@ export class FootballFieldComponent { step.endTime = Math.max(0, step.startTime + step.duration); // Ne pas permettre les valeurs négatives } } else if (((this.draggedTimelineIndex - 1) >= 0) && - (this.draggedTimelinePlayer.timeline[this.draggedTimelineIndex - 1])) { - const prevStep = this.draggedTimelinePlayer.timeline[this.draggedTimelineIndex - 1]; + (this.draggedTimelinePlayer.steps[this.draggedTimelineIndex - 1])) { + const prevStep = this.draggedTimelinePlayer.steps[this.draggedTimelineIndex - 1]; if ((prevStep.endTime <= newStartTime) && ((newStartTime + step.duration) <= (this.getTotalTimelineDuration()))) { //console.log("PLOP1.1: (",prevStep.endTime, "/", newStartTime,"),(",newStartTime + step.duration,"/",this.getTotalTimelineDuration(),")"); @@ -980,7 +1008,7 @@ export class FootballFieldComponent { onTimelineMouseUp() { console.log("onMouseUp"); if (this.isTimelineDragging) { - console.log("timeline:", this.draggedTimelinePlayer.timeline); + console.log("timeline:", this.draggedTimelinePlayer.steps); this.isTimelineDragging = false; this.draggedTimelinePlayer = null; this.draggedTimelineIndex = -1;