Add alien‑ship warp‑out effect and bullet ownership handling
- Added `warpOut()` to `AlienShip` with spin‑shrink animation and black‑hole ring visual. - Modified `spawnAlienBullet` to pass the firing ship to `Bullet`. - Extended `Bullet` constructor with `ownerEntity` to store the originating `AlienShip`. - Updated collision callbacks in `GameScene` to trigger `warpOut()` on the owning ship when its bullet hits the player or when the player collides with an alien, and to remove the ship from the alien list. - Fixed overlap order for alien bullets vs. player. These changes give alien ships a dramatic disappearance animation and ensure proper cleanup after collisions.
This commit is contained in:
parent
9ac607d2e7
commit
33d340ef10
|
|
@ -37,13 +37,75 @@ export default class AlienShip {
|
||||||
if (time - this.lastFired > ALIEN_FIRE_RATE) {
|
if (time - this.lastFired > ALIEN_FIRE_RATE) {
|
||||||
this.lastFired = time;
|
this.lastFired = time;
|
||||||
const angle = Phaser.Math.RadToDeg(Math.atan2(dy, dx));
|
const angle = Phaser.Math.RadToDeg(Math.atan2(dy, dx));
|
||||||
this.scene.spawnAlienBullet(this.sprite.x, this.sprite.y, angle);
|
this.scene.spawnAlienBullet(this.sprite.x, this.sprite.y, angle, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap around screen
|
// Wrap around screen
|
||||||
this.scene.physics.world.wrap(this.sprite, 40);
|
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() {
|
destroy() {
|
||||||
if (!this.alive) return;
|
if (!this.alive) return;
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ const ALIEN_BULLET_SPEED = 350; // pixels/sec – noticeably slower than player
|
||||||
const BULLET_LIFETIME = 1800; // ms
|
const BULLET_LIFETIME = 1800; // ms
|
||||||
|
|
||||||
export default class Bullet {
|
export default class Bullet {
|
||||||
constructor(scene, x, y, angle, owner, group) {
|
constructor(scene, x, y, angle, owner, group, ownerEntity = null) {
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.owner = owner; // 'player' or 'alien'
|
this.owner = owner; // 'player' or 'alien'
|
||||||
|
this.ownerEntity = ownerEntity; // AlienShip instance that fired this bullet, or null
|
||||||
this.alive = true;
|
this.alive = true;
|
||||||
|
|
||||||
const textureKey = owner === 'player' ? 'bullet' : 'alien_bullet';
|
const textureKey = owner === 'player' ? 'bullet' : 'alien_bullet';
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ export default class GameScene extends Phaser.Scene {
|
||||||
this.onPlayerBulletHitAlien, null, this
|
this.onPlayerBulletHitAlien, null, this
|
||||||
);
|
);
|
||||||
this.physics.add.overlap(
|
this.physics.add.overlap(
|
||||||
this.alienBulletsGroup, this.player.sprite,
|
this.player.sprite, this.alienBulletsGroup,
|
||||||
this.onAlienBulletHitPlayer, null, this
|
this.onAlienBulletHitPlayer, null, this
|
||||||
);
|
);
|
||||||
this.physics.add.overlap(
|
this.physics.add.overlap(
|
||||||
|
|
@ -239,9 +239,9 @@ export default class GameScene extends Phaser.Scene {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnAlienBullet(x, y, angle) {
|
spawnAlienBullet(x, y, angle, ownerShip = null) {
|
||||||
this.alienBullets.push(
|
this.alienBullets.push(
|
||||||
new Bullet(this, x, y, angle, 'alien', this.alienBulletsGroup)
|
new Bullet(this, x, y, angle, 'alien', this.alienBulletsGroup, ownerShip)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,14 +276,20 @@ export default class GameScene extends Phaser.Scene {
|
||||||
this.playerBullets = this.playerBullets.filter(b => b !== bullet);
|
this.playerBullets = this.playerBullets.filter(b => b !== bullet);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAlienBulletHitPlayer(alienBulletSprite, playerSprite) {
|
onAlienBulletHitPlayer(playerSprite, alienBulletSprite) {
|
||||||
if (!alienBulletSprite.active) return;
|
if (!alienBulletSprite.active) return;
|
||||||
const bullet = alienBulletSprite.gameEntity;
|
const bullet = alienBulletSprite.gameEntity;
|
||||||
if (!bullet || !bullet.alive) return;
|
if (!bullet || !bullet.alive) return;
|
||||||
|
|
||||||
|
const ownerShip = bullet.ownerEntity;
|
||||||
bullet.destroy();
|
bullet.destroy();
|
||||||
this.alienBullets = this.alienBullets.filter(b => b !== bullet);
|
this.alienBullets = this.alienBullets.filter(b => b !== bullet);
|
||||||
this.handlePlayerHit();
|
this.handlePlayerHit();
|
||||||
|
|
||||||
|
if (ownerShip && ownerShip.alive) {
|
||||||
|
ownerShip.warpOut();
|
||||||
|
this.aliens = this.aliens.filter(a => a !== ownerShip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPlayerHitAsteroid(playerSprite, asteroidSprite) {
|
onPlayerHitAsteroid(playerSprite, asteroidSprite) {
|
||||||
|
|
@ -297,7 +303,10 @@ export default class GameScene extends Phaser.Scene {
|
||||||
if (!alienSprite.active) return;
|
if (!alienSprite.active) return;
|
||||||
const alien = alienSprite.gameEntity;
|
const alien = alienSprite.gameEntity;
|
||||||
if (!alien || !alien.alive) return;
|
if (!alien || !alien.alive) return;
|
||||||
|
|
||||||
this.handlePlayerHit();
|
this.handlePlayerHit();
|
||||||
|
alien.warpOut();
|
||||||
|
this.aliens = this.aliens.filter(a => a !== alien);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePlayerHit() {
|
handlePlayerHit() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue