Shoot Them Up - Day 5
Advanced LevelEnemies spawn in waves with increasing difficulty. Each wave completes when all enemies are destroyed.
let wave = 1;
let enemies = [];
function spawnWave() {
// More rows and columns as waves progress
const rows = Math.min(3 + Math.floor(wave / 3), 5);
const cols = Math.min(4 + Math.floor(wave / 2), 8);
const spacing = 55;
const startX = (canvas.width - cols * spacing) / 2;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
// Enemy type based on row (top = strongest)
const type = row === 0 ? 2 : (row === 1 ? 1 : 0);
enemies.push({
x: startX + col * spacing,
y: -50 - row * 50, // Start above screen
targetY: 60 + row * 50, // Move down to this Y
type: type,
health: type + 1, // HP based on type
moveDir: 1,
canShoot: type >= 1 // Only strong enemies shoot
});
}
}
}
// Check if wave is complete
if (enemies.length === 0) {
wave++;
setTimeout(spawnWave, 1000); // Brief pause between waves
}
When enemies die, spawn particles that fly outward and fade. Creates satisfying destruction feedback.
let particles = [];
function createExplosion(x, y, size) {
for (let i = 0; i < 12; i++) {
particles.push({
x: x,
y: y,
// Random velocity in all directions
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
size: Math.random() * size + 2,
life: 30, // Frames until death
color: Math.random() > 0.5 ? '#ff6633' : '#ffcc00'
});
}
}
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
// Apply velocity
p.x += p.vx;
p.y += p.vy;
// Decay
p.life--;
p.size *= 0.95; // Shrink over time
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
function drawParticles() {
for (const p of particles) {
ctx.globalAlpha = p.life / 30; // Fade out
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
Enemies have a chance to drop power-ups. Effects are temporary using setTimeout.
const powerUpTypes = ['life', 'rapid', 'shield'];
// Spawn power-up when enemy dies (10% chance)
if (Math.random() < 0.1) {
powerUps.push({
x: enemy.x,
y: enemy.y,
type: powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)]
});
}
// Apply power-up effect
function collectPowerUp(powerUp) {
switch (powerUp.type) {
case 'life':
lives++;
break;
case 'rapid':
// Faster fire rate for 5 seconds
player.fireRate = 80; // Normal is 150
setTimeout(() => {
player.fireRate = 150;
}, 5000);
break;
case 'shield':
player.shielded = true;
setTimeout(() => {
player.shielded = false;
}, 3000);
break;
}
}
Always show when a power-up is active (glow, icon, etc.) so players know their temporary ability.
Different enemy types have different behaviors, health, and point values.
function updateEnemy(enemy) {
// Move down to target Y position
if (enemy.y < enemy.targetY) {
enemy.y += 2;
} else {
// Side-to-side movement (faster in later waves)
enemy.x += enemy.moveDir * (0.5 + wave * 0.1);
// Reverse at screen edges
if (enemy.x <= 10 || enemy.x >= canvas.width - 45) {
enemy.moveDir *= -1;
}
// Shooting (stronger enemies only)
if (enemy.canShoot) {
enemy.shootTimer--;
if (enemy.shootTimer <= 0) {
enemyShoot(enemy);
// Faster shooting in later waves
enemy.shootTimer = 150 - wave * 5 + Math.random() * 100;
}
}
}
}
// Point values by type
const pointValues = [100, 200, 300]; // type 0, 1, 2
score += pointValues[enemy.type];
Efficiently manage bullets with pooling-like behavior and off-screen cleanup.
let playerBullets = [];
let enemyBullets = [];
let lastFire = 0;
function fireBullet() {
const now = Date.now();
if (now - lastFire < player.fireRate) return;
lastFire = now;
playerBullets.push({
x: player.x + player.width / 2 - 3,
y: player.y,
width: 6,
height: 15,
speed: 10
});
}
function updateBullets() {
// Update and remove off-screen bullets
for (let i = playerBullets.length - 1; i >= 0; i--) {
playerBullets[i].y -= playerBullets[i].speed;
if (playerBullets[i].y < -20) {
playerBullets.splice(i, 1);
}
}
// Same for enemy bullets (moving down)
for (let i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].y += enemyBullets[i].speed;
if (enemyBullets[i].y > canvas.height + 20) {
enemyBullets.splice(i, 1);
}
}
}
Stars at different speeds create depth illusion. Faster stars appear closer.
const stars = [];
// Initialize stars with varying speeds
for (let i = 0; i < 100; i++) {
stars.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2 + 0.5,
speed: Math.random() * 2 + 1 // Varying speeds
});
}
function updateStars() {
for (const star of stars) {
star.y += star.speed;
// Wrap around when off screen
if (star.y > canvas.height) {
star.y = 0;
star.x = Math.random() * canvas.width;
}
}
}
function drawStars() {
for (const star of stars) {
// Brighter stars are "closer" (larger, faster)
ctx.fillStyle = `rgba(255, 255, 255, ${0.5 + star.size / 4})`;
ctx.beginPath();
ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
ctx.fill();
}
}