Asteroids-2026/js/entities/AlienShip.js

167 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
});
}
explode() {
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;
// Brief cyan flash before implosion
this.sprite.setTint(0x00ffff);
scene.time.delayedCall(60, () => {
if (this.sprite && this.sprite.active) this.sprite.clearTint();
});
// Phase 1 (0400ms): sprite collapses and spins 360°
scene.tweens.add({
targets: this.sprite,
scaleX: 0,
scaleY: 0,
angle: this.sprite.angle + 360,
duration: 400,
ease: 'Cubic.In',
onComplete: () => {
if (this.sprite && this.sprite.active) this.sprite.destroy();
}
});
// Phase 2 (200700ms): cyan shockwave ring expands and fades
scene.time.delayedCall(200, () => {
const gfx = scene.add.graphics();
gfx.setDepth(5);
const counter = { t: 0 };
scene.tweens.add({
targets: counter,
t: 1,
duration: 500,
onUpdate: () => {
const t = counter.t;
gfx.clear();
const radius = t * 120;
if (radius < 1) return;
gfx.lineStyle(3, 0x00ffff, Math.max(0, 1 - t));
gfx.strokeCircle(x, y, radius);
},
onComplete: () => gfx.destroy()
});
});
}
destroy() {
if (!this.alive) return;
this.alive = false;
if (this.sprite && this.sprite.active) {
this.sprite.destroy();
}
}
}