100 lines
3.4 KiB
JavaScript
100 lines
3.4 KiB
JavaScript
const PLAYER_BULLET_SPEED = 700; // pixels/sec
|
|
const ALIEN_BULLET_SPEED = 350; // pixels/sec
|
|
const MISSILE_SPEED = 180; // pixels/sec — slow enough for the player to outrun
|
|
const MISSILE_TURN_RATE = 120; // degrees/sec max steering
|
|
|
|
const BULLET_LIFETIME = 1800; // ms
|
|
const MISSILE_LIFETIME = 5000; // ms
|
|
|
|
export default class Bullet {
|
|
constructor(scene, x, y, angle, owner, group, ownerEntity = null) {
|
|
this.scene = scene;
|
|
this.owner = owner; // 'player', 'alien', or 'missile'
|
|
this.ownerEntity = ownerEntity;
|
|
this.alive = true;
|
|
this.isHoming = (owner === 'missile');
|
|
|
|
const textureKey = owner === 'player' ? 'bullet'
|
|
: owner === 'missile' ? 'homing_missile'
|
|
: 'alien_bullet';
|
|
|
|
this.sprite = scene.physics.add.sprite(x, y, textureKey);
|
|
group.add(this.sprite);
|
|
this.sprite.gameEntity = this;
|
|
this.sprite.body.setAllowGravity(false);
|
|
|
|
const speed = owner === 'player' ? PLAYER_BULLET_SPEED
|
|
: owner === 'missile' ? MISSILE_SPEED
|
|
: ALIEN_BULLET_SPEED;
|
|
|
|
const rad = Phaser.Math.DegToRad(angle);
|
|
this.sprite.setVelocity(Math.cos(rad) * speed, Math.sin(rad) * speed);
|
|
|
|
// Track current heading for homing missiles; rotate sprite to face direction of travel
|
|
this.currentAngleDeg = angle;
|
|
if (this.isHoming) {
|
|
this.sprite.setAngle(angle);
|
|
}
|
|
|
|
this.createdAt = scene.time.now;
|
|
this.lifetime = this.isHoming ? MISSILE_LIFETIME : BULLET_LIFETIME;
|
|
}
|
|
|
|
update(time) {
|
|
if (!this.alive) return false;
|
|
|
|
// Expire by lifetime
|
|
if (time - this.createdAt > this.lifetime) {
|
|
this.destroy();
|
|
return false;
|
|
}
|
|
|
|
// Expire if far off-screen
|
|
const { x, y } = this.sprite;
|
|
if (x < -80 || x > 1680 || y < -80 || y > 980) {
|
|
this.destroy();
|
|
return false;
|
|
}
|
|
|
|
// Homing steering — updates velocity and rotation every frame
|
|
if (this.isHoming) {
|
|
this._steerTowardPlayer();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
_steerTowardPlayer() {
|
|
const player = this.scene.player;
|
|
if (!player || !player.alive || !player.sprite.active) return;
|
|
|
|
// Cap dt to avoid huge first-frame jumps
|
|
const dt = Math.min(this.scene.game.loop.delta / 1000, 0.1);
|
|
|
|
const targetAngle = Phaser.Math.RadToDeg(
|
|
Math.atan2(
|
|
player.sprite.y - this.sprite.y,
|
|
player.sprite.x - this.sprite.x
|
|
)
|
|
);
|
|
|
|
// Rotate toward target at limited turn rate
|
|
const maxTurn = MISSILE_TURN_RATE * dt;
|
|
const diff = Phaser.Math.Angle.ShortestBetween(this.currentAngleDeg, targetAngle);
|
|
this.currentAngleDeg += Phaser.Math.Clamp(diff, -maxTurn, maxTurn);
|
|
|
|
// Apply updated velocity and rotate sprite to face direction of travel
|
|
const rad = Phaser.Math.DegToRad(this.currentAngleDeg);
|
|
this.sprite.setVelocity(Math.cos(rad) * MISSILE_SPEED, Math.sin(rad) * MISSILE_SPEED);
|
|
this.sprite.setAngle(this.currentAngleDeg);
|
|
}
|
|
|
|
destroy() {
|
|
if (!this.alive) return;
|
|
this.alive = false;
|
|
if (this.sprite && this.sprite.active) {
|
|
this.sprite.destroy();
|
|
}
|
|
}
|
|
}
|