Add audio assets and integrate music/SFX handling
- Added new sound effect and music MP3 files to the project. - Updated **BootScene** to preload all music tracks and sound effects. - Implemented music playback: * MenuScene now plays looping title music and stops it on shutdown. * GameScene now stops any playing music on shutdown and starts level‑specific background music with fallback to the last track. - Added SFX triggers for player shooting, alien shooting, missile launch, impacts, alien death, and player death. - Cleaned up formatting and minor code style issues in GameScene (group/array declarations, tweens, UI text creation, level setup loops, etc.). - Ensured proper cleanup of music objects when scenes change.
This commit is contained in:
parent
f194886ed9
commit
26a80b6b89
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -7,6 +7,24 @@ export default class BootScene extends Phaser.Scene {
|
|||
super({ key: 'BootScene' });
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.audio('title', 'js/audio/music/title.mp3');
|
||||
this.load.audio('track01', 'js/audio/music/track01.mp3');
|
||||
this.load.audio('track02', 'js/audio/music/track02.mp3');
|
||||
this.load.audio('track03', 'js/audio/music/track03.mp3');
|
||||
this.load.audio('track04', 'js/audio/music/track04.mp3');
|
||||
this.load.audio('track05', 'js/audio/music/track05.mp3');
|
||||
this.load.audio('track06', 'js/audio/music/track06.mp3');
|
||||
this.load.audio('track07', 'js/audio/music/track07.mp3');
|
||||
|
||||
this.load.audio('sfx_player_shoot', 'js/audio/fx/player_shoot.mp3');
|
||||
this.load.audio('sfx_player_death', 'js/audio/fx/player_death.mp3');
|
||||
this.load.audio('sfx_alien_shoot', 'js/audio/fx/alien_shoot.mp3');
|
||||
this.load.audio('sfx_missle', 'js/audio/fx/missle.mp3');
|
||||
this.load.audio('sfx_impact', 'js/audio/fx/impact.mp3');
|
||||
this.load.audio('sfx_alien_death', 'js/audio/fx/alien_death.mp3');
|
||||
}
|
||||
|
||||
create() {
|
||||
this._createTextures();
|
||||
this.scene.start('MenuScene');
|
||||
|
|
|
|||
|
|
@ -20,16 +20,16 @@ export default class GameScene extends Phaser.Scene {
|
|||
this.levelSpeedMult = 1;
|
||||
|
||||
// Physics groups (used for overlap detection)
|
||||
this.asteroidsGroup = this.physics.add.group();
|
||||
this.aliensGroup = this.physics.add.group();
|
||||
this.asteroidsGroup = this.physics.add.group();
|
||||
this.aliensGroup = this.physics.add.group();
|
||||
this.playerBulletsGroup = this.physics.add.group();
|
||||
this.alienBulletsGroup = this.physics.add.group();
|
||||
this.alienBulletsGroup = this.physics.add.group();
|
||||
|
||||
// Entity arrays for custom update logic
|
||||
this.asteroids = [];
|
||||
this.aliens = [];
|
||||
this.asteroids = [];
|
||||
this.aliens = [];
|
||||
this.playerBullets = [];
|
||||
this.alienBullets = [];
|
||||
this.alienBullets = [];
|
||||
|
||||
this.player = new Player(this, 800, 450);
|
||||
|
||||
|
|
@ -49,11 +49,14 @@ export default class GameScene extends Phaser.Scene {
|
|||
this.cameras.main.setPostPipeline('CRTPipeline');
|
||||
}
|
||||
|
||||
// Stop level music when this scene shuts down (e.g. returning to menu)
|
||||
this.events.once('shutdown', () => { if (this.music) this.music.stop(); });
|
||||
|
||||
// Custom reticle cursor – hide the OS cursor and draw our own
|
||||
this.game.canvas.style.cursor = 'none';
|
||||
this.reticleOuter = this.add.image(800, 450, 'reticle_outer').setDepth(20);
|
||||
this.reticleInner = this.add.image(800, 450, 'reticle_inner').setDepth(20);
|
||||
this.tweens.add({ targets: this.reticleOuter, angle: 360, duration: 8000, repeat: -1, ease: 'Linear' });
|
||||
this.tweens.add({ targets: this.reticleOuter, angle: 360, duration: 8000, repeat: -1, ease: 'Linear' });
|
||||
this.tweens.add({ targets: this.reticleInner, angle: -360, duration: 12000, repeat: -1, ease: 'Linear' });
|
||||
this.events.once('shutdown', () => {
|
||||
this.game.canvas.style.cursor = 'default';
|
||||
|
|
@ -91,11 +94,11 @@ export default class GameScene extends Phaser.Scene {
|
|||
|
||||
createUI() {
|
||||
const mono = { fontFamily: 'monospace', fill: '#ffffff' };
|
||||
this.scoreText = this.add.text(16, 16, 'SCORE: 0', { ...mono, fontSize: '20px' }).setDepth(10);
|
||||
this.livesText = this.add.text(16, 44, 'LIVES: 3', { ...mono, fontSize: '20px' }).setDepth(10);
|
||||
this.levelText = this.add.text(800, 16, 'LEVEL 1', { ...mono, fontSize: '24px' })
|
||||
this.scoreText = this.add.text(16, 16, 'SCORE: 0', { ...mono, fontSize: '20px' }).setDepth(10);
|
||||
this.livesText = this.add.text(16, 44, 'LIVES: 3', { ...mono, fontSize: '20px' }).setDepth(10);
|
||||
this.levelText = this.add.text(800, 16, 'LEVEL 1', { ...mono, fontSize: '24px' })
|
||||
.setOrigin(0.5, 0).setDepth(10);
|
||||
this.msgText = this.add.text(800, 420, '', { ...mono, fontSize: '52px', align: 'center' })
|
||||
this.msgText = this.add.text(800, 420, '', { ...mono, fontSize: '52px', align: 'center' })
|
||||
.setOrigin(0.5).setDepth(10).setVisible(false);
|
||||
}
|
||||
|
||||
|
|
@ -115,14 +118,24 @@ export default class GameScene extends Phaser.Scene {
|
|||
|
||||
// ─── Level Management ────────────────────────────────────────────────────
|
||||
|
||||
_playLevelMusic() {
|
||||
if (this.music) { this.music.stop(); this.music.destroy(); this.music = null; }
|
||||
const trackNum = String(this.level).padStart(2, '0');
|
||||
const key = `track${trackNum}`;
|
||||
// Fall back to the highest available track if this level doesn't have one yet
|
||||
const resolvedKey = this.cache.audio.exists(key) ? key : 'track07';
|
||||
this.music = this.sound.add(resolvedKey, { loop: true, volume: 0.7 });
|
||||
this.music.play();
|
||||
}
|
||||
|
||||
startLevel() {
|
||||
this.levelComplete = false;
|
||||
|
||||
// Clear all entities
|
||||
this.asteroids.forEach(a => a.destroy()); this.asteroids = [];
|
||||
this.aliens.forEach(a => a.destroy()); this.aliens = [];
|
||||
this.asteroids.forEach(a => a.destroy()); this.asteroids = [];
|
||||
this.aliens.forEach(a => a.destroy()); this.aliens = [];
|
||||
this.playerBullets.forEach(b => b.destroy()); this.playerBullets = [];
|
||||
this.alienBullets.forEach(b => b.destroy()); this.alienBullets = [];
|
||||
this.alienBullets.forEach(b => b.destroy()); this.alienBullets = [];
|
||||
|
||||
// Respawn player at center
|
||||
this.player.respawn(800, 450);
|
||||
|
|
@ -130,20 +143,21 @@ export default class GameScene extends Phaser.Scene {
|
|||
// Progressive asteroid difficulty: more rocks, mixed sizes, faster per level
|
||||
this.levelSpeedMult = Math.min(1 + (this.level - 1) * 0.12, 2.0);
|
||||
|
||||
const largeCount = Math.max(3, 2 + this.level);
|
||||
const largeCount = Math.max(3, 2 + this.level);
|
||||
const mediumCount = Math.floor(this.level / 2);
|
||||
const smallCount = Math.floor(this.level / 3);
|
||||
const smallCount = Math.floor(this.level / 3);
|
||||
|
||||
const spawnOne = (size) => {
|
||||
const { x, y } = this.edgePosition();
|
||||
this.asteroids.push(new Asteroid(this, x, y, size, this.asteroidsGroup, this.levelSpeedMult));
|
||||
};
|
||||
for (let i = 0; i < largeCount; i++) spawnOne('large');
|
||||
for (let i = 0; i < largeCount; i++) spawnOne('large');
|
||||
for (let i = 0; i < mediumCount; i++) spawnOne('medium');
|
||||
for (let i = 0; i < smallCount; i++) spawnOne('small');
|
||||
for (let i = 0; i < smallCount; i++) spawnOne('small');
|
||||
|
||||
this.updateUI();
|
||||
this.showMessage(`LEVEL ${this.level}`, 2000);
|
||||
this._playLevelMusic();
|
||||
}
|
||||
|
||||
// Random position along any screen edge, away from center
|
||||
|
|
@ -153,10 +167,10 @@ export default class GameScene extends Phaser.Scene {
|
|||
do {
|
||||
const edge = Phaser.Math.Between(0, 3);
|
||||
switch (edge) {
|
||||
case 0: x = Phaser.Math.Between(0, W); y = 0; break;
|
||||
case 1: x = Phaser.Math.Between(0, W); y = H; break;
|
||||
case 2: x = 0; y = Phaser.Math.Between(0, H); break;
|
||||
default: x = W; y = Phaser.Math.Between(0, H); break;
|
||||
case 0: x = Phaser.Math.Between(0, W); y = 0; break;
|
||||
case 1: x = Phaser.Math.Between(0, W); y = H; break;
|
||||
case 2: x = 0; y = Phaser.Math.Between(0, H); break;
|
||||
default: x = W; y = Phaser.Math.Between(0, H); break;
|
||||
}
|
||||
} while (Phaser.Math.Distance.Between(x, y, 800, 450) < 200);
|
||||
return { x, y };
|
||||
|
|
@ -186,18 +200,21 @@ export default class GameScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
spawnPlayerBullet(x, y, angle) {
|
||||
this.sound.play('sfx_player_shoot', { volume: 0.2 });
|
||||
this.playerBullets.push(
|
||||
new Bullet(this, x, y, angle, 'player', this.playerBulletsGroup)
|
||||
);
|
||||
}
|
||||
|
||||
spawnAlienBullet(x, y, angle, ownerShip = null) {
|
||||
this.sound.play('sfx_alien_shoot', { volume: 0.45 });
|
||||
this.alienBullets.push(
|
||||
new Bullet(this, x, y, angle, 'alien', this.alienBulletsGroup, ownerShip)
|
||||
);
|
||||
}
|
||||
|
||||
spawnHomingMissile(x, y, ownerShip) {
|
||||
this.sound.play('sfx_missle', { volume: 0.5 });
|
||||
const player = this.player;
|
||||
const angle = Phaser.Math.RadToDeg(
|
||||
Math.atan2(player.sprite.y - y, player.sprite.x - x)
|
||||
|
|
@ -211,7 +228,7 @@ export default class GameScene extends Phaser.Scene {
|
|||
|
||||
onPlayerBulletHitAsteroid(bulletSprite, asteroidSprite) {
|
||||
if (!bulletSprite.active || !asteroidSprite.active) return;
|
||||
const bullet = bulletSprite.gameEntity;
|
||||
const bullet = bulletSprite.gameEntity;
|
||||
const asteroid = asteroidSprite.gameEntity;
|
||||
if (!bullet || !asteroid || !bullet.alive || !asteroid.alive) return;
|
||||
|
||||
|
|
@ -228,11 +245,12 @@ export default class GameScene extends Phaser.Scene {
|
|||
onPlayerBulletHitAlien(bulletSprite, alienSprite) {
|
||||
if (!bulletSprite.active || !alienSprite.active) return;
|
||||
const bullet = bulletSprite.gameEntity;
|
||||
const alien = alienSprite.gameEntity;
|
||||
const alien = alienSprite.gameEntity;
|
||||
if (!bullet || !alien || !bullet.alive || !alien.alive) return;
|
||||
|
||||
this.addScore(200);
|
||||
this.spawnImpactSparks(bulletSprite.x, bulletSprite.y);
|
||||
this.sound.play('sfx_alien_death', { volume: 0.65 });
|
||||
alien.explode();
|
||||
this.aliens = this.aliens.filter(a => a !== alien);
|
||||
|
||||
|
|
@ -276,6 +294,7 @@ export default class GameScene extends Phaser.Scene {
|
|||
handlePlayerHit() {
|
||||
if (!this.player.hit()) return; // invincible – ignore
|
||||
|
||||
this.sound.play('sfx_player_death', { volume: 0.7 });
|
||||
this.player.die(); // hide ship and go invincible immediately
|
||||
this.lives--;
|
||||
this.updateUI();
|
||||
|
|
@ -319,6 +338,7 @@ export default class GameScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
spawnImpactSparks(x, y) {
|
||||
this.sound.play('sfx_impact', { volume: 1.0 });
|
||||
const count = Phaser.Math.Between(3, 5);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const gfx = this.add.graphics();
|
||||
|
|
@ -329,7 +349,7 @@ export default class GameScene extends Phaser.Scene {
|
|||
gfx.y = y;
|
||||
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const dist = Phaser.Math.Between(20, 50);
|
||||
const dist = Phaser.Math.Between(20, 50);
|
||||
this.tweens.add({
|
||||
targets: gfx,
|
||||
x: x + Math.cos(angle) * dist,
|
||||
|
|
@ -408,7 +428,7 @@ export default class GameScene extends Phaser.Scene {
|
|||
|
||||
// Update bullets; keep only still-alive ones
|
||||
this.playerBullets = this.playerBullets.filter(b => b.update(time));
|
||||
this.alienBullets = this.alienBullets.filter(b => b.update(time));
|
||||
this.alienBullets = this.alienBullets.filter(b => b.update(time));
|
||||
|
||||
this.checkLevelComplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ export default class MenuScene extends Phaser.Scene {
|
|||
if (this.renderer.type === Phaser.WEBGL) {
|
||||
this.cameras.main.setPostPipeline('CRTPipeline');
|
||||
}
|
||||
|
||||
// Title music — looping; stop when this scene shuts down
|
||||
this.music = this.sound.add('title', { loop: true, volume: 0.7 });
|
||||
this.music.play();
|
||||
this.events.once('shutdown', () => { if (this.music) this.music.stop(); });
|
||||
}
|
||||
|
||||
update() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue