From 71082f713a1fe1822789509b050eaf51e179dcdd Mon Sep 17 00:00:00 2001 From: Pierre Wessman <4029607+pierrewessman@users.noreply.github.com> Date: Fri, 3 Oct 2025 12:58:43 +0200 Subject: [PATCH] Refactor player and goal collision handling using Phaser physics groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add playerGroup for dynamic collision management between all players - Add goalPostsGroup for centralized goal post collision handling - Add addPlayer() and removePlayer() methods for runtime player management - Reduce collision setup from 15+ lines to 2 group-based colliders - Adjust threat detection radius from 8m to 4m for more realistic evasion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/config/constants.ts | 2 +- src/game/GameScene.ts | 77 ++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/config/constants.ts b/src/config/constants.ts index 0ecb7d0..acc4664 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -70,7 +70,7 @@ export const CLOSE_RANGE_SHOT_SPEED_MULTIPLIER = 0.6; // Close-range shots have export const GOALIE_RANGE = 3; // meters - how far goalie moves from center // Collision avoidance constants -export const THREAT_DETECTION_RADIUS = 8; // meters - radius to detect approaching opponents +export const THREAT_DETECTION_RADIUS = 4; // meters - radius to detect approaching opponents export const EVASION_ANGLE = Math.PI / 3; // radians (60 degrees) - angle to turn when evading // Tackle constants diff --git a/src/game/GameScene.ts b/src/game/GameScene.ts index fbfe5bf..eadfad3 100644 --- a/src/game/GameScene.ts +++ b/src/game/GameScene.ts @@ -48,6 +48,8 @@ export class GameScene extends Phaser.Scene { private rightGoal!: Goal; private puck!: Puck; private players: Player[] = []; + private playerGroup!: Phaser.Physics.Arcade.Group; + private goalPostsGroup!: Phaser.Physics.Arcade.StaticGroup; private behaviorTrees: Map = new Map(); // Track last reception attempt time for each player to avoid spamming attempts @@ -71,6 +73,11 @@ export class GameScene extends Phaser.Scene { } private createPlayers() { + // Create physics group for all players + this.playerGroup = this.physics.add.group({ + collideWorldBounds: false + }); + // Create one home center at (-10, 0) - left side of center ice const homeCenter = new Player( this, @@ -93,19 +100,43 @@ export class GameScene extends Phaser.Scene { { speed: 80, skill: 70, tackling: 85, balance: 80, handling: 65 } ); - this.players.push(homeCenter, awayDefender); + // Add players to tracking array and physics group + this.addPlayer(homeCenter); + this.addPlayer(awayDefender); - // Create behavior trees for each player (reused every frame) - this.players.forEach(player => { - this.behaviorTrees.set(player.id, new BehaviorTree(player)); - }); - - // Setup player-player collisions - this.physics.add.collider(homeCenter, awayDefender, (obj1, obj2) => { + // Setup player-player collisions for entire group + this.physics.add.collider(this.playerGroup, this.playerGroup, (obj1, obj2) => { this.handlePlayerCollision(obj1 as Player, obj2 as Player); }); } + /** + * Add a player to the scene (can be called at any time for debugging) + */ + addPlayer(player: Player) { + this.players.push(player); + this.playerGroup.add(player); + this.behaviorTrees.set(player.id, new BehaviorTree(player)); + + // Add collision with all goal posts + this.physics.add.collider(player, this.goalPostsGroup); + } + + /** + * Remove a player from the scene (can be called at any time for debugging) + */ + removePlayer(playerId: string) { + const index = this.players.findIndex(p => p.id === playerId); + if (index === -1) return; + + const player = this.players[index]; + this.players.splice(index, 1); + this.playerGroup.remove(player); + this.behaviorTrees.delete(playerId); + this.lastReceptionAttempt.delete(playerId); + player.destroy(); + } + private setupEventListeners() { // Listen for goal events this.events.on('goal', (data: { team: string; goal: string }) => { @@ -122,29 +153,17 @@ export class GameScene extends Phaser.Scene { // Initialize puck at center ice (0, 0 in game coordinates) this.puck = new Puck(this, 27, 5); - // Add collisions between puck and goal posts with custom bounce handler - this.physics.add.collider(this.puck, this.leftGoal.getLeftPost(), this.handlePuckPostBounce, undefined, this); - this.physics.add.collider(this.puck, this.leftGoal.getRightPost(), this.handlePuckPostBounce, undefined, this); - this.physics.add.collider(this.puck, this.leftGoal.getBackBar(), this.handlePuckPostBounce, undefined, this); - this.physics.add.collider(this.puck, this.rightGoal.getLeftPost(), this.handlePuckPostBounce, undefined, this); - this.physics.add.collider(this.puck, this.rightGoal.getRightPost(), this.handlePuckPostBounce, undefined, this); - this.physics.add.collider(this.puck, this.rightGoal.getBackBar(), this.handlePuckPostBounce, undefined, this); - - // Add player collisions with goal posts (prevent skating through goals) - this.players.forEach(player => { - this.physics.add.collider(player, this.leftGoal.getLeftPost()); - this.physics.add.collider(player, this.leftGoal.getRightPost()); - this.physics.add.collider(player, this.leftGoal.getBackBar()); - this.physics.add.collider(player, this.rightGoal.getLeftPost()); - this.physics.add.collider(player, this.rightGoal.getRightPost()); - this.physics.add.collider(player, this.rightGoal.getBackBar()); - }); + // Add collisions between puck and all goal posts with custom bounce handler + this.physics.add.collider(this.puck, this.goalPostsGroup, this.handlePuckPostBounce, undefined, this); } private createGoals() { const centerX = (RINK_LENGTH * SCALE) / 2; const centerY = (RINK_WIDTH * SCALE) / 2; + // Create static physics group for all goal posts + this.goalPostsGroup = this.physics.add.staticGroup(); + // Left goal (positioned at left goal line) const leftGoalX = centerX - (GOAL_LINE_OFFSET * SCALE); this.leftGoal = new Goal(this, leftGoalX, centerY, true); @@ -152,6 +171,14 @@ export class GameScene extends Phaser.Scene { // Right goal (positioned at right goal line) const rightGoalX = centerX + (GOAL_LINE_OFFSET * SCALE); this.rightGoal = new Goal(this, rightGoalX, centerY, false); + + // Add all goal posts to the group + this.goalPostsGroup.add(this.leftGoal.getLeftPost()); + this.goalPostsGroup.add(this.leftGoal.getRightPost()); + this.goalPostsGroup.add(this.leftGoal.getBackBar()); + this.goalPostsGroup.add(this.rightGoal.getLeftPost()); + this.goalPostsGroup.add(this.rightGoal.getRightPost()); + this.goalPostsGroup.add(this.rightGoal.getBackBar()); } private drawRink() {