Add death/explosion visual effects and impact sparks
- Implement `AlienShip.explode()` with cyan flash, shrinking spin animation and a shockwave ring. - Enhance `Player.die()` to flash white, hide the sprite, and spawn shattering shard graphics. - Introduce `GameScene.spawnImpactSparks()` to create brief yellow spark particles on bullet impacts. - Update bullet‑asteroid and bullet‑alien collision handling to trigger impact sparks and use the new alien explosion. - Minor refactor to use the new visual effects for a more polished death sequence.
This commit is contained in:
parent
9a40e9a23a
commit
dc7f18c17c
|
|
@ -106,6 +106,56 @@ export default class AlienShip {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (0–400ms): 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 (200–700ms): 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() {
|
destroy() {
|
||||||
if (!this.alive) return;
|
if (!this.alive) return;
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
|
|
|
||||||
|
|
@ -102,11 +102,57 @@ export default class Player {
|
||||||
// Hide the ship and go invincible immediately – call before delaying respawn
|
// Hide the ship and go invincible immediately – call before delaying respawn
|
||||||
die() {
|
die() {
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
this.sprite.setActive(false).setVisible(false);
|
|
||||||
this.sprite.body.setVelocity(0, 0);
|
|
||||||
// Go invincible right away so no further collision callbacks count
|
|
||||||
this.invincible = true;
|
this.invincible = true;
|
||||||
this.invincibleUntil = this.scene.time.now + 99999;
|
this.invincibleUntil = this.scene.time.now + 99999;
|
||||||
|
this.sprite.body.setVelocity(0, 0);
|
||||||
|
|
||||||
|
const x = this.sprite.x;
|
||||||
|
const y = this.sprite.y;
|
||||||
|
const angle = this.sprite.angle;
|
||||||
|
|
||||||
|
// White flash for 100ms, then hide
|
||||||
|
this.sprite.setTint(0xffffff);
|
||||||
|
this.scene.time.delayedCall(100, () => {
|
||||||
|
if (this.sprite && this.sprite.active) {
|
||||||
|
this.sprite.clearTint();
|
||||||
|
this.sprite.setActive(false).setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._spawnShatterShards(x, y, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
_spawnShatterShards(x, y, angle) {
|
||||||
|
const scene = this.scene;
|
||||||
|
const defs = [
|
||||||
|
{ angleOffset: -115, dist: 65, size: 7, spin: 270 },
|
||||||
|
{ angleOffset: 115, dist: 65, size: 9, spin: -270 },
|
||||||
|
{ angleOffset: 175, dist: 80, size: 11, spin: 240 },
|
||||||
|
];
|
||||||
|
|
||||||
|
defs.forEach((def, i) => {
|
||||||
|
const gfx = scene.add.graphics();
|
||||||
|
gfx.setDepth(5);
|
||||||
|
gfx.fillStyle(0x00ff44, 1);
|
||||||
|
const s = def.size;
|
||||||
|
if (i === 0) gfx.fillTriangle(0, -s, -s * 0.6, s * 0.5, s * 0.4, s * 0.5);
|
||||||
|
else if (i === 1) gfx.fillTriangle(s * 0.5, -s * 0.8, -s * 0.5, -s * 0.3, 0, s);
|
||||||
|
else gfx.fillTriangle(-s * 0.7, 0, s * 0.7, 0, 0, s);
|
||||||
|
gfx.x = x;
|
||||||
|
gfx.y = y;
|
||||||
|
|
||||||
|
const rad = Phaser.Math.DegToRad(angle + def.angleOffset);
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: gfx,
|
||||||
|
x: x + Math.cos(rad) * def.dist,
|
||||||
|
y: y + Math.sin(rad) * def.dist,
|
||||||
|
angle: gfx.angle + def.spin,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 800,
|
||||||
|
ease: 'Quad.Out',
|
||||||
|
onComplete: () => gfx.destroy()
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makeInvincible() {
|
makeInvincible() {
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,7 @@ export default class GameScene extends Phaser.Scene {
|
||||||
const scoreTable = { large: 20, medium: 50, small: 100 };
|
const scoreTable = { large: 20, medium: 50, small: 100 };
|
||||||
this.addScore(scoreTable[asteroid.size] || 20);
|
this.addScore(scoreTable[asteroid.size] || 20);
|
||||||
|
|
||||||
|
this.spawnImpactSparks(bulletSprite.x, bulletSprite.y);
|
||||||
this.splitAsteroid(asteroid);
|
this.splitAsteroid(asteroid);
|
||||||
|
|
||||||
bullet.destroy();
|
bullet.destroy();
|
||||||
|
|
@ -185,7 +186,8 @@ export default class GameScene extends Phaser.Scene {
|
||||||
if (!bullet || !alien || !bullet.alive || !alien.alive) return;
|
if (!bullet || !alien || !bullet.alive || !alien.alive) return;
|
||||||
|
|
||||||
this.addScore(200);
|
this.addScore(200);
|
||||||
alien.destroy();
|
this.spawnImpactSparks(bulletSprite.x, bulletSprite.y);
|
||||||
|
alien.explode();
|
||||||
this.aliens = this.aliens.filter(a => a !== alien);
|
this.aliens = this.aliens.filter(a => a !== alien);
|
||||||
|
|
||||||
bullet.destroy();
|
bullet.destroy();
|
||||||
|
|
@ -270,6 +272,30 @@ export default class GameScene extends Phaser.Scene {
|
||||||
this.scoreText.setText(`SCORE: ${this.score}`);
|
this.scoreText.setText(`SCORE: ${this.score}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawnImpactSparks(x, y) {
|
||||||
|
const count = Phaser.Math.Between(3, 5);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const gfx = this.add.graphics();
|
||||||
|
gfx.setDepth(5);
|
||||||
|
gfx.fillStyle(0xffff00, 1);
|
||||||
|
gfx.fillCircle(0, 0, Phaser.Math.Between(2, 4));
|
||||||
|
gfx.x = x;
|
||||||
|
gfx.y = y;
|
||||||
|
|
||||||
|
const angle = Math.random() * Math.PI * 2;
|
||||||
|
const dist = Phaser.Math.Between(20, 50);
|
||||||
|
this.tweens.add({
|
||||||
|
targets: gfx,
|
||||||
|
x: x + Math.cos(angle) * dist,
|
||||||
|
y: y + Math.sin(angle) * dist,
|
||||||
|
alpha: 0,
|
||||||
|
duration: 250,
|
||||||
|
ease: 'Quad.Out',
|
||||||
|
onComplete: () => gfx.destroy()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
triggerGameOver() {
|
triggerGameOver() {
|
||||||
this.gameOver = true;
|
this.gameOver = true;
|
||||||
this.player.alive = false;
|
this.player.alive = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue