145 lines
4.7 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|