```git commit message
Implement wave-based enemy system with shooting enemies, improved visuals, and sound effects - Added enemy bullet system with dedicated group and canvas texture - Implemented wave progression system (3 waves) with increasing difficulty - Enhanced enemy spawning with wave-specific properties and shooting mechanics - Added player explosion animation on death with sound effect - Included wave start notifications with scaling text animation - Improved background scrolling and enemy off-screen cleanup - Integrated new sound assets for enemy shooting, player death, and wave transitions - Updated game scene with proper texture handling for different enemy types across waves This commit transforms the basic shooter into a structured wave-based gameplay experience with escalating challenge levels. ```
This commit is contained in:
parent
8a7b321872
commit
bd00c62205
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -17,6 +17,8 @@
|
|||
}
|
||||
#game-container {
|
||||
position: relative;
|
||||
}
|
||||
#game-container canvas {
|
||||
cursor: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Binary file not shown.
BIN
raw/player.psd
BIN
raw/player.psd
Binary file not shown.
|
|
@ -8,6 +8,9 @@ export class GameScene extends Phaser.Scene {
|
|||
this.player = null;
|
||||
this.enemies = null;
|
||||
this.bullets = null;
|
||||
|
||||
// Enemy bullets group
|
||||
this.enemyBullets = null;
|
||||
|
||||
// Game state
|
||||
this.score = 0;
|
||||
|
|
@ -15,6 +18,10 @@ export class GameScene extends Phaser.Scene {
|
|||
|
||||
// Mouse reticle
|
||||
this.reticle = null;
|
||||
|
||||
// Wave system
|
||||
this.waveTimer = 0;
|
||||
this.currentWave = 0;
|
||||
}
|
||||
|
||||
preload() {
|
||||
|
|
@ -34,11 +41,19 @@ export class GameScene extends Phaser.Scene {
|
|||
frameWidth: 48,
|
||||
frameHeight: 48
|
||||
});
|
||||
this.load.spritesheet('enemy-sprite', 'assets/enemy-sprite.png', {
|
||||
frameWidth: 48,
|
||||
frameHeight: 48
|
||||
});
|
||||
|
||||
// Load background music and sounds
|
||||
this.load.audio('bgm', 'assets/music/NeonPulse.mp3');
|
||||
this.load.audio('main-gun', 'assets/sounds/main-gun.mp3');
|
||||
this.load.audio('enemy-kill', 'assets/sounds/enemy-kill.mp3');
|
||||
this.load.audio('enemy-shoot', 'assets/sounds/enemy-shoot.mp3');
|
||||
this.load.audio('player-death', 'assets/sounds/player-death.mp3');
|
||||
this.load.audio('enemy-shoot', 'assets/sounds/enemy-shoot.mp3');
|
||||
this.load.audio('next-wave', 'assets/sounds/next-wave.mp3');
|
||||
|
||||
// Create simple placeholder graphics for now
|
||||
this.createPlaceholderGraphics();
|
||||
|
|
@ -57,11 +72,13 @@ export class GameScene extends Phaser.Scene {
|
|||
|
||||
// Create groups for bullets and enemies
|
||||
this.bullets = this.physics.add.group();
|
||||
this.enemyBullets = this.physics.add.group();
|
||||
this.enemies = this.physics.add.group();
|
||||
|
||||
// Setup collision detection
|
||||
this.physics.add.overlap(this.bullets, this.enemies, this.hitEnemy, null, this);
|
||||
this.physics.add.overlap(this.player.sprite, this.enemies, this.hitPlayer, null, this);
|
||||
this.physics.add.overlap(this.player.sprite, this.enemyBullets, this.hitPlayer, null, this);
|
||||
|
||||
// Start enemy spawning
|
||||
this.time.addEvent({
|
||||
|
|
@ -94,18 +111,31 @@ export class GameScene extends Phaser.Scene {
|
|||
//this.bgSpeed3 = 0.8;
|
||||
}
|
||||
|
||||
update() {
|
||||
update(time, delta) {
|
||||
if (this.gameOver) return;
|
||||
|
||||
// Update background positions for parallax effect
|
||||
this.updateBackgrounds();
|
||||
|
||||
// Update wave timer
|
||||
this.waveTimer += delta;
|
||||
|
||||
// Check for wave changes every 30 seconds (30,000 milliseconds)
|
||||
if (this.waveTimer >= 5000 && this.currentWave === 0) {
|
||||
this.startWave(1);
|
||||
} else if (this.waveTimer >= 10000 && this.currentWave === 1) {
|
||||
this.startWave(2);
|
||||
}
|
||||
|
||||
// Get A/D keys for player movement
|
||||
const aKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);
|
||||
const dKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);
|
||||
|
||||
// Update player with A/D controls
|
||||
this.player.update(aKey, dKey);
|
||||
|
||||
// Check if enemies are off-screen and destroy them
|
||||
this.checkEnemiesOffScreen();
|
||||
}
|
||||
|
||||
updateBackgrounds() {
|
||||
|
|
@ -115,7 +145,6 @@ export class GameScene extends Phaser.Scene {
|
|||
//this.bgLayer3.x -= this.bgSpeed3;
|
||||
|
||||
// Reset positions to create continuous scrolling effect
|
||||
console.log(this.bgLayer1.y);
|
||||
if (this.bgLayer1.y > 736) {
|
||||
this.bgLayer1.y = 30; // Width of game + layer width
|
||||
}
|
||||
|
|
@ -157,18 +186,6 @@ export class GameScene extends Phaser.Scene {
|
|||
canvas.width = 32;
|
||||
canvas.height = 32;
|
||||
|
||||
// // Player ship (triangle)
|
||||
// const ctx = canvas.getContext('2d');
|
||||
// ctx.fillStyle = '#00ffcc';
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(16, 5);
|
||||
// ctx.lineTo(5, 30);
|
||||
// ctx.lineTo(27, 30);
|
||||
// ctx.closePath();
|
||||
// ctx.fill();
|
||||
|
||||
// this.textures.addCanvas('player', canvas);
|
||||
|
||||
// Enemy (square)
|
||||
const enemyCanvas = document.createElement('canvas');
|
||||
enemyCanvas.width = 24;
|
||||
|
|
@ -192,6 +209,19 @@ export class GameScene extends Phaser.Scene {
|
|||
bCtx.fill();
|
||||
|
||||
this.textures.addCanvas('bullet', bulletCanvas);
|
||||
|
||||
// Enemy Bullet (small circle)
|
||||
const enemyBulletCanvas = document.createElement('canvas');
|
||||
enemyBulletCanvas.width = 8;
|
||||
enemyBulletCanvas.height = 8;
|
||||
|
||||
const ebCtx = enemyBulletCanvas.getContext('2d');
|
||||
ebCtx.fillStyle = '#00ffcc';
|
||||
ebCtx.beginPath();
|
||||
ebCtx.arc(4, 4, 3, 0, Math.PI * 2);
|
||||
ebCtx.fill();
|
||||
|
||||
this.textures.addCanvas('enemyBullet', enemyBulletCanvas);
|
||||
}
|
||||
|
||||
createUI() {
|
||||
|
|
@ -238,15 +268,53 @@ export class GameScene extends Phaser.Scene {
|
|||
|
||||
spawnEnemy() {
|
||||
// Spawn enemies at random x position within game width (800px)
|
||||
const x = Phaser.Math.Between(20, 780);
|
||||
const enemy = this.enemies.create(x, 0, 'enemy');
|
||||
const x = Phaser.Math.Between(20, 580);
|
||||
|
||||
// Calculate texture range with minimum of 0
|
||||
const minTexture = Math.max(0, this.currentWave - 2);
|
||||
const maxTexture = this.currentWave;
|
||||
|
||||
// Ensure we don't get negative values
|
||||
const texture = Phaser.Math.Between(minTexture, maxTexture);
|
||||
|
||||
const enemy = this.enemies.create(x, 0, 'enemy-sprite', texture);
|
||||
|
||||
// Randomize enemy speed
|
||||
const speed = Phaser.Math.Between(50, 150);
|
||||
enemy.setVelocityY(speed);
|
||||
|
||||
// Add some rotation for visual effect
|
||||
enemy.setRotation(Phaser.Math.FloatBetween(-0.2, 0.2));
|
||||
//enemy.setRotation(Phaser.Math.FloatBetween(-0.2, 0.2));
|
||||
|
||||
// Apply wave-specific properties
|
||||
this.applyWaveProperties(enemy, texture);
|
||||
}
|
||||
|
||||
applyWaveProperties(enemy, texture) {
|
||||
// Set the current wave number on the enemy
|
||||
enemy.waveNumber = this.currentWave;
|
||||
|
||||
// Wave 1: Basic enemies (no shooting)
|
||||
if (this.currentWave === 0) {
|
||||
enemy.canShoot = false;
|
||||
if (enemy.shootTimer) {
|
||||
enemy.shootTimer.remove();
|
||||
enemy.shootTimer = null;
|
||||
}
|
||||
}
|
||||
// Wave 2: Enemies that can shoot
|
||||
else if (this.currentWave >= 1 && texture > 0) {
|
||||
enemy.canShoot = true;
|
||||
// Only set up shooting timer if it doesn't exist yet
|
||||
if (!enemy.shootTimer) {
|
||||
enemy.shootTimer = this.time.addEvent({
|
||||
delay: Phaser.Math.Between(1000, 3000),
|
||||
callback: () => this.enemyShoot(enemy, texture),
|
||||
callbackScope: this,
|
||||
loop: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hitEnemy(bullet, enemy) {
|
||||
|
|
@ -259,8 +327,37 @@ export class GameScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
hitPlayer(player, enemy) {
|
||||
// Create explosion at player's position
|
||||
const explosion = this.add.sprite(this.player.sprite.x, this.player.sprite.y, 'player-sprite', 3);
|
||||
const explosion2 = this.add.sprite(this.player.sprite.x, this.player.sprite.y, 'player-sprite', 3);
|
||||
|
||||
// Add rotation and scaling animation
|
||||
this.tweens.add({
|
||||
targets: explosion,
|
||||
scale: 3, // Scale up to 3x
|
||||
rotation: Math.PI * 2, // Full rotation
|
||||
alpha: 0.5, // Fade out slightly
|
||||
duration: 1000,
|
||||
ease: 'Power2',
|
||||
onComplete: () => {
|
||||
explosion.destroy();
|
||||
}
|
||||
});
|
||||
this.tweens.add({
|
||||
targets: explosion2,
|
||||
scale: 3, // Scale up to 3x
|
||||
rotation: -Math.PI * 2, // Full rotation
|
||||
alpha: 0.5, // Fade out slightly
|
||||
duration: 1000,
|
||||
ease: 'Power2',
|
||||
onComplete: () => {
|
||||
explosion2.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
player.destroy();
|
||||
enemy.destroy();
|
||||
this.sound.play('player-death');
|
||||
|
||||
this.gameOver = true;
|
||||
|
||||
|
|
@ -273,8 +370,81 @@ export class GameScene extends Phaser.Scene {
|
|||
gameOverText.setOrigin(0.5);
|
||||
|
||||
// Restart game after delay
|
||||
// this.time.delayedCall(2000, () => {
|
||||
// this.scene.restart();
|
||||
// });
|
||||
}
|
||||
|
||||
startWave(waveNumber) {
|
||||
this.currentWave = waveNumber;
|
||||
this.sound.play('next-wave');
|
||||
|
||||
// Add wave indicator text
|
||||
const waveText = this.add.text(300, 150, `WAVE ${waveNumber + 1}`, {
|
||||
fontSize: '48px',
|
||||
fill: '#ffff00',
|
||||
fontFamily: 'Arial'
|
||||
});
|
||||
waveText.setOrigin(0.5);
|
||||
|
||||
// Add scaling animation
|
||||
this.tweens.add({
|
||||
targets: waveText,
|
||||
scale: 2, // Grow to double size
|
||||
duration: 1000,
|
||||
ease: 'Elastic.out', // Elastic easing for bouncy effect
|
||||
yoyo: true, // Scale back and forth
|
||||
repeat: 1 // Do the animation twice (total of 3 states)
|
||||
});
|
||||
|
||||
// Remove text after delay
|
||||
this.time.delayedCall(2000, () => {
|
||||
this.scene.restart();
|
||||
waveText.destroy();
|
||||
});
|
||||
|
||||
// Apply wave properties to all existing enemies
|
||||
this.enemies.children.iterate((enemy) => {
|
||||
this.applyWaveProperties(enemy);
|
||||
});
|
||||
}
|
||||
|
||||
enemyShoot(enemy, texture) {
|
||||
// Only shoot if enemy can shoot and isn't destroyed
|
||||
if (!enemy.canShoot || !enemy.active) return;
|
||||
|
||||
console.log(texture);
|
||||
// Check if this is wave 3 and enemy has texture 2 (special shooting)
|
||||
if (this.currentWave >= 2 && enemy.waveNumber >= 2 && texture >= 2) {
|
||||
// Shoot toward player
|
||||
const angle = Phaser.Math.Angle.Between(
|
||||
enemy.x,
|
||||
enemy.y,
|
||||
this.player.sprite.x,
|
||||
this.player.sprite.y
|
||||
);
|
||||
|
||||
const bullet = this.enemyBullets.create(enemy.x, enemy.y, 'enemyBullet');
|
||||
const speed = 200;
|
||||
bullet.setVelocityX(Math.cos(angle) * speed);
|
||||
bullet.setVelocityY(Math.sin(angle) * speed);
|
||||
bullet.setScale(1.5);
|
||||
} else {
|
||||
// Regular straight-down shooting
|
||||
const bullet = this.enemyBullets.create(enemy.x, enemy.y, 'enemyBullet');
|
||||
bullet.setVelocityY(300); // Move straight down at 200px/s
|
||||
bullet.setScale(1.5);
|
||||
}
|
||||
|
||||
// Sound effect for enemy shooting
|
||||
this.sound.play('enemy-shoot');
|
||||
}
|
||||
|
||||
checkEnemiesOffScreen() {
|
||||
// Destroy enemies that have gone below the screen
|
||||
this.enemies.children.iterate((enemy) => {
|
||||
if (enemy && enemy.y > 800) { // Adjust 600 to your game height as needed
|
||||
enemy.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue