Add pause-aware goal reset timer with proper scene cleanup

- Add 3-second countdown timer after goal that resets the scene
- Timer pauses when game is paused, respecting pause state
- Freeze all game updates (puck, players, goals) during countdown
- Properly reset timer flags in create() to fix post-restart state
- Add safety checks in Player and Puck update to handle destroyed entities
- Remove player goal deceleration behavior (superseded by scene freeze)
- Add destroy() method to Player for proper resource cleanup
- Clean up goal event listeners before scene restart

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pierre Wessman 2025-10-04 16:12:21 +02:00
parent ca6a444dec
commit 29885d1992
3 changed files with 45 additions and 22 deletions

View File

@ -6,7 +6,6 @@ import {
PLAYER_RADIUS_SKATER, PLAYER_RADIUS_SKATER,
SPEED_SCALE_FACTOR, SPEED_SCALE_FACTOR,
MOVEMENT_STOP_THRESHOLD, MOVEMENT_STOP_THRESHOLD,
GOAL_DECELERATION_RATE,
DEBUG, DEBUG,
TACKLE_COOLDOWN, TACKLE_COOLDOWN,
TACKLE_FALL_DURATION, TACKLE_FALL_DURATION,
@ -97,8 +96,7 @@ export class Player extends Phaser.GameObjects.Container {
// Home team (left side) faces right, Away team (right side) faces left // Home team (left side) faces right, Away team (right side) faces left
this.currentAngle = this.team === 'home' ? 0 : Math.PI; this.currentAngle = this.team === 'home' ? 0 : Math.PI;
// Listen for goal events // Note: Goal events are handled by GameScene (freezes game and resets after 3s)
this.scene.events.on('goal', this.onGoal, this);
// Note: Player must be added to scene via scene.add.existing(player) and scene.physics.add.existing(player) // Note: Player must be added to scene via scene.add.existing(player) and scene.physics.add.existing(player)
// This is handled by GameScene.addPlayer() method, which also sets up the physics body // This is handled by GameScene.addPlayer() method, which also sets up the physics body
@ -182,28 +180,13 @@ export class Player extends Phaser.GameObjects.Container {
this.targetY = targetY; this.targetY = targetY;
} }
/**
* Handle goal event
*/
private onGoal(_data: { team: string; goal: string }) {
// Gradually decelerate player after goal
const decelerate = () => {
this.speedMultiplier *= GOAL_DECELERATION_RATE;
if (this.speedMultiplier > 0.01) {
this.scene.time.delayedCall(50, decelerate);
} else {
this.speedMultiplier = 0;
}
};
decelerate();
}
/** /**
* Update player movement each frame * Update player movement each frame
*/ */
public update(delta: number) { public update(delta: number) {
// Skip update if body doesn't exist (player being destroyed)
if (!this.body) return;
const deltaSeconds = delta / 1000; const deltaSeconds = delta / 1000;
// Check if player is fallen and should stay still // Check if player is fallen and should stay still
@ -466,4 +449,17 @@ export class Player extends Phaser.GameObjects.Container {
this.debugDetectionCircle.fillPath(); this.debugDetectionCircle.fillPath();
this.debugDetectionCircle.strokePath(); this.debugDetectionCircle.strokePath();
} }
/**
* Clean up resources when player is destroyed
*/
destroy(fromScene?: boolean) {
// Destroy detection sensor
if (this.detectionSensor) {
this.detectionSensor.destroy();
}
// Call parent destroy
super.destroy(fromScene);
}
} }

View File

@ -112,6 +112,9 @@ export class Puck extends Phaser.GameObjects.Container {
* Update puck game coordinates based on physics body position * Update puck game coordinates based on physics body position
*/ */
public update() { public update() {
// Skip update if body doesn't exist (puck being destroyed)
if (!this.body) return;
// Use body center position (body.x/y is top-left, need to account for that) // Use body center position (body.x/y is top-left, need to account for that)
const bodyX = this.body.x + this.body.width / 2; const bodyX = this.body.x + this.body.width / 2;
const bodyY = this.body.y + this.body.height / 2; const bodyY = this.body.y + this.body.height / 2;

View File

@ -61,17 +61,28 @@ export class GameScene extends Phaser.Scene {
private isPaused: boolean = false; private isPaused: boolean = false;
private pauseButton!: Phaser.GameObjects.Text; private pauseButton!: Phaser.GameObjects.Text;
// Goal reset timer
private goalResetTimer: number = 0;
private isWaitingForReset: boolean = false;
constructor() { constructor() {
super({ key: 'GameScene' }); super({ key: 'GameScene' });
} }
create() { create() {
console.log('[GameScene] Creating scene...');
// Reset goal timer flags
this.goalResetTimer = 0;
this.isWaitingForReset = false;
this.drawRink(); this.drawRink();
this.createGoals(); this.createGoals();
this.createPuck(); this.createPuck();
this.createPlayers(); this.createPlayers();
this.setupEventListeners(); this.setupEventListeners();
this.createPauseButton(); this.createPauseButton();
console.log('[GameScene] Scene created. Players:', this.players.length);
} }
private createPlayers() { private createPlayers() {
@ -210,7 +221,9 @@ export class GameScene extends Phaser.Scene {
// Stop the puck (caught by net) // Stop the puck (caught by net)
this.puck.body.setVelocity(0, 0); this.puck.body.setVelocity(0, 0);
// Future: update score, trigger celebration, reset to faceoff, etc. // Start 3-second countdown to reset
this.goalResetTimer = 3000;
this.isWaitingForReset = true;
}); });
} }
@ -327,6 +340,17 @@ export class GameScene extends Phaser.Scene {
update(_time: number, delta: number) { update(_time: number, delta: number) {
if (this.isPaused) return; if (this.isPaused) return;
// Handle goal reset timer
if (this.isWaitingForReset) {
this.goalResetTimer -= delta;
if (this.goalResetTimer <= 0) {
// Clean up before restart
this.events.off('goal');
this.scene.restart();
}
return; // Don't update anything while waiting for reset
}
this.updatePuck(); this.updatePuck();
this.updatePlayers(delta); this.updatePlayers(delta);
this.checkGoals(); this.checkGoals();