| |
| class AnimalSystem { |
| constructor(canvas) { |
| this.animals = []; |
| this.maxAnimals = 8; |
| this.canvas = canvas || document.getElementById('gameCanvas'); |
| } |
| |
| update(deltaTime) { |
| const dt = deltaTime * 0.001; |
| |
| for (let i = this.animals.length - 1; i >= 0; i--) { |
| const animal = this.animals[i]; |
| this.updateAnimal(animal, dt); |
| |
| |
| if (animal.shouldRemove) { |
| this.animals.splice(i, 1); |
| } |
| } |
| } |
| |
| updateAnimal(animal, dt) { |
| switch (animal.state) { |
| case 'entering': |
| this.updateEntering(animal, dt); |
| break; |
| case 'seeking': |
| this.updateSeeking(animal, dt); |
| break; |
| case 'eating': |
| this.updateEating(animal, dt); |
| break; |
| case 'satisfied': |
| this.updateSatisfied(animal, dt); |
| break; |
| case 'leaving': |
| this.updateLeaving(animal, dt); |
| break; |
| } |
| |
| |
| animal.animationTime += dt; |
| |
| |
| animal.x += animal.vx * dt; |
| animal.y += animal.vy * dt; |
| |
| |
| animal.lifeTime += dt; |
| if (animal.maxLifeTime && animal.lifeTime > animal.maxLifeTime) { |
| animal.shouldRemove = true; |
| } |
| } |
| |
| updateEntering(animal, dt) { |
| |
| const rect = this.canvas ? this.canvas.getBoundingClientRect() : { width: 800, height: 600 }; |
| const displayWidth = rect.width; |
| const displayHeight = rect.height; |
| |
| const targetX = animal.side === 'left' ? 100 : displayWidth - 100; |
| const targetY = displayHeight - 100; |
| |
| console.log(`Canvas显示尺寸: ${displayWidth}x${displayHeight}, 目标位置: (${targetX}, ${targetY})`); |
| |
| const dx = targetX - animal.x; |
| const dy = targetY - animal.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| console.log(`动物${animal.type}进入中: 当前位置(${animal.x.toFixed(1)}, ${animal.y.toFixed(1)}), 目标位置(${targetX}, ${targetY}), 距离: ${distance.toFixed(1)}`); |
| |
| if (distance < 20) { |
| animal.state = 'seeking'; |
| animal.vx = 0; |
| animal.vy = 0; |
| console.log(`动物${animal.type}已进入屏幕,状态改为seeking`); |
| } else { |
| animal.vx = (dx / distance) * animal.speed; |
| animal.vy = (dy / distance) * animal.speed; |
| } |
| } |
| |
| updateSeeking(animal, dt) { |
| |
| const food = this.findNearestFood(animal); |
| |
| if (food) { |
| console.log(`${animal.type}找到食物: ${food.foodType}, 距离: ${Math.sqrt((food.x - animal.x)**2 + (food.y - animal.y)**2).toFixed(1)}`); |
| const dx = food.x - animal.x; |
| const dy = food.y - animal.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| if (distance < 30) { |
| |
| animal.state = 'eating'; |
| animal.targetFood = food; |
| animal.eatingTime = 0; |
| animal.vx = 0; |
| animal.vy = 0; |
| console.log(`${animal.type}开始吃食物!`); |
| } else { |
| |
| animal.vx = (dx / distance) * animal.speed * 0.8; |
| animal.vy = (dy / distance) * animal.speed * 0.8; |
| } |
| } else { |
| |
| if (Math.random() < 0.1) { |
| console.log(`${animal.type}没有找到食物,喜欢: ${animal.preferredFood}, 随机移动`); |
| animal.vx = (Math.random() - 0.5) * animal.speed * 0.5; |
| animal.vy = (Math.random() - 0.5) * animal.speed * 0.5; |
| } |
| } |
| |
| |
| if (animal.lifeTime > 10) { |
| animal.state = 'leaving'; |
| } |
| } |
| |
| updateEating(animal, dt) { |
| animal.eatingTime += dt; |
| |
| |
| if (animal.eatingTime > 0.5 && animal.targetFood) { |
| |
| this.removeFoodParticle(animal.targetFood); |
| animal.targetFood = null; |
| animal.foodEaten++; |
| } |
| |
| if (animal.eatingTime > 1) { |
| if (animal.foodEaten >= animal.maxFood) { |
| animal.state = 'satisfied'; |
| } else { |
| animal.state = 'seeking'; |
| } |
| } |
| } |
| |
| updateSatisfied(animal, dt) { |
| |
| animal.satisfiedTime = (animal.satisfiedTime || 0) + dt; |
| |
| if (animal.satisfiedTime > 2) { |
| animal.state = 'leaving'; |
| } |
| } |
| |
| updateLeaving(animal, dt) { |
| |
| const exitX = animal.side === 'left' ? -50 : this.canvas.width + 50; |
| const dx = exitX - animal.x; |
| const distance = Math.abs(dx); |
| |
| if (distance < 10) { |
| animal.shouldRemove = true; |
| } else { |
| animal.vx = (dx / distance) * animal.speed * 1.2; |
| animal.vy = 0; |
| } |
| } |
| |
| findNearestFood(animal) { |
| |
| if (!window.game || !window.game.particleSystem) return null; |
| |
| const particles = window.game.particleSystem.particles; |
| let nearestFood = null; |
| let nearestDistance = Infinity; |
| let totalFood = 0; |
| let groundedFood = 0; |
| let matchingFood = 0; |
| |
| particles.forEach(particle => { |
| if (particle.type === 'food') { |
| totalFood++; |
| if (particle.isGrounded) { |
| groundedFood++; |
| if (particle.foodType === animal.preferredFood) { |
| matchingFood++; |
| const dx = particle.x - animal.x; |
| const dy = particle.y - animal.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| |
| if (distance < nearestDistance) { |
| nearestDistance = distance; |
| nearestFood = particle; |
| } |
| } |
| } |
| } |
| }); |
| |
| |
| if (Math.random() < 0.01) { |
| console.log(`${animal.type}寻找食物: 总食物=${totalFood}, 落地食物=${groundedFood}, 匹配食物=${matchingFood}, 喜欢=${animal.preferredFood}`); |
| if (totalFood > 0) { |
| console.log(`示例食物类型: ${particles.find(p => p.type === 'food')?.foodType}`); |
| } |
| } |
| |
| return nearestFood; |
| } |
| |
| removeFoodParticle(foodParticle) { |
| if (!window.game || !window.game.particleSystem) return; |
| |
| const particles = window.game.particleSystem.particles; |
| const index = particles.indexOf(foodParticle); |
| if (index > -1) { |
| particles.splice(index, 1); |
| |
| |
| window.game.particleSystem.createSparkleParticles( |
| foodParticle.x, |
| foodParticle.y, |
| 5 |
| ); |
| } |
| } |
| |
| createAnimal(foodType, side = 'left') { |
| if (this.animals.length >= this.maxAnimals) { |
| console.log(`已达到最大动物数量: ${this.maxAnimals}`); |
| return null; |
| } |
| |
| const animalType = this.getAnimalForFood(foodType); |
| |
| const rect = this.canvas ? this.canvas.getBoundingClientRect() : { width: 800, height: 600 }; |
| const displayWidth = rect.width; |
| const displayHeight = rect.height; |
| const startX = side === 'left' ? -50 : displayWidth + 50; |
| const startY = displayHeight - 60; |
| |
| console.log(`Canvas显示尺寸: ${displayWidth}x${displayHeight}, 动物起始位置: (${startX}, ${startY})`); |
| |
| const animal = { |
| x: startX, |
| y: startY, |
| vx: 0, |
| vy: 0, |
| type: animalType, |
| side: side, |
| state: 'entering', |
| speed: Math.random() * 30 + 40, |
| size: Math.random() * 20 + 30, |
| preferredFood: foodType, |
| foodEaten: 0, |
| maxFood: Math.floor(Math.random() * 3) + 2, |
| animationTime: 0, |
| lifeTime: 0, |
| maxLifeTime: 30, |
| shouldRemove: false, |
| color: this.getAnimalColor(animalType), |
| emoji: this.getAnimalEmoji(animalType) |
| }; |
| |
| this.animals.push(animal); |
| console.log(`动物已创建: ${animalType}, 位置: (${animal.x}, ${animal.y}), 当前动物总数: ${this.animals.length}`); |
| console.log(`动物详细信息:`, animal); |
| return animal; |
| } |
| |
| createLittleGirl(side = 'right') { |
| |
| const rect = this.canvas ? this.canvas.getBoundingClientRect() : { width: 800, height: 600 }; |
| const displayWidth = rect.width; |
| const displayHeight = rect.height; |
| const startX = side === 'left' ? -50 : displayWidth + 50; |
| const startY = displayHeight - 80; |
| |
| console.log(`小女孩Canvas显示尺寸: ${displayWidth}x${displayHeight}, 起始位置: (${startX}, ${startY})`); |
| |
| const girl = { |
| x: startX, |
| y: startY, |
| vx: 0, |
| vy: 0, |
| type: 'girl', |
| side: side, |
| state: 'entering', |
| speed: 60, |
| size: 40, |
| preferredFood: ['strawberry', 'watermelon'], |
| foodEaten: 0, |
| maxFood: 5, |
| animationTime: 0, |
| lifeTime: 0, |
| maxLifeTime: 45, |
| shouldRemove: false, |
| color: '#FFB6C1', |
| emoji: '👧', |
| isSpecial: true |
| }; |
| |
| this.animals.push(girl); |
| console.log(`小女孩已创建: 位置: (${girl.x}, ${girl.y}), 当前动物总数: ${this.animals.length}`); |
| return girl; |
| } |
| |
| getAnimalForFood(foodType) { |
| const animalMap = { |
| 'apple': 'rabbit', |
| 'banana': 'monkey', |
| 'orange': 'bird', |
| 'strawberry': 'bear', |
| 'watermelon': 'elephant', |
| 'grape': 'fox', |
| 'pizza': 'cat', |
| 'burger': 'dog', |
| 'cake': 'mouse', |
| 'cookie': 'squirrel', |
| 'bread': 'duck', |
| 'cheese': 'rat', |
| 'fish': 'cat', |
| 'chicken': 'dog', |
| 'rice': 'bird', |
| 'noodles': 'panda', |
| 'carrot': 'rabbit', |
| 'corn': 'chicken' |
| }; |
| |
| return animalMap[foodType] || 'cat'; |
| } |
| |
| getAnimalColor(animalType) { |
| const colorMap = { |
| 'rabbit': '#F5DEB3', |
| 'monkey': '#DEB887', |
| 'bird': '#87CEEB', |
| 'bear': '#8B4513', |
| 'elephant': '#A9A9A9', |
| 'fox': '#FF8C00', |
| 'cat': '#FFB6C1', |
| 'dog': '#DEB887', |
| 'mouse': '#C0C0C0', |
| 'squirrel': '#D2691E', |
| 'duck': '#FFFF00', |
| 'rat': '#808080', |
| 'panda': '#000000', |
| 'chicken': '#FFFF00' |
| }; |
| |
| return colorMap[animalType] || '#FFB6C1'; |
| } |
| |
| getAnimalEmoji(animalType) { |
| const emojiMap = { |
| 'rabbit': '🐰', |
| 'monkey': '🐵', |
| 'bird': '🐦', |
| 'bear': '🐻', |
| 'elephant': '🐘', |
| 'fox': '🦊', |
| 'cat': '🐱', |
| 'dog': '🐶', |
| 'mouse': '🐭', |
| 'squirrel': '🐿️', |
| 'duck': '🦆', |
| 'rat': '🐀', |
| 'panda': '🐼', |
| 'chicken': '🐔' |
| }; |
| |
| return emojiMap[animalType] || '🐱'; |
| } |
| |
| render(ctx) { |
| if (this.animals.length > 0) { |
| console.log(`渲染${this.animals.length}个动物`); |
| } |
| this.animals.forEach(animal => { |
| this.renderAnimal(ctx, animal); |
| }); |
| } |
| |
| renderAnimal(ctx, animal) { |
| ctx.save(); |
| |
| |
| if (!animal.debugRendered) { |
| console.log(`正在渲染动物: ${animal.type}, 位置: (${animal.x}, ${animal.y}), emoji: ${animal.emoji}, 尺寸: ${animal.size}`); |
| animal.debugRendered = true; |
| } |
| |
| |
| ctx.translate(animal.x, animal.y); |
| |
| |
| if (animal.vx < 0) { |
| ctx.scale(-1, 1); |
| } |
| |
| |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; |
| ctx.beginPath(); |
| ctx.ellipse(0, animal.size * 0.4, animal.size * 0.6, animal.size * 0.2, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| if (animal.emoji) { |
| ctx.font = `${animal.size}px Arial`; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillStyle = '#000000'; |
| ctx.fillText(animal.emoji, 0, -animal.size * 0.2); |
| } else { |
| |
| this.renderSimpleAnimal(ctx, animal); |
| } |
| |
| |
| ctx.font = '12px Arial'; |
| ctx.fillStyle = '#FF0000'; |
| ctx.fillText(animal.type, 0, animal.size * 0.6); |
| |
| |
| this.renderAnimalState(ctx, animal); |
| |
| |
| this.renderAnimalEffects(ctx, animal); |
| |
| ctx.restore(); |
| } |
| |
| renderSimpleAnimal(ctx, animal) { |
| |
| ctx.fillStyle = animal.color; |
| ctx.beginPath(); |
| ctx.ellipse(0, 0, animal.size * 0.4, animal.size * 0.3, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.beginPath(); |
| ctx.ellipse(0, -animal.size * 0.3, animal.size * 0.3, animal.size * 0.25, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#000000'; |
| ctx.beginPath(); |
| ctx.arc(-animal.size * 0.1, -animal.size * 0.35, 2, 0, Math.PI * 2); |
| ctx.arc(animal.size * 0.1, -animal.size * 0.35, 2, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| |
| renderAnimalState(ctx, animal) { |
| |
| switch (animal.state) { |
| case 'eating': |
| |
| ctx.fillStyle = '#FF69B4'; |
| ctx.font = '16px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('💖', 0, -animal.size * 0.8); |
| break; |
| case 'satisfied': |
| |
| ctx.fillStyle = '#32CD32'; |
| ctx.font = '16px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText('😊', 0, -animal.size * 0.8); |
| break; |
| } |
| } |
| |
| renderAnimalEffects(ctx, animal) { |
| |
| if (animal.isSpecial) { |
| |
| const sparkleCount = 3; |
| const time = animal.animationTime * 2; |
| |
| ctx.fillStyle = '#FFD700'; |
| for (let i = 0; i < sparkleCount; i++) { |
| const angle = (i / sparkleCount) * Math.PI * 2 + time; |
| const radius = animal.size * 0.8; |
| const x = Math.cos(angle) * radius; |
| const y = Math.sin(angle) * radius; |
| |
| ctx.beginPath(); |
| ctx.arc(x, y, 3, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| } |
| |
| |
| if (Math.abs(animal.vx) > 10 && animal.state !== 'eating') { |
| ctx.fillStyle = 'rgba(139, 69, 19, 0.3)'; |
| for (let i = 0; i < 3; i++) { |
| const dustX = -animal.vx * 0.1 * (i + 1) + (Math.random() - 0.5) * 10; |
| const dustY = animal.size * 0.3 + Math.random() * 5; |
| |
| ctx.beginPath(); |
| ctx.arc(dustX, dustY, Math.random() * 3 + 1, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| } |
| } |
| |
| |
| clear() { |
| this.animals = []; |
| } |
| |
| |
| getAnimalCount() { |
| return this.animals.length; |
| } |
| |
| |
| getAnimalCountByType(type) { |
| return this.animals.filter(animal => animal.type === type).length; |
| } |
| |
| |
| removeAnimal(animal) { |
| const index = this.animals.indexOf(animal); |
| if (index > -1) { |
| this.animals.splice(index, 1); |
| } |
| } |
| |
| |
| makeAllAnimalsLeave() { |
| this.animals.forEach(animal => { |
| if (animal.state !== 'leaving') { |
| animal.state = 'leaving'; |
| } |
| }); |
| } |
| } |
|
|