// 粒子系统类 class ParticleSystem { constructor() { this.particles = []; this.maxParticles = 150; // 降低最大粒子数 this.gravity = 0.5; this.bounce = 0.6; this.friction = 0.98; this.lastCleanupTime = 0; } update(deltaTime) { const dt = deltaTime * 0.001; // 转换为秒 for (let i = this.particles.length - 1; i >= 0; i--) { const particle = this.particles[i]; this.updateParticle(particle, dt); // 移除生命周期结束的粒子 if (particle.life <= 0) { this.particles.splice(i, 1); } } } updateParticle(particle, dt) { // 更新位置 particle.x += particle.vx * dt; particle.y += particle.vy * dt; // 应用重力 particle.vy += this.gravity * dt * 60; // 60fps基准 // 应用摩擦力 particle.vx *= this.friction; // 边界碰撞检测 this.handleBoundaryCollision(particle); // 更新生命周期 particle.life -= dt / particle.maxLife; particle.opacity = Math.max(0, particle.life); // 更新大小(某些粒子会变化) if (particle.sizeChange) { particle.size += particle.sizeChange * dt; particle.size = Math.max(1, particle.size); } // 更新旋转 if (particle.rotation !== undefined) { particle.rotation += particle.rotationSpeed * dt; } } handleBoundaryCollision(particle) { const canvas = document.getElementById('gameCanvas'); const canvasWidth = canvas.width; const canvasHeight = canvas.height; // 底部碰撞 if (particle.y + particle.size > canvasHeight) { particle.y = canvasHeight - particle.size; particle.vy *= -this.bounce; // 减少水平速度 particle.vx *= 0.8; // 如果速度太小,停止弹跳 if (Math.abs(particle.vy) < 10) { particle.vy = 0; particle.isGrounded = true; } } // 左右边界碰撞 if (particle.x - particle.size < 0) { particle.x = particle.size; particle.vx *= -this.bounce; } else if (particle.x + particle.size > canvasWidth) { particle.x = canvasWidth - particle.size; particle.vx *= -this.bounce; } // 顶部边界(防止粒子飞出屏幕) if (particle.y - particle.size < 0) { particle.y = particle.size; particle.vy *= -this.bounce; } } createFoodParticle(x, y, foodType) { if (this.particles.length >= this.maxParticles) { // 移除最老的粒子 this.particles.shift(); } const particle = { x: x + (Math.random() - 0.5) * 20, y: y + (Math.random() - 0.5) * 10, vx: (Math.random() - 0.5) * 200, vy: -Math.random() * 150 - 50, size: Math.random() * 15 + 8, color: this.getFoodColor(foodType), emoji: this.getFoodEmoji(foodType), life: 1, maxLife: Math.random() * 3 + 2, opacity: 1, rotation: Math.random() * Math.PI * 2, rotationSpeed: (Math.random() - 0.5) * 5, type: 'food', foodType: foodType, isGrounded: false, bounceCount: 0, maxBounces: Math.floor(Math.random() * 3) + 2 }; this.particles.push(particle); return particle; } createExplosionParticles(x, y, count = 10, color = '#FFD700') { for (let i = 0; i < count; i++) { const angle = (i / count) * Math.PI * 2; const speed = Math.random() * 100 + 50; const particle = { x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, size: Math.random() * 8 + 4, color: color, life: 1, maxLife: Math.random() * 1 + 0.5, opacity: 1, type: 'explosion', sizeChange: -5 // 粒子逐渐变小 }; this.particles.push(particle); } } createSparkleParticles(x, y, count = 5) { for (let i = 0; i < count; i++) { const particle = { x: x + (Math.random() - 0.5) * 50, y: y + (Math.random() - 0.5) * 50, vx: (Math.random() - 0.5) * 30, vy: -Math.random() * 30 - 10, size: Math.random() * 4 + 2, color: Math.random() < 0.5 ? '#FFD700' : '#FF69B4', life: 1, maxLife: Math.random() * 1.5 + 0.5, opacity: 1, type: 'sparkle', twinkle: true }; this.particles.push(particle); } } render(ctx) { this.particles.forEach(particle => { this.renderParticle(ctx, particle); }); } renderParticle(ctx, particle) { ctx.save(); ctx.globalAlpha = particle.opacity; // 移动到粒子位置 ctx.translate(particle.x, particle.y); // 应用旋转 if (particle.rotation !== undefined) { ctx.rotate(particle.rotation); } switch (particle.type) { case 'food': this.renderFoodParticle(ctx, particle); break; case 'explosion': this.renderExplosionParticle(ctx, particle); break; case 'sparkle': this.renderSparkleParticle(ctx, particle); break; default: this.renderDefaultParticle(ctx, particle); break; } ctx.restore(); } renderFoodParticle(ctx, particle) { // 绘制食物表情符号 if (particle.emoji) { ctx.font = `${particle.size}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(particle.emoji, 0, 0); } else { // 备用:绘制彩色圆形 ctx.fillStyle = particle.color; ctx.beginPath(); ctx.arc(0, 0, particle.size / 2, 0, Math.PI * 2); ctx.fill(); // 添加高光 ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; ctx.beginPath(); ctx.arc(-particle.size / 6, -particle.size / 6, particle.size / 4, 0, Math.PI * 2); ctx.fill(); } } renderExplosionParticle(ctx, particle) { ctx.fillStyle = particle.color; ctx.shadowBlur = 10; ctx.shadowColor = particle.color; ctx.beginPath(); ctx.arc(0, 0, particle.size, 0, Math.PI * 2); ctx.fill(); } renderSparkleParticle(ctx, particle) { ctx.fillStyle = particle.color; ctx.shadowBlur = 8; ctx.shadowColor = particle.color; // 绘制星形 const spikes = 4; const outerRadius = particle.size; const innerRadius = particle.size * 0.4; ctx.beginPath(); for (let i = 0; i < spikes * 2; i++) { const angle = (i / (spikes * 2)) * Math.PI * 2; const radius = i % 2 === 0 ? outerRadius : innerRadius; const x = Math.cos(angle) * radius; const y = Math.sin(angle) * radius; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } } ctx.closePath(); ctx.fill(); // 闪烁效果 if (particle.twinkle) { const twinkleAlpha = Math.sin(Date.now() * 0.01) * 0.5 + 0.5; ctx.globalAlpha *= twinkleAlpha; } } renderDefaultParticle(ctx, particle) { ctx.fillStyle = particle.color || '#FFD700'; ctx.beginPath(); ctx.arc(0, 0, particle.size, 0, Math.PI * 2); ctx.fill(); } getFoodColor(foodType) { const colorMap = { 'apple': '#FF6B6B', 'banana': '#FFE66D', 'orange': '#FF8E53', 'strawberry': '#FF6B9D', 'watermelon': '#C44569', 'grape': '#9B59B6', 'pizza': '#F39C12', 'burger': '#D35400', 'cake': '#F8C471', 'cookie': '#D2691E', 'bread': '#DEB887', 'cheese': '#F1C40F', 'fish': '#3498DB', 'chicken': '#E67E22', 'rice': '#ECF0F1', 'noodles': '#F4D03F' }; return colorMap[foodType] || '#FFD700'; } getFoodEmoji(foodType) { const emojiMap = { 'apple': '🍎', 'banana': '🍌', 'orange': '🍊', 'strawberry': '🍓', 'watermelon': '🍉', 'grape': '🍇', 'pizza': '🍕', 'burger': '🍔', 'cake': '🍰', 'cookie': '🍪', 'bread': '🍞', 'cheese': '🧀', 'fish': '🐟', 'chicken': '🍗', 'rice': '🍚', 'noodles': '🍜', 'carrot': '🥕', 'tomato': '🍅', 'corn': '🌽', 'broccoli': '🥦', 'potato': '🥔', 'onion': '🧅' }; return emojiMap[foodType] || '🍽️'; } // 清理所有粒子 clear() { this.particles = []; } // 获取指定类型的粒子数量 getParticleCount(type = null) { if (type) { return this.particles.filter(p => p.type === type).length; } return this.particles.length; } // 移除指定类型的粒子 removeParticlesByType(type) { this.particles = this.particles.filter(p => p.type !== type); } // 获取地面上的食物粒子数量 getGroundedFoodCount() { return this.particles.filter(p => p.type === 'food' && p.isGrounded).length; } // 在指定区域内创建粒子爆炸效果 createAreaExplosion(x, y, radius, particleCount = 20) { for (let i = 0; i < particleCount; i++) { const angle = Math.random() * Math.PI * 2; const distance = Math.random() * radius; const particleX = x + Math.cos(angle) * distance; const particleY = y + Math.sin(angle) * distance; this.createExplosionParticles(particleX, particleY, 3); } } // 清理无效粒子 cleanupInvalidParticles() { const beforeCount = this.particles.length; // 移除生命周期结束的粒子 this.particles = this.particles.filter(particle => particle.life > 0); // 移除超出边界太远的粒子 const canvas = document.getElementById('gameCanvas'); if (canvas) { const margin = 100; // 边界外100像素 this.particles = this.particles.filter(particle => { return particle.x > -margin && particle.x < canvas.width + margin && particle.y > -margin && particle.y < canvas.height + margin; }); } const afterCount = this.particles.length; return beforeCount - afterCount; // 返回清理的粒子数量 } // 优化粒子更新 updateParticle(particle, dt) { // 更新生命周期 particle.life -= dt / particle.maxLife; if (particle.life <= 0) { return; // 粒子死亡,将在下次清理时移除 } // 更新位置 particle.x += particle.vx * dt; particle.y += particle.vy * dt; // 应用重力 particle.vy += this.gravity * dt * 60; // 60fps基准 // 应用摩擦力 particle.vx *= this.friction; // 边界碰撞检测 this.handleBoundaryCollision(particle); // 更新透明度 particle.opacity = Math.max(0, particle.life); // 更新大小(某些粒子会变化) if (particle.sizeChange) { particle.size += particle.sizeChange * dt; particle.size = Math.max(1, particle.size); } // 更新旋转 if (particle.rotation !== undefined) { particle.rotation += particle.rotationSpeed * dt; } // 更新生命时间(用于清理) particle.lifeTime = (particle.lifeTime || 0) + dt; } // 获取特定状态的粒子统计 getParticleStats() { const stats = { total: this.particles.length, food: 0, explosion: 0, sparkle: 0, grounded: 0, moving: 0 }; this.particles.forEach(particle => { if (particle.type === 'food') { stats.food++; if (particle.isGrounded) stats.grounded++; else stats.moving++; } else if (particle.type === 'explosion') { stats.explosion++; } else if (particle.type === 'sparkle') { stats.sparkle++; } }); return stats; } // 强制清理最老的粒子 forceCleanupOldest(count) { if (count <= 0) return 0; // 按生命时间排序,移除最老的 const sortedParticles = this.particles .map((particle, index) => ({ particle, index, lifeTime: particle.lifeTime || 0 })) .sort((a, b) => b.lifeTime - a.lifeTime); const toRemove = Math.min(count, sortedParticles.length); const indicesToRemove = sortedParticles .slice(0, toRemove) .map(item => item.index) .sort((a, b) => b - a); // 从后往前删除 indicesToRemove.forEach(index => { this.particles.splice(index, 1); }); return toRemove; } } // 食物粒子特殊效果类 class FoodParticleEffects { static createBurstEffect(particleSystem, x, y, foodType, count = 15) { for (let i = 0; i < count; i++) { const angle = (i / count) * Math.PI * 2; const speed = Math.random() * 80 + 40; const particle = { x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 30, size: Math.random() * 12 + 6, color: particleSystem.getFoodColor(foodType), emoji: particleSystem.getFoodEmoji(foodType), life: 1, maxLife: Math.random() * 2 + 1, opacity: 1, rotation: Math.random() * Math.PI * 2, rotationSpeed: (Math.random() - 0.5) * 8, type: 'food', foodType: foodType, isGrounded: false }; particleSystem.particles.push(particle); } } static createFountainEffect(particleSystem, x, y, foodType) { const particleCount = 8; for (let i = 0; i < particleCount; i++) { const angle = -Math.PI / 2 + (Math.random() - 0.5) * Math.PI / 3; const speed = Math.random() * 120 + 80; const particle = { x: x + (Math.random() - 0.5) * 30, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, size: Math.random() * 18 + 10, color: particleSystem.getFoodColor(foodType), emoji: particleSystem.getFoodEmoji(foodType), life: 1, maxLife: Math.random() * 4 + 2, opacity: 1, rotation: Math.random() * Math.PI * 2, rotationSpeed: (Math.random() - 0.5) * 6, type: 'food', foodType: foodType, isGrounded: false }; particleSystem.particles.push(particle); } } }