2025-10-01 14:51:24 +00:00

145 lines
4.7 KiB
TypeScript

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;
}
}
}