flappy-bird-game / combat_animations.js
offerpk3's picture
Create combat_animations.js
dab69cc verified
// 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();