Enhance player combat animations and effects with punch mechanics

This commit is contained in:
Z. Cliffe Schreuders
2026-02-13 09:53:58 +00:00
parent 61afc0a666
commit a84d309809
3 changed files with 187 additions and 103 deletions

View File

@@ -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) {

View File

@@ -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();
});
}
}
/**

View File

@@ -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);
}
}
});
});