import Phaser from 'phaser'; import { SCALE, GOAL_WIDTH, GOAL_DEPTH } from '../config/constants'; import type { Puck } from '../entities/Puck'; export class Goal extends Phaser.GameObjects.Container { private leftPost!: Phaser.Physics.Arcade.Image; private rightPost!: Phaser.Physics.Arcade.Image; private backBar!: Phaser.Physics.Arcade.Image; private scoringZone!: Phaser.GameObjects.Zone; private isLeftGoal: boolean; private puckInZone: boolean = false; constructor(scene: Phaser.Scene, x: number, y: number, isLeftGoal: boolean = true) { super(scene, x, y); this.isLeftGoal = isLeftGoal; // Add to scene scene.add.existing(this); this.createGoalStructure(); } private createGoalStructure() { const postThickness = 0.3 * SCALE; // Post thickness const goalWidth = GOAL_WIDTH * SCALE; // Width of goal opening (top to bottom) const goalDepth = GOAL_DEPTH * SCALE; // Depth extending into zone const barThickness = 0.4 * SCALE; // Thicker bar to prevent high-speed puck tunneling // Create graphics for visual representation const graphics = this.scene.add.graphics(); // Draw goal outline (visual only) // Left goal extends leftward (negative X), right goal extends rightward (positive X) const offsetX = this.isLeftGoal ? -goalDepth : 0; // Fill the scoring zone with semi-transparent color graphics.fillStyle(0x00ff00, 0.2); // Green, 20% opacity graphics.fillRect( offsetX + barThickness, -goalWidth / 2 + postThickness, goalDepth - barThickness * 2, goalWidth - postThickness * 2 ); // Draw goal posts (filled red) graphics.fillStyle(0xff0000, 1); // Top post (horizontal bar across top) graphics.fillRect(offsetX, -goalWidth / 2, goalDepth, postThickness); // Bottom post (horizontal bar across bottom) graphics.fillRect(offsetX, goalWidth / 2 - postThickness, goalDepth, postThickness); // Back bar (vertical bar at the back) const backX = this.isLeftGoal ? offsetX : offsetX + goalDepth - barThickness; graphics.fillRect(backX, -goalWidth / 2, barThickness, goalWidth); this.add(graphics); // Create physics bodies for collision (in world coordinates, not container-relative) const physics = this.scene.physics; // Top post - rigid body (invisible, collision only) this.leftPost = this.scene.add.rectangle( this.x + offsetX + goalDepth / 2, this.y - goalWidth / 2 + postThickness / 2, goalDepth, postThickness ) as any; this.leftPost.setVisible(false); physics.add.existing(this.leftPost, true); // true = static body // Bottom post - rigid body (invisible, collision only) this.rightPost = this.scene.add.rectangle( this.x + offsetX + goalDepth / 2, this.y + goalWidth / 2 - postThickness / 2, goalDepth, postThickness ) as any; this.rightPost.setVisible(false); physics.add.existing(this.rightPost, true); // Back bar (invisible, collision only) this.backBar = this.scene.add.rectangle( this.x + backX + barThickness / 2, this.y, barThickness, goalWidth ) as any; this.backBar.setVisible(false); physics.add.existing(this.backBar, true); // Scoring zone - sensor (no physical collision, just detection) this.scoringZone = this.scene.add.zone( this.x + offsetX + goalDepth / 2, this.y, goalDepth - barThickness * 2, goalWidth - postThickness * 2 ); physics.add.existing(this.scoringZone, true); } public getLeftPost(): Phaser.Physics.Arcade.Image { return this.leftPost; } public getRightPost(): Phaser.Physics.Arcade.Image { return this.rightPost; } public getBackBar(): Phaser.Physics.Arcade.Image { return this.backBar; } public getScoringZone(): Phaser.GameObjects.Zone { return this.scoringZone; } public checkGoal(puck: Puck): void { const puckBody = puck.body; const zoneBody = this.scoringZone.body as Phaser.Physics.Arcade.Body; if (!puckBody || !zoneBody) return; // Check if puck overlaps with scoring zone const overlap = Phaser.Geom.Intersects.RectangleToRectangle( new Phaser.Geom.Rectangle(puckBody.x, puckBody.y, puckBody.width, puckBody.height), new Phaser.Geom.Rectangle(zoneBody.x, zoneBody.y, zoneBody.width, zoneBody.height) ); if (overlap && !this.puckInZone) { this.puckInZone = true; const scoringTeam = this.isLeftGoal ? 'away' : 'home'; // Emit goal event this.scene.events.emit('goal', { team: scoringTeam, goal: this.isLeftGoal ? 'left' : 'right' }); } else if (!overlap && this.puckInZone) { this.puckInZone = false; } } }