Spaces:
Running
Running
| // Combat Animations for Flappy Forest Flight - Enhanced Edition | |
| // This file contains the animation system implementation for combat features including: | |
| // - Bird attack animations | |
| // - Enemy movement animations | |
| // - Projectile animations | |
| // - Hit and explosion effects | |
| // - Visual feedback for health changes | |
| // --- ANIMATION CONSTANTS --- | |
| const ANIMATION_FRAME_DURATION = 100; // milliseconds per frame | |
| const SPRITE_SHEET_COLUMNS = 4; // Number of columns in sprite sheets | |
| const SPRITE_SHEET_ROWS = 2; // Number of rows in sprite sheets | |
| // --- ANIMATION ASSET URLS --- | |
| const ANIMATION_ASSETS = { | |
| // Sprite sheets | |
| bird_attack_sheet: 'https://iili.io/JJvVAYu.png', | |
| bee_flying_sheet: 'https://iili.io/JJvVCdX.png', | |
| turtle_walking_sheet: 'https://iili.io/JJvVpGS.png', | |
| snake_moving_sheet: 'https://iili.io/JJvVLsp.png', | |
| // Explosion animation frames | |
| explosion_sheet: 'https://iili.io/JJvVQFI.png', | |
| hit_effect_sheet: 'https://iili.io/JJvVZkb.png', | |
| // Projectile animations | |
| stone_spinning: 'https://iili.io/JJvVnJn.png', | |
| missile_flying: 'https://iili.io/JJvVcGR.png', | |
| s400_missile_flying: 'https://iili.io/JJvVlXt.png', | |
| rocket_flying: 'https://iili.io/JJvVMSP.png', | |
| // Health change indicators | |
| health_increase: 'https://iili.io/JJvVOuF.png', | |
| health_decrease: 'https://iili.io/JJvV8Jv.png' | |
| }; | |
| // Add animation assets to main assets | |
| for (const [key, url] of Object.entries(ANIMATION_ASSETS)) { | |
| ASSETS[key] = url; | |
| } | |
| // --- ANIMATION OBJECTS --- | |
| // Animation state for bird | |
| bird.animationState = { | |
| attacking: false, | |
| attackFrame: 0, | |
| attackTotalFrames: 5, | |
| attackTimer: 0, | |
| hitFlash: false, | |
| hitFlashTimer: 0, | |
| hitFlashDuration: 200 | |
| }; | |
| // Animation states for enemies | |
| function initEnemyAnimations(enemy) { | |
| enemy.animationState = { | |
| currentFrame: 0, | |
| totalFrames: 6, | |
| frameTimer: 0, | |
| hitFlash: false, | |
| hitFlashTimer: 0, | |
| hitFlashDuration: 200 | |
| }; | |
| // Set animation properties based on enemy type | |
| switch (enemy.type) { | |
| case 'bee': | |
| enemy.animationState.totalFrames = 8; | |
| break; | |
| case 'turtle': | |
| enemy.animationState.totalFrames = 6; | |
| break; | |
| case 'snake': | |
| enemy.animationState.totalFrames = 7; | |
| break; | |
| } | |
| return enemy; | |
| } | |
| // Animation states for projectiles | |
| function initProjectileAnimation(projectile) { | |
| projectile.animationState = { | |
| currentFrame: 0, | |
| totalFrames: 4, | |
| frameTimer: 0, | |
| rotation: 0, | |
| rotationSpeed: 0.1 | |
| }; | |
| // Set animation properties based on projectile type | |
| switch (projectile.type) { | |
| case 's400_missile': | |
| projectile.animationState.rotationSpeed = 0; | |
| break; | |
| case 'rocket': | |
| projectile.animationState.rotationSpeed = 0; | |
| break; | |
| case 'stone': | |
| projectile.animationState.rotationSpeed = 0.2; | |
| break; | |
| } | |
| return projectile; | |
| } | |
| // --- ANIMATION INITIALIZATION --- | |
| function initAnimationSystem() { | |
| // Reset bird animation state | |
| bird.animationState = { | |
| attacking: false, | |
| attackFrame: 0, | |
| attackTotalFrames: 5, | |
| attackTimer: 0, | |
| hitFlash: false, | |
| hitFlashTimer: 0, | |
| hitFlashDuration: 200 | |
| }; | |
| // Initialize animations for existing enemies | |
| for (let i = 0; i < enemies.length; i++) { | |
| initEnemyAnimations(enemies[i]); | |
| } | |
| // Initialize animations for existing projectiles | |
| for (let i = 0; i < enemyProjectiles.length; i++) { | |
| initProjectileAnimation(enemyProjectiles[i]); | |
| } | |
| for (let i = 0; i < bird.stones.length; i++) { | |
| bird.stones[i].animationState = { | |
| rotation: 0, | |
| rotationSpeed: 0.2 | |
| }; | |
| } | |
| for (let i = 0; i < bird.missiles.length; i++) { | |
| bird.missiles[i].animationState = { | |
| currentFrame: 0, | |
| totalFrames: 4, | |
| frameTimer: 0 | |
| }; | |
| } | |
| } | |
| // --- ANIMATION UPDATE FUNCTIONS --- | |
| function updateBirdAnimations(deltaTime) { | |
| // Update attack animation | |
| if (bird.isAttacking) { | |
| bird.animationState.attacking = true; | |
| bird.animationState.attackTimer += deltaTime * 1000; | |
| if (bird.animationState.attackTimer >= ANIMATION_FRAME_DURATION) { | |
| bird.animationState.attackFrame++; | |
| bird.animationState.attackTimer = 0; | |
| if (bird.animationState.attackFrame >= bird.animationState.attackTotalFrames) { | |
| bird.animationState.attackFrame = 0; | |
| bird.animationState.attacking = false; | |
| } | |
| } | |
| } | |
| // Update hit flash animation | |
| if (bird.animationState.hitFlash) { | |
| bird.animationState.hitFlashTimer += deltaTime * 1000; | |
| if (bird.animationState.hitFlashTimer >= bird.animationState.hitFlashDuration) { | |
| bird.animationState.hitFlash = false; | |
| bird.animationState.hitFlashTimer = 0; | |
| } | |
| } | |
| } | |
| function updateEnemyAnimations(deltaTime) { | |
| for (let i = 0; i < enemies.length; i++) { | |
| const enemy = enemies[i]; | |
| // Update frame animation | |
| enemy.animationState.frameTimer += deltaTime * 1000; | |
| if (enemy.animationState.frameTimer >= ANIMATION_FRAME_DURATION) { | |
| enemy.animationState.currentFrame = | |
| (enemy.animationState.currentFrame + 1) % enemy.animationState.totalFrames; | |
| enemy.animationState.frameTimer = 0; | |
| } | |
| // Update hit flash animation | |
| if (enemy.animationState.hitFlash) { | |
| enemy.animationState.hitFlashTimer += deltaTime * 1000; | |
| if (enemy.animationState.hitFlashTimer >= enemy.animationState.hitFlashDuration) { | |
| enemy.animationState.hitFlash = false; | |
| enemy.animationState.hitFlashTimer = 0; | |
| } | |
| } | |
| } | |
| } | |
| function updateProjectileAnimations(deltaTime) { | |
| // Update bird stones | |
| for (let i = 0; i < bird.stones.length; i++) { | |
| const stone = bird.stones[i]; | |
| if (stone.animationState) { | |
| stone.animationState.rotation += stone.animationState.rotationSpeed * deltaTime * 60; | |
| } | |
| } | |
| // Update bird missiles | |
| for (let i = 0; i < bird.missiles.length; i++) { | |
| const missile = bird.missiles[i]; | |
| if (missile.animationState) { | |
| missile.animationState.frameTimer += deltaTime * 1000; | |
| if (missile.animationState.frameTimer >= ANIMATION_FRAME_DURATION) { | |
| missile.animationState.currentFrame = | |
| (missile.animationState.currentFrame + 1) % missile.animationState.totalFrames; | |
| missile.animationState.frameTimer = 0; | |
| } | |
| } | |
| } | |
| // Update enemy projectiles | |
| for (let i = 0; i < enemyProjectiles.length; i++) { | |
| const projectile = enemyProjectiles[i]; | |
| if (projectile.animationState) { | |
| // Update frame animation | |
| projectile.animationState.frameTimer += deltaTime * 1000; | |
| if (projectile.animationState.frameTimer >= ANIMATION_FRAME_DURATION) { | |
| projectile.animationState.currentFrame = | |
| (projectile.animationState.currentFrame + 1) % projectile.animationState.totalFrames; | |
| projectile.animationState.frameTimer = 0; | |
| } | |
| // Update rotation | |
| projectile.animationState.rotation += | |
| projectile.animationState.rotationSpeed * deltaTime * 60; | |
| } | |
| } | |
| } | |
| function updateParticleAnimations(deltaTime) { | |
| for (let i = particles.length - 1; i >= 0; i--) { | |
| const particle = particles[i]; | |
| if (particle.type === 'hit' || particle.type === 'explosion') { | |
| particle.frameTimer = (particle.frameTimer || 0) + deltaTime * 1000; | |
| if (particle.frameTimer >= ANIMATION_FRAME_DURATION) { | |
| particle.frame++; | |
| particle.frameTimer = 0; | |
| if (particle.frame >= particle.maxFrames) { | |
| particles.splice(i, 1); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // --- DRAWING FUNCTIONS WITH ANIMATIONS --- | |
| function drawBirdWithAnimations() { | |
| // Determine which bird image to use based on evolution stage | |
| let birdImageKey = BIRD_COLORS[bird.color]; | |
| if (bird.evolution > 0) { | |
| birdImageKey = `bird_evolved_${bird.evolution}`; | |
| } | |
| const birdImage = assets.images[birdImageKey] || assets.images[BIRD_COLORS[bird.color]]; | |
| if (!birdImage) return; | |
| ctx.save(); | |
| // Apply hit flash effect | |
| if (bird.animationState.hitFlash) { | |
| ctx.globalAlpha = 0.7; | |
| ctx.shadowColor = '#FF0000'; | |
| ctx.shadowBlur = 20; | |
| } else if (bird.invincible) { | |
| ctx.globalAlpha = 0.5 + Math.sin(Date.now() * 0.01) * 0.5; | |
| } | |
| // Apply power-up glow effect | |
| if (bird.isPowerUpActive) { | |
| ctx.shadowColor = 'gold'; | |
| ctx.shadowBlur = 20; | |
| } else if (doublePointsActive) { | |
| ctx.shadowColor = '#FF9800'; | |
| ctx.shadowBlur = 15; | |
| } else if (turboBoostActive) { | |
| ctx.shadowColor = '#2196F3'; | |
| ctx.shadowBlur = 15; | |
| } | |
| ctx.translate(bird.x, bird.y); | |
| ctx.rotate(bird.rotation); | |
| // Draw the bird | |
| ctx.drawImage( | |
| birdImage, | |
| -bird.width / 2, | |
| -bird.height / 2, | |
| bird.width, | |
| bird.height | |
| ); | |
| // Draw attack animation overlay if attacking | |
| if (bird.animationState.attacking) { | |
| const attackImage = assets.images.bird_attack_sheet; | |
| if (attackImage) { | |
| const frameWidth = attackImage.width / bird.animationState.attackTotalFrames; | |
| const frameHeight = attackImage.height; | |
| ctx.drawImage( | |
| attackImage, | |
| bird.animationState.attackFrame * frameWidth, | |
| 0, | |
| frameWidth, | |
| frameHeight, | |
| -bird.width / 2 - 10, | |
| -bird.height / 2 - 10, | |
| bird.width + 20, | |
| bird.height + 20 | |
| ); | |
| } | |
| } | |
| ctx.restore(); | |
| } | |
| function drawEnemiesWithAnimations() { | |
| for (const enemy of enemies) { | |
| const enemySheetImage = assets.images[`${enemy.type}_moving_sheet`]; | |
| if (!enemySheetImage) continue; | |
| ctx.save(); | |
| ctx.translate(enemy.x, enemy.y); | |
| ctx.rotate(enemy.rotation || 0); | |
| // Apply hit flash effect | |
| if (enemy.animationState.hitFlash) { | |
| ctx.globalAlpha = 0.7; | |
| ctx.shadowColor = '#FF0000'; | |
| ctx.shadowBlur = 20; | |
| } | |
| // Calculate frame position in sprite sheet | |
| const frameWidth = enemySheetImage.width / enemy.animationState.totalFrames; | |
| const frameHeight = enemySheetImage.height; | |
| // Draw enemy with animation frame | |
| ctx.drawImage( | |
| enemySheetImage, | |
| enemy.animationState.currentFrame * frameWidth, | |
| 0, | |
| frameWidth, | |
| frameHeight, | |
| -enemy.width / 2, | |
| -enemy.height / 2, | |
| enemy.width, | |
| enemy.height | |
| ); | |
| // Draw health bar above enemy | |
| const healthBarWidth = enemy.width; | |
| const healthBarHeight = 5; | |
| const healthPercent = getEnemyHealthPercent(enemy); | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; | |
| ctx.fillRect( | |
| -healthBarWidth / 2, | |
| -enemy.height / 2 - 10, | |
| healthBarWidth, | |
| healthBarHeight | |
| ); | |
| ctx.fillStyle = getHealthColor(healthPercent); | |
| ctx.fillRect( | |
| -healthBarWidth / 2, | |
| -enemy.height / 2 - 10, | |
| healthBarWidth * healthPercent, | |
| healthBarHeight | |
| ); | |
| ctx.restore(); | |
| } | |
| } | |
| function drawProjectilesWithAnimations() { | |
| // Draw bird stones with rotation | |
| for (const stone of bird.stones) { | |
| const stoneImage = assets.images.stone_spinning; | |
| if (!stoneImage) continue; | |
| ctx.save(); | |
| ctx.translate(stone.x, stone.y); | |
| if (stone.animationState) { | |
| ctx.rotate(stone.animationState.rotation); | |
| } else { | |
| ctx.rotate(Date.now() * 0.01); // Fallback rotation | |
| } | |
| ctx.drawImage( | |
| stoneImage, | |
| -stone.size / 2, | |
| -stone.size / 2, | |
| stone.size, | |
| stone.size | |
| ); | |
| ctx.restore(); | |
| } | |
| // Draw bird missiles with animation frames | |
| for (const missile of bird.missiles) { | |
| const missileImage = assets.images.missile_flying; | |
| if (!missileImage) continue; | |
| ctx.save(); | |
| ctx.translate(missile.x, missile.y); | |
| // Draw missile with animation frame if available | |
| if (missile.animationState) { | |
| const frameWidth = missileImage.width / 4; // Assuming 4 frames | |
| const frameHeight = missileImage.height; | |
| ctx.drawImage( | |
| missileImage, | |
| missile.animationState.currentFrame * frameWidth, | |
| 0, | |
| frameWidth, | |
| frameHeight, | |
| -missile.size / 2, | |
| -missile.size / 2, | |
| missile.size, | |
| missile.size | |
| ); | |
| } else { | |
| // Fallback to static image | |
| ctx.drawImage( | |
| missileImage, | |
| -missile.size / 2, | |
| -missile.size / 2, | |
| missile.size, | |
| missile.size | |
| ); | |
| } | |
| // Draw missile trail | |
| ctx.globalAlpha = 0.7; | |
| ctx.fillStyle = '#FF9800'; | |
| for (let i = 0; i < 5; i++) { | |
| const trailSize = missile.size * 0.7 * (1 - i * 0.15); | |
| const trailDist = i * 5; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| -trailDist, | |
| 0, | |
| trailSize / 2, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| // Draw enemy projectiles with animations | |
| for (const projectile of enemyProjectiles) { | |
| let projectileImageKey; | |
| switch (projectile.type) { | |
| case 's400_missile': | |
| projectileImageKey = 's400_missile_flying'; | |
| break; | |
| case 'rocket': | |
| projectileImageKey = 'rocket_flying'; | |
| break; | |
| case 'stone': | |
| projectileImageKey = 'stone_spinning'; | |
| break; | |
| default: | |
| projectileImageKey = projectile.type; | |
| } | |
| const projectileImage = assets.images[projectileImageKey]; | |
| if (!projectileImage) continue; | |
| ctx.save(); | |
| ctx.translate(projectile.x, projectile.y); | |
| if (projectile.rotation) { | |
| ctx.rotate(projectile.rotation); | |
| } else if (projectile.animationState && projectile.animationState.rotation) { | |
| ctx.rotate(projectile.animationState.rotation); | |
| } | |
| // Draw projectile with animation frame if available | |
| if (projectile.animationState && projectile.type !== 'stone') { | |
| const frameWidth = projectileImage.width / 4; // Assuming 4 frames | |
| const frameHeight = projectileImage.height; | |
| ctx.drawImage( | |
| projectileImage, | |
| projectile.animationState.currentFrame * frameWidth, | |
| 0, | |
| frameWidth, | |
| frameHeight, | |
| -projectile.size / 2, | |
| -projectile.size / 2, | |
| projectile.size, | |
| projectile.size | |
| ); | |
| } else { | |
| // Fallback to static image or rotating stone | |
| ctx.drawImage( | |
| projectileImage, | |
| -projectile.size / 2, | |
| -projectile.size / 2, | |
| projectile.size, | |
| projectile.size | |
| ); | |
| } | |
| // Draw trail for missiles and rockets | |
| if (projectile.type === 's400_missile' || projectile.type === 'rocket') { | |
| ctx.globalAlpha = 0.7; | |
| ctx.fillStyle = projectile.type === 's400_missile' ? '#F44336' : '#2196F3'; | |
| for (let i = 0; i < 5; i++) { | |
| const trailSize = projectile.size * 0.7 * (1 - i * 0.15); | |
| const trailDist = i * 5; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| -trailDist, | |
| 0, | |
| trailSize / 2, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fill(); | |
| } | |
| } | |
| ctx.restore(); | |
| } | |
| } | |
| function drawParticleEffects() { | |
| for (const particle of particles) { | |
| // Draw standard particles | |
| if (!particle.type) { | |
| ctx.globalAlpha = particle.alpha; | |
| ctx.fillStyle = 'white'; | |
| ctx.beginPath(); | |
| ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.globalAlpha = 1; | |
| continue; | |
| } | |
| // Draw effect particles with animation frames | |
| if (particle.type === 'hit') { | |
| const hitImage = assets.images.hit_effect_sheet; | |
| if (!hitImage) continue; | |
| const frameWidth = hitImage.width / particle.maxFrames; | |
| const frameHeight = hitImage.height; | |
| ctx.globalAlpha = 1 - (particle.frame / particle.maxFrames); | |
| ctx.drawImage( | |
| hitImage, | |
| Math.floor(particle.frame) * frameWidth, | |
| 0, | |
| frameWidth, | |
| frameHeight, | |
| particle.x - particle.size / 2, | |
| particle.y - particle.size / 2, | |
| particle.size, | |
| particle.size | |
| ); | |
| ctx.globalAlpha = 1; | |
| } else if (particle.type === 'explosion') { | |
| const explosionImage = assets.images.explosion_sheet; | |
| if (!explosionImage) continue; | |
| const frameWidth = explosionImage.width / particle.maxFrames; | |
| const frameHeight = explosionImage.height; | |
| ctx.globalAlpha = 1 - (particle.frame / particle.maxFrames) * 0.5; | |
| ctx.drawImage( | |
| explosionImage, | |
| Math.floor(particle.frame) * frameWidth, | |
| 0, | |
| frameWidth, | |
| frameHeight, | |
| particle.x - particle.size / 2, | |
| particle.y - particle.size / 2, | |
| particle.size, | |
| particle.size | |
| ); | |
| ctx.globalAlpha = 1; | |
| } | |
| } | |
| } | |
| // --- EFFECT CREATION FUNCTIONS --- | |
| function createHitEffectAnimated(x, y, size = 30) { | |
| particles.push({ | |
| x: x, | |
| y: y, | |
| type: 'hit', | |
| frame: 0, | |
| frameTimer: 0, | |
| maxFrames: 5, | |
| size: size, | |
| alpha: 1 | |
| }); | |
| } | |
| function createExplosionEffectAnimated(x, y, size = 60) { | |
| particles.push({ | |
| x: x, | |
| y: y, | |
| type: 'explosion', | |
| frame: 0, | |
| frameTimer: 0, | |
| maxFrames: 8, | |
| size: size, | |
| alpha: 1 | |
| }); | |
| // Add smaller particle effects around explosion | |
| for (let i = 0; i < 10; i++) { | |
| particles.push({ | |
| x: x + (Math.random() - 0.5) * size * 0.8, | |
| y: y + (Math.random() - 0.5) * size * 0.8, | |
| vx: (Math.random() - 0.5) * 5, | |
| vy: (Math.random() - 0.5) * 5, | |
| size: Math.random() * 5 + 2, | |
| alpha: 1 | |
| }); | |
| } | |
| } | |
| function createHealthChangeEffect(x, y, isIncrease) { | |
| const effectImage = isIncrease ? 'health_increase' : 'health_decrease'; | |
| particles.push({ | |
| x: x, | |
| y: y, | |
| type: 'health_change', | |
| image: effectImage, | |
| frame: 0, | |
| frameTimer: 0, | |
| maxFrames: 6, | |
| size: 40, | |
| alpha: 1, | |
| vy: -1 // Float upward | |
| }); | |
| } | |
| // --- EVENT HANDLERS --- | |
| function onEnemyHit(enemy) { | |
| // Set hit flash animation | |
| enemy.animationState.hitFlash = true; | |
| enemy.animationState.hitFlashTimer = 0; | |
| // Create hit effect at enemy position | |
| createHitEffectAnimated(enemy.x, enemy.y); | |
| } | |
| function onBirdHit() { | |
| // Set hit flash animation | |
| bird.animationState.hitFlash = true; | |
| bird.animationState.hitFlashTimer = 0; | |
| // Create hit effect at bird position | |
| createHitEffectAnimated(bird.x, bird.y); | |
| // Create health decrease effect | |
| createHealthChangeEffect(bird.x, bird.y - 30, false); | |
| } | |
| function onEnemyDefeat(enemy) { | |
| // Create explosion effect | |
| createExplosionEffectAnimated(enemy.x, enemy.y, 70); | |
| // Create score particles | |
| for (let i = 0; i < 15; i++) { | |
| particles.push({ | |
| x: enemy.x + (Math.random() - 0.5) * 30, | |
| y: enemy.y + (Math.random() - 0.5) * 30, | |
| vx: (Math.random() - 0.5) * 3, | |
| vy: -Math.random() * 2 - 1, // Float upward | |
| size: Math.random() * 4 + 2, | |
| alpha: 1, | |
| color: '#FFD700' // Gold color for score particles | |
| }); | |
| } | |
| } | |
| // --- INTEGRATION WITH COMBAT SYSTEM --- | |
| // These functions need to be modified in the combat system | |
| // Modify checkBirdProjectileCollisions to include animation effects | |
| function enhancedCheckBirdProjectileCollisions() { | |
| // Check stone collisions | |
| for (let i = bird.stones.length - 1; i >= 0; i--) { | |
| const stone = bird.stones[i]; | |
| for (let j = enemies.length - 1; j >= 0; j--) { | |
| const enemy = enemies[j]; | |
| // Calculate distance | |
| const dx = stone.x - enemy.x; | |
| const dy = stone.y - enemy.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| // Check collision | |
| if (distance < stone.size + enemy.width / 2) { | |
| // Apply damage | |
| enemy.health -= stone.damage; | |
| // Trigger hit animation | |
| onEnemyHit(enemy); | |
| // Play hit sound | |
| playSound('soundEnemyHit', 0.4); | |
| // Remove stone | |
| bird.stones.splice(i, 1); | |
| // Check if enemy is defeated | |
| if (enemy.health <= 0) { | |
| onEnemyDefeat(enemy); | |
| defeatEnemy(enemy, j); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| // Check missile collisions (similar modifications) | |
| // ... | |
| } | |
| // Modify checkEnemyProjectileCollisions to include animation effects | |
| function enhancedCheckEnemyProjectileCollisions() { | |
| for (let i = enemyProjectiles.length - 1; i >= 0; i--) { | |
| const projectile = enemyProjectiles[i]; | |
| // Calculate distance | |
| const dx = projectile.x - bird.x; | |
| const dy = projectile.y - bird.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| // Check collision | |
| if (distance < projectile.size + bird.width / 3) { | |
| // Only apply damage if bird is not invincible | |
| if (!bird.invincible) { | |
| // Apply damage | |
| bird.health -= projectile.damage; | |
| // Trigger hit animation | |
| onBirdHit(); | |
| // Make bird temporarily invincible | |
| bird.invincible = true; | |
| setTimeout(() => { | |
| bird.invincible = false; | |
| }, INVINCIBILITY_DURATION); | |
| // Play hit sound | |
| playSound('soundBirdHit', 0.4); | |
| // Reset combo | |
| updateCombo(false); | |
| // Check game over | |
| if (bird.health <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| // Create effect based on projectile type | |
| if (projectile.type === 's400_missile' || projectile.type === 'rocket') { | |
| createExplosionEffectAnimated(projectile.x, projectile.y); | |
| playSound('soundExplosion', 0.5); | |
| } else { | |
| createHitEffectAnimated(projectile.x, projectile.y); | |
| } | |
| // Remove projectile | |
| enemyProjectiles.splice(i, 1); | |
| } | |
| } | |
| } | |
| // --- ANIMATION INTEGRATION WITH MAIN GAME LOOP --- | |
| // These functions need to be called from the main game loop | |
| // Call in game initialization | |
| // initAnimationSystem(); | |
| // Call in main update function | |
| // updateBirdAnimations(deltaTime); | |
| // updateEnemyAnimations(deltaTime); | |
| // updateProjectileAnimations(deltaTime); | |
| // updateParticleAnimations(deltaTime); | |
| // Call in main draw function instead of regular drawing functions | |
| // drawBirdWithAnimations(); | |
| // drawEnemiesWithAnimations(); | |
| // drawProjectilesWithAnimations(); | |
| // drawParticleEffects(); | |