Asteroids-2026/js/entities/Bullet.js

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