From a84d309809051e8165e6775b9aa2ac1bd5478731 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Fri, 13 Feb 2026 09:53:58 +0000 Subject: [PATCH] Enhance player combat animations and effects with punch mechanics --- public/break_escape/js/core/player.js | 50 +++++-- .../break_escape/js/systems/player-combat.js | 114 +++++++++++++--- .../break_escape/js/systems/player-effects.js | 126 +++++++----------- 3 files changed, 187 insertions(+), 103 deletions(-) diff --git a/public/break_escape/js/core/player.js b/public/break_escape/js/core/player.js index 9facac1..68f26b5 100644 --- a/public/break_escape/js/core/player.js +++ b/public/break_escape/js/core/player.js @@ -374,6 +374,7 @@ function createAtlasPlayerAnimations(spriteSheet) { const playerConfig = window.gameScenario?.player?.spriteConfig || {}; const idleFrameRate = playerConfig.idleFrameRate || 6; // Slower for breathing effect const walkFrameRate = playerConfig.walkFrameRate || 10; + const punchFrameRate = playerConfig.punchFrameRate || 12; // Faster for action animations // Direction mapping: atlas directions → player directions const directionMap = { @@ -392,6 +393,14 @@ function createAtlasPlayerAnimations(spriteSheet) { 'breathing-idle': 'idle', 'walk': 'walk' }; + + // Animation type framework (for grouping and frame rate) + const animationFramework = { + 'idle': { frameRate: idleFrameRate, repeat: -1, name: 'idle' }, + 'walk': { frameRate: walkFrameRate, repeat: -1, name: 'walk' }, + 'cross-punch': { frameRate: punchFrameRate, repeat: 0, name: 'attack' }, + 'lead-jab': { frameRate: punchFrameRate, repeat: 0, name: 'attack' } + }; // Create animations from atlas metadata for (const [atlasAnimKey, frames] of Object.entries(animations)) { @@ -404,8 +413,15 @@ function createAtlasPlayerAnimations(spriteSheet) { const playerDirection = directionMap[atlasDirection] || atlasDirection; const playerType = animTypeMap[atlasType] || atlasType; - // Create animation key: "walk-right", "idle-down", etc. - const animKey = `${playerType}-${playerDirection}`; + // Create animation key: "walk-right", "idle-down", "cross-punch_east", "lead-jab_south", etc. + const animKey = playerType === 'idle' || playerType === 'walk' + ? `${playerType}-${playerDirection}` + : `${playerType}_${atlasDirection}`; // Keep atlas direction for punch animations + + // Debug for punch animations + if (playerType === 'cross-punch' || playerType === 'lead-jab') { + console.log(` - Punch anim: ${atlasAnimKey} → type: ${playerType}, direction: ${atlasDirection}, key: ${animKey}, frames: ${frames.length}`); + } // For idle animations, create a custom sequence: hold rotation frame for 2s, then loop breathing animation if (playerType === 'idle') { @@ -428,20 +444,38 @@ function createAtlasPlayerAnimations(spriteSheet) { console.log(` ✓ Created custom idle animation: ${animKey} (rotation + breath, ${idleAnimFrames.length} frames)`); } } else { - // Standard animation + // Standard animation (walk, cross-punch, lead-jab, etc.) + const frameConfig = animationFramework[playerType] || { frameRate: walkFrameRate, repeat: -1 }; if (!gameRef.anims.exists(animKey)) { + const frameArray = frames.map(frameName => ({ key: spriteSheet, frame: frameName })); + console.log(` - Creating ${animKey} with ${frameArray.length} frames, frameRate: ${frameConfig.frameRate}, repeat: ${frameConfig.repeat}`); + if (frameArray.length === 0) { + console.warn(` ⚠️ Warning: Animation has 0 frames!`); + } gameRef.anims.create({ key: animKey, - frames: frames.map(frameName => ({ key: spriteSheet, frame: frameName })), - frameRate: playerType === 'idle' ? idleFrameRate : walkFrameRate, - repeat: -1 + frames: frameArray, + frameRate: frameConfig.frameRate, + repeat: frameConfig.repeat }); - console.log(` ✓ Created player animation: ${animKey} (${frames.length} frames @ ${playerType === 'idle' ? idleFrameRate : walkFrameRate} fps)`); + console.log(` ✓ Created ${frameConfig.name} animation: ${animKey} (${frames.length} frames @ ${frameConfig.frameRate} fps, repeat: ${frameConfig.repeat})`); } } } - console.log(`✅ Player atlas animations created for ${spriteSheet} (idle: ${idleFrameRate} fps, walk: ${walkFrameRate} fps)`); + console.log(`✅ Player atlas animations created for ${spriteSheet} (idle: ${idleFrameRate} fps, walk: ${walkFrameRate} fps, punch: ${punchFrameRate} fps)`); + + // Log all punch animations created + const punchAnims = Object.keys(animations).filter(key => key.includes('cross-punch') || key.includes('lead-jab')); + if (punchAnims.length > 0) { + console.log(`🥊 Punch animations available (${punchAnims.length} total):`); + punchAnims.forEach(animName => { + const frameCount = animations[animName].length; + console.log(` - ${animName}: ${frameCount} frames`); + }); + } else { + console.warn('⚠️ No punch animations found in atlas!'); + } } function createLegacyPlayerAnimations(spriteSheet) { diff --git a/public/break_escape/js/systems/player-combat.js b/public/break_escape/js/systems/player-combat.js index bcffcb1..575d06f 100644 --- a/public/break_escape/js/systems/player-combat.js +++ b/public/break_escape/js/systems/player-combat.js @@ -56,30 +56,112 @@ export class PlayerCombat { } /** - * Play punch animation (placeholder) + * Map player directions to atlas compass directions + */ + mapDirectionToCompass(direction) { + const directionMap = { + 'right': 'east', + 'left': 'west', + 'up': 'north', + 'down': 'south', + 'up-right': 'north-east', + 'up-left': 'north-west', + 'down-right': 'south-east', + 'down-left': 'south-west' + }; + return directionMap[direction] || 'south'; + } + + /** + * Play punch animation - tries cross-punch and lead-jab with fallback to red tint */ playPunchAnimation() { if (!window.player) return; - // Apply red tint - if (window.spriteEffects) { - window.spriteEffects.applyAttackTint(window.player); + const player = window.player; + const direction = player.lastDirection || 'down'; + const compassDir = this.mapDirectionToCompass(direction); + + // Try to play punch animation (cross-punch then lead-jab) + const crossPunchKey = `cross-punch_${compassDir}`; + const leadJabKey = `lead-jab_${compassDir}`; + + console.log(`🥊 Punch attempt: direction=${direction}, compass=${compassDir}`); + console.log(` - Trying: ${crossPunchKey} (exists: ${this.scene.anims.exists(crossPunchKey)})`); + console.log(` - Trying: ${leadJabKey} (exists: ${this.scene.anims.exists(leadJabKey)})`); + + // Debug: list all animations starting with cross-punch or lead-jab + const allAnimsManager = this.scene.anims; + const punchAnimsInScene = []; + if (allAnimsManager.animationlist) { + Object.keys(allAnimsManager.animationlist).forEach(key => { + if (key.includes('cross-punch') || key.includes('lead-jab')) { + punchAnimsInScene.push(key); + } + }); } - - // Play walk animation if not already playing - if (!window.player.anims.isPlaying) { - const direction = window.player.lastDirection || 'down'; - window.player.play(`walk_${direction}`, true); + if (punchAnimsInScene.length > 0) { + console.log(` - Available punch animations in scene: ${punchAnimsInScene.join(', ')}`); + } else { + console.warn(` - ⚠️ NO punch animations found in scene!`); } - - // Remove tint after animation - this.scene.time.delayedCall(COMBAT_CONFIG.player.punchAnimationDuration, () => { + + let animPlayed = false; + let playedKey = null; + + // Try cross-punch animation first + if (this.scene.anims.exists(crossPunchKey)) { + console.log(` ✓ Found ${crossPunchKey}, playing...`); + player.anims.play(crossPunchKey, true); + animPlayed = true; + playedKey = crossPunchKey; + console.log(` - After play: currentAnim=${player.anims.currentAnim?.key}, visible=${player.visible}, alpha=${player.alpha}`); + } + // Fall back to lead-jab animation + else if (this.scene.anims.exists(leadJabKey)) { + console.log(` ✓ Found ${leadJabKey}, playing...`); + player.anims.play(leadJabKey, true); + animPlayed = true; + playedKey = leadJabKey; + console.log(` - After play: currentAnim=${player.anims.currentAnim?.key}, visible=${player.visible}, alpha=${player.alpha}`); + } + + if (animPlayed) { + console.log(`🥊 Playing punch animation: ${playedKey}`); + // Animation will complete naturally + // Listen for animation complete event to return to idle + player.once('animationcomplete', () => { + const idleKey = `idle-${direction}`; + if (player.anims && player.anims.exists && this.scene.anims.exists(idleKey)) { + player.anims.play(idleKey, true); + } + }); + } else { + // Fallback: red tint + walk animation + console.log(`⚠️ No punch animations found (tried ${crossPunchKey}, ${leadJabKey}), using fallback (red tint)`); + + // Apply red tint if (window.spriteEffects) { - window.spriteEffects.clearAttackTint(window.player); + window.spriteEffects.applyAttackTint(player); } - // Stop animation - window.player.anims.stop(); - }); + + // Play walk animation if not already playing + if (!player.anims.isPlaying) { + const walkKey = `walk-${direction}`; + if (this.scene.anims.exists(walkKey)) { + player.play(walkKey, true); + } + } + + // Remove tint after animation + this.scene.time.delayedCall(COMBAT_CONFIG.player.punchAnimationDuration, () => { + if (window.spriteEffects) { + window.spriteEffects.clearAttackTint(player); + } + // Stop animation + player.anims.stop(); + }); + } } /** diff --git a/public/break_escape/js/systems/player-effects.js b/public/break_escape/js/systems/player-effects.js index 6d4c255..d612038 100644 --- a/public/break_escape/js/systems/player-effects.js +++ b/public/break_escape/js/systems/player-effects.js @@ -93,7 +93,7 @@ export function createPlayerBumpEffect() { playerCollisionBottom >= itemBottomStart && playerCollisionTop <= itemBottomEnd) { - // Player stepped over a floor item - create one-time hop effect + // Player stepped over a floor item - trigger punch effect steppedOverItems.add(itemId); lastHopTime = currentTime; // Update hop time @@ -102,89 +102,57 @@ export function createPlayerBumpEffect() { steppedOverItems.delete(itemId); }, 2000); - // Create one-time hop effect - if (playerBumpTween) { - playerBumpTween.destroy(); - } - + // Replace red tint effect with punch animation (cross-punch or lead-jab) isPlayerBumping = true; - // Create hop effect using visual overlay - if (playerBumpTween) { - playerBumpTween.destroy(); - } - - // Create a visual overlay sprite that follows the player - if (playerVisualOverlay) { - playerVisualOverlay.destroy(); - } - - playerVisualOverlay = gameRef.add.sprite(player.x, player.y, player.texture.key); - playerVisualOverlay.setFrame(player.frame.name); - playerVisualOverlay.setScale(player.scaleX, player.scaleY); - playerVisualOverlay.setFlipX(player.flipX); // Copy horizontal flip state - playerVisualOverlay.setFlipY(player.flipY); // Copy vertical flip state - playerVisualOverlay.setDepth(player.depth + 1); - playerVisualOverlay.setAlpha(0.8); - - // Hide the original player temporarily - player.setAlpha(0); - - // Always hop upward - negative Y values move sprite up on screen - const hopHeight = -15; // Consistent upward hop - - // Debug: Log the hop details - console.log(`Hop triggered - Player Y: ${player.y}, Overlay Y: ${playerVisualOverlay.y}, Hop Height: ${hopHeight}, Target Y: ${playerVisualOverlay.y + hopHeight}`); - console.log(`Player movement - DeltaX: ${currentX - lastPlayerPosition.x}, DeltaY: ${currentY - lastPlayerPosition.y}`); - - // Start the hop animation with a simple up-down motion - playerBumpTween = gameRef.tweens.add({ - targets: { hopOffset: 0 }, - hopOffset: hopHeight, - duration: 120, - ease: 'Power2', - yoyo: true, - onUpdate: (tween) => { - if (playerVisualOverlay && playerVisualOverlay.active) { - // Apply the hop offset to the current player position - playerVisualOverlay.setY(player.y + tween.getValue()); - } - }, - onComplete: () => { - // Clean up overlay and restore player - if (playerVisualOverlay) { - playerVisualOverlay.destroy(); - playerVisualOverlay = null; - } - player.setAlpha(1); // Restore player visibility - isPlayerBumping = false; - playerBumpTween = null; - } - }); - - // Make overlay follow player movement during hop - const followPlayer = () => { - if (playerVisualOverlay && playerVisualOverlay.active) { - // Update X position and flip states, Y is handled by the tween - playerVisualOverlay.setX(player.x); - playerVisualOverlay.setFlipX(player.flipX); // Update flip state - playerVisualOverlay.setFlipY(player.flipY); // Update flip state - } + // Map player direction to atlas compass direction + const directionMap = { + 'right': 'east', + 'left': 'west', + 'up': 'north', + 'down': 'south', + 'up-right': 'north-east', + 'up-left': 'north-west', + 'down-right': 'south-east', + 'down-left': 'south-west' }; + const compassDir = directionMap[player.direction] || 'south'; - // Update overlay position every frame during hop - const followInterval = setInterval(() => { - if (!playerVisualOverlay || !playerVisualOverlay.active) { - clearInterval(followInterval); - return; - } - followPlayer(); - }, 16); // ~60fps + // Try to play punch/jab animation if available + const crossPunchKey = `cross-punch_${compassDir}`; + const leadJabKey = `lead-jab_${compassDir}`; - // Clean up interval when hop completes - setTimeout(() => { - clearInterval(followInterval); - }, 240); // Slightly longer than animation duration + let animPlayed = false; + + // Try cross-punch animation first + if (gameRef.anims.exists(crossPunchKey)) { + player.anims.play(crossPunchKey, true); + animPlayed = true; + console.log(`🥊 Bump: Playing cross-punch animation: ${crossPunchKey}`); + } + // Fall back to lead-jab animation + else if (gameRef.anims.exists(leadJabKey)) { + player.anims.play(leadJabKey, true); + animPlayed = true; + console.log(`🥊 Bump: Playing lead-jab animation: ${leadJabKey}`); + } + + if (animPlayed) { + // Restore to idle after animation completes + player.once('animationcomplete', () => { + const animDir = typeof getAnimationKey === 'function' ? getAnimationKey(player.direction) : player.direction; + player.anims.play(`idle-${animDir}`, true); + isPlayerBumping = false; + }); + } else { + // Fallback: flash red tint for 120ms if no animation available + console.log(`⚠️ No punch animations found (tried ${crossPunchKey}, ${leadJabKey}), using red tint fallback`); + player.setTint(0xff4444); + setTimeout(() => { + player.clearTint(); + isPlayerBumping = false; + }, 120); + } } }); });