'use strict'; // === PARTICLE EFFECTS === function spawnParticles(x, y, color, count) { if (!gameState.particles) gameState.particles = []; if (gameState.particles.length > MAX_PARTICLES) { gameState.particles.splice(0, gameState.particles.length - MAX_PARTICLES + count); } for (let i = 0; i < count; i++) { const angle = Math.random() * Math.PI * 2; const speed = 1 + Math.random() * 3; gameState.particles.push({ x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 1, gravity: 0.05, color: color, size: 1 + Math.random() * 2, life: 300 + Math.random() * 400, maxLife: 700, maxAlpha: 0.8 + Math.random() * 0.2, alpha: 1, }); } } function spawnHitParticles(x, y) { spawnParticles(x, y, '#FF0000', 4); } function spawnDefeatParticles(x, y) { spawnParticles(x, y, '#FFD700', 12); spawnParticles(x, y, '#FFFFFF', 6); } function spawnExplosionParticles(x, y) { spawnParticles(x, y, '#FF4500', 15); spawnParticles(x, y, '#FFD700', 8); spawnParticles(x, y, '#FFFFFF', 4); } function renderParticles() { if (!gameState.particles) return; for (const p of gameState.particles) { ctx.save(); ctx.globalAlpha = p.alpha; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } // Draw projectiles function renderProjectiles() { if (!gameState.projectiles) return; for (const p of gameState.projectiles) { ctx.fillStyle = p.color || '#FFFF00'; ctx.beginPath(); ctx.arc(p.x * TILE + TILE / 2, p.y * TILE + TILE / 2, 4, 0, Math.PI * 2); ctx.fill(); // Trail ctx.fillStyle = (p.color || '#FFFF00') + '40'; ctx.beginPath(); ctx.arc(p.x * TILE + TILE / 2 - p.dir.x * 6, p.y * TILE + TILE / 2 - p.dir.y * 6, 3, 0, Math.PI * 2); ctx.fill(); } } if (typeof module !== 'undefined' && module.exports) { module.exports = { spawnParticles, spawnHitParticles, spawnDefeatParticles, spawnExplosionParticles, renderParticles, renderProjectiles }; }