Refactor player and goal collision handling using Phaser physics groups

- 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 <noreply@anthropic.com>
This commit is contained in:
Pierre Wessman 2025-10-03 12:58:43 +02:00
parent f26d8a3189
commit 71082f713a
2 changed files with 53 additions and 26 deletions

View File

@ -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 export const GOALIE_RANGE = 3; // meters - how far goalie moves from center
// Collision avoidance constants // 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 export const EVASION_ANGLE = Math.PI / 3; // radians (60 degrees) - angle to turn when evading
// Tackle constants // Tackle constants

View File

@ -48,6 +48,8 @@ export class GameScene extends Phaser.Scene {
private rightGoal!: Goal; private rightGoal!: Goal;
private puck!: Puck; private puck!: Puck;
private players: Player[] = []; private players: Player[] = [];
private playerGroup!: Phaser.Physics.Arcade.Group;
private goalPostsGroup!: Phaser.Physics.Arcade.StaticGroup;
private behaviorTrees: Map<string, BehaviorTree> = new Map(); private behaviorTrees: Map<string, BehaviorTree> = new Map();
// Track last reception attempt time for each player to avoid spamming attempts // Track last reception attempt time for each player to avoid spamming attempts
@ -71,6 +73,11 @@ export class GameScene extends Phaser.Scene {
} }
private createPlayers() { 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 // Create one home center at (-10, 0) - left side of center ice
const homeCenter = new Player( const homeCenter = new Player(
this, this,
@ -93,19 +100,43 @@ export class GameScene extends Phaser.Scene {
{ speed: 80, skill: 70, tackling: 85, balance: 80, handling: 65 } { 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) // Setup player-player collisions for entire group
this.players.forEach(player => { this.physics.add.collider(this.playerGroup, this.playerGroup, (obj1, obj2) => {
this.behaviorTrees.set(player.id, new BehaviorTree(player));
});
// Setup player-player collisions
this.physics.add.collider(homeCenter, awayDefender, (obj1, obj2) => {
this.handlePlayerCollision(obj1 as Player, obj2 as Player); 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() { private setupEventListeners() {
// Listen for goal events // Listen for goal events
this.events.on('goal', (data: { team: string; goal: string }) => { 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) // Initialize puck at center ice (0, 0 in game coordinates)
this.puck = new Puck(this, 27, 5); this.puck = new Puck(this, 27, 5);
// Add collisions between puck and goal posts with custom bounce handler // Add collisions between puck and all 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.goalPostsGroup, 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());
});
} }
private createGoals() { private createGoals() {
const centerX = (RINK_LENGTH * SCALE) / 2; const centerX = (RINK_LENGTH * SCALE) / 2;
const centerY = (RINK_WIDTH * 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) // Left goal (positioned at left goal line)
const leftGoalX = centerX - (GOAL_LINE_OFFSET * SCALE); const leftGoalX = centerX - (GOAL_LINE_OFFSET * SCALE);
this.leftGoal = new Goal(this, leftGoalX, centerY, true); 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) // Right goal (positioned at right goal line)
const rightGoalX = centerX + (GOAL_LINE_OFFSET * SCALE); const rightGoalX = centerX + (GOAL_LINE_OFFSET * SCALE);
this.rightGoal = new Goal(this, rightGoalX, centerY, false); 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() { private drawRink() {