Asteroids-2026/js/entities/AlienShip.js

117 lines
3.7 KiB
JavaScript

const ALIEN_SPEED = 130; // pixels/sec
const ALIEN_FIRE_RATE = 2500; // ms between shots
export default class AlienShip {
constructor(scene, x, y, group) {
this.scene = scene;
this.alive = true;
this.sprite = scene.physics.add.sprite(x, y, 'alien');
group.add(this.sprite);
this.sprite.gameEntity = this;
this.sprite.body.setAllowGravity(false);
// Circular hitbox: texture is 64x48, use radius 20 centered at (32,24)
this.sprite.body.setCircle(20, 12, 4);
this.lastFired = 0;
}
update(time, player) {
if (!this.alive) return;
if (!player || !player.alive || !player.sprite.active) return;
// Move toward player
const dx = player.sprite.x - this.sprite.x;
const dy = player.sprite.y - this.sprite.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 5) {
this.sprite.setVelocity(
(dx / dist) * ALIEN_SPEED,
(dy / dist) * ALIEN_SPEED
);
}
// Fire at player periodically
if (time - this.lastFired > ALIEN_FIRE_RATE) {
this.lastFired = time;
const angle = Phaser.Math.RadToDeg(Math.atan2(dy, dx));
this.scene.spawnAlienBullet(this.sprite.x, this.sprite.y, angle, this);
}
// Wrap around screen
this.scene.physics.world.wrap(this.sprite, 40);
}
warpOut() {
if (!this.alive) return;
this.alive = false;
this.sprite.body.setVelocity(0, 0);
const scene = this.scene;
const x = this.sprite.x;
const y = this.sprite.y;
// Spin and shrink the sprite into the singularity
scene.tweens.add({
targets: this.sprite,
scaleX: 0,
scaleY: 0,
angle: this.sprite.angle + 720,
duration: 500,
ease: 'Cubic.In',
onComplete: () => {
if (this.sprite && this.sprite.active) this.sprite.destroy();
}
});
// Black-hole ring effect
const gfx = scene.add.graphics();
const counter = { t: 0 };
scene.tweens.add({
targets: counter,
t: 1,
duration: 650,
onUpdate: () => {
const t = counter.t;
gfx.clear();
// Ring expands 0→38px over first 30%, then collapses 38→0px
const r = t < 0.3
? (t / 0.3) * 38
: ((1 - t) / 0.7) * 38;
if (r < 1) return;
const a = Math.max(0, 1 - t * 0.7);
// Dark singularity core
gfx.fillStyle(0x000000, a);
gfx.fillCircle(x, y, r * 0.55);
// Outer cyan ring
gfx.lineStyle(3, 0x00ffff, a);
gfx.strokeCircle(x, y, r);
// Inner white ring
gfx.lineStyle(1, 0xffffff, a * 0.6);
gfx.strokeCircle(x, y, r * 0.55);
// Rotating distortion spokes
const off = t * Math.PI * 6;
for (let i = 0; i < 6; i++) {
const ang = (i / 6) * Math.PI * 2 + off;
gfx.lineStyle(1, 0x88ffff, a * 0.7);
gfx.beginPath();
gfx.moveTo(x + Math.cos(ang) * r * 0.55, y + Math.sin(ang) * r * 0.55);
gfx.lineTo(x + Math.cos(ang) * r, y + Math.sin(ang) * r);
gfx.strokePath();
}
},
onComplete: () => gfx.destroy()
});
}
destroy() {
if (!this.alive) return;
this.alive = false;
if (this.sprite && this.sprite.active) {
this.sprite.destroy();
}
}
}