From a6458a9ff26c868ba30d4c788966cfdde0b3ffd3 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Sun, 15 Feb 2026 00:30:24 +0000 Subject: [PATCH] feat: Add sprite padding constants and adjust depth calculations for player and NPC sprites --- public/break_escape/js/core/game.js | 7 +++-- public/break_escape/js/core/player.js | 19 ++++++++---- public/break_escape/js/systems/npc-hostile.js | 29 +++++++++++++++++++ public/break_escape/js/systems/npc-sprites.js | 15 ++++++---- .../break_escape/js/systems/player-health.js | 26 +++++++++++++++++ public/break_escape/js/utils/constants.js | 6 ++++ 6 files changed, 90 insertions(+), 12 deletions(-) diff --git a/public/break_escape/js/core/game.js b/public/break_escape/js/core/game.js index adf9444..de2d9d3 100644 --- a/public/break_escape/js/core/game.js +++ b/public/break_escape/js/core/game.js @@ -22,7 +22,7 @@ import { PlayerCombat } from '../systems/player-combat.js'; import { NPCCombat } from '../systems/npc-combat.js'; import { ApiClient } from '../api-client.js'; // Import to ensure window.ApiClient is set import { getTutorialManager } from '../systems/tutorial-manager.js'; -import { TILE_SIZE } from '../utils/constants.js'; +import { TILE_SIZE, SPRITE_PADDING_BOTTOM_ATLAS, SPRITE_PADDING_BOTTOM_LEGACY } from '../utils/constants.js'; // Global variables that will be set by main.js let gameScenario; @@ -885,7 +885,10 @@ export async function create() { } else { // NPC was out of range - treat click as a movement request // Calculate floor-level destination at the NPC's position, offset to stop short - const npcBottomY = npcAtPosition.y + (npcAtPosition.height * (1 - (npcAtPosition.originY || 0.5))); + // Account for sprite padding (16px for atlas sprites) + const spriteCenterToBottom = npcAtPosition.height * (1 - (npcAtPosition.originY || 0.5)); + const paddingOffset = npcAtPosition.isAtlas ? SPRITE_PADDING_BOTTOM_ATLAS : SPRITE_PADDING_BOTTOM_LEGACY; + const npcBottomY = npcAtPosition.y + spriteCenterToBottom - paddingOffset; const stopShortOffset = TILE_SIZE * 0.75; // Stop 24 pixels short (3/4 tile) movePlayerToPoint(npcAtPosition.x, npcBottomY + stopShortOffset); return; diff --git a/public/break_escape/js/core/player.js b/public/break_escape/js/core/player.js index 4746710..239e4b4 100644 --- a/public/break_escape/js/core/player.js +++ b/public/break_escape/js/core/player.js @@ -10,7 +10,9 @@ import { PLAYER_FEET_OFFSET_Y, ROOM_CHECK_THRESHOLD, CLICK_INDICATOR_SIZE, - CLICK_INDICATOR_DURATION + CLICK_INDICATOR_DURATION, + SPRITE_PADDING_BOTTOM_ATLAS, + SPRITE_PADDING_BOTTOM_LEGACY } from '../utils/constants.js?v=8'; export let player = null; @@ -94,6 +96,9 @@ export function createPlayer(gameInstance) { player = gameInstance.add.sprite(startRoomPosition.x, startRoomPosition.y, playerSprite, initialFrame); gameInstance.physics.add.existing(player); + // Store atlas detection flag for depth calculations + player.isAtlas = isAtlas; + // Keep the character at original size player.setScale(1); @@ -624,8 +629,12 @@ export function movePlayerToPoint(x, y) { } function updatePlayerDepth(x, y) { - // Get the bottom of the player sprite (feet position) - const playerBottomY = y + (player.height * player.scaleY) / 2; + // Get the bottom of the player sprite, accounting for padding + // Atlas sprites (80x80) have 16px padding at bottom, legacy sprites (64x64) have minimal padding + // Use actual y parameter so depth follows visual position (including during death animations) + const spriteCenterToBottom = (player.height * player.scaleY) / 2; + const paddingOffset = player.isAtlas ? SPRITE_PADDING_BOTTOM_ATLAS : SPRITE_PADDING_BOTTOM_LEGACY; + const playerBottomY = y + spriteCenterToBottom - paddingOffset; // Simple depth calculation: world Y position + layer offset const playerDepth = playerBottomY + 0.5; // World Y + sprite layer offset @@ -637,8 +646,8 @@ function updatePlayerDepth(x, y) { // Debug logging - only show when depth actually changes significantly const lastDepth = player.lastDepth || 0; if (Math.abs(playerDepth - lastDepth) > 25) { // Reduced threshold for finer granularity - console.log(`Player depth: ${playerDepth} (World Y: ${playerBottomY})`); - console.log(` Player layers: worldY(${playerBottomY}) + 0.5`); + console.log(`Player depth: ${playerDepth} (Feet Y: ${playerBottomY}, Padding: ${paddingOffset}px)`); + console.log(` Player layers: feetY(${playerBottomY}) + 0.5`); player.lastDepth = playerDepth; } } diff --git a/public/break_escape/js/systems/npc-hostile.js b/public/break_escape/js/systems/npc-hostile.js index 3d85854..c33ae9f 100644 --- a/public/break_escape/js/systems/npc-hostile.js +++ b/public/break_escape/js/systems/npc-hostile.js @@ -153,6 +153,35 @@ function playNPCDeathAnimation(npcId, sprite) { if (sprite.anims.isPlaying) { sprite.anims.stop(); } + + // Store original origin for restoration + const originalOriginY = sprite.originY; + + // Add animation update listener to progressively shift visual display downward + sprite.on('animationupdate', (anim, frame) => { + if (anim.key === deathAnimKey) { + // Calculate progress through animation (0 to 1) + const totalFrames = anim.getTotalFrames(); + const currentFrame = frame.index; + const progress = currentFrame / totalFrames; + + // Shift sprite's visual display downward by adjusting origin + // This moves the texture down without changing sprite.y (keeps depth constant) + // Decrease originY by ~0.5 to shift texture down by half sprite height (~40px for 80px sprite) + const originOffset = progress * 0.5; + sprite.setOrigin(sprite.originX, originalOriginY - originOffset); + } + }); + + // Clean up listener when animation completes + sprite.once('animationcomplete', (anim) => { + if (anim.key === deathAnimKey) { + sprite.off('animationupdate'); + // Origin stays shifted - defeated body appears lower visually but sprite.y unchanged + // Depth remains constant, naturally rendering behind standing characters at same Y + } + }); + sprite.play(deathAnimKey); console.log(`💀 Playing NPC death animation: ${deathAnimKey}`); } else { diff --git a/public/break_escape/js/systems/npc-sprites.js b/public/break_escape/js/systems/npc-sprites.js index c5e1e19..aadfcfe 100644 --- a/public/break_escape/js/systems/npc-sprites.js +++ b/public/break_escape/js/systems/npc-sprites.js @@ -7,7 +7,7 @@ * @module npc-sprites */ -import { TILE_SIZE } from '../utils/constants.js?v=8'; +import { TILE_SIZE, SPRITE_PADDING_BOTTOM_ATLAS, SPRITE_PADDING_BOTTOM_LEGACY } from '../utils/constants.js?v=8'; /** * Create an NPC sprite in the game world @@ -78,6 +78,7 @@ export function createNPCSprite(scene, npc, roomData) { const sprite = scene.add.sprite(worldPos.x, worldPos.y, spriteSheet, initialFrame); sprite.npcId = npc.id; // Tag for identification sprite._isNPC = true; // Mark as NPC sprite + sprite.isAtlas = isAtlas; // Store for depth calculation console.log(`🎭 NPC ${npc.id} created with ${isAtlas ? 'atlas' : 'legacy'} sprite (${spriteSheet}), initial frame: ${initialFrame}`); @@ -556,16 +557,20 @@ export function setupNPCAnimations(scene, sprite, spriteSheet, config, npcId) { /** * Update NPC sprite depth based on Y position * - * Uses same system as player (bottomY + 0.5) to ensure correct - * perspective in top-down view. + * Uses same system as player (feetY + 0.5) to ensure correct + * perspective in top-down view. Accounts for sprite padding. * * @param {Phaser.Sprite} sprite - NPC sprite to update */ export function updateNPCDepth(sprite) { if (!sprite || !sprite.body) return; - // Get the bottom of the sprite (feet position) - const spriteBottomY = sprite.y + (sprite.displayHeight / 2); + // Get the bottom of the sprite, accounting for padding + // Atlas sprites (80x80) have 16px padding at bottom, legacy sprites (64x64) have minimal padding + // Use actual sprite.y so depth follows visual position (including during death animations) + const spriteCenterToBottom = sprite.displayHeight / 2; + const paddingOffset = sprite.isAtlas ? SPRITE_PADDING_BOTTOM_ATLAS : SPRITE_PADDING_BOTTOM_LEGACY; + const spriteBottomY = sprite.y + spriteCenterToBottom - paddingOffset; // Set depth using standard formula const depth = spriteBottomY + 0.5; // World Y + sprite layer offset diff --git a/public/break_escape/js/systems/player-health.js b/public/break_escape/js/systems/player-health.js index 19d8fcd..69981d9 100644 --- a/public/break_escape/js/systems/player-health.js +++ b/public/break_escape/js/systems/player-health.js @@ -99,6 +99,32 @@ function playPlayerDeathAnimation() { const deathAnimKey = `falling-back-death_${compassDir}`; if (player.scene.anims.exists(deathAnimKey)) { + // Store original origin for visual shift + const originalOriginY = player.originY; + + // Add animation update listener to progressively shift visual display downward + player.on('animationupdate', (anim, frame) => { + if (anim.key === deathAnimKey) { + // Calculate progress through animation (0 to 1) + const totalFrames = anim.getTotalFrames(); + const currentFrame = frame.index; + const progress = currentFrame / totalFrames; + + // Shift player's visual display downward by adjusting origin + // Decrease originY by ~0.4 to shift texture down (~32px for 80px sprite) + const originOffset = progress * 0.4; + player.setOrigin(player.originX, originalOriginY - originOffset); + } + }); + + // Clean up listener when animation completes + player.once('animationcomplete', (anim) => { + if (anim.key === deathAnimKey) { + player.off('animationupdate'); + // Origin stays shifted - defeated player appears lower but depth unchanged + } + }); + player.play(deathAnimKey); console.log(`💀 Playing player death animation: ${deathAnimKey}`); } else { diff --git a/public/break_escape/js/utils/constants.js b/public/break_escape/js/utils/constants.js index 14ae64b..9025bb4 100644 --- a/public/break_escape/js/utils/constants.js +++ b/public/break_escape/js/utils/constants.js @@ -26,6 +26,12 @@ export const CLICK_INDICATOR_DURATION = 800; // milliseconds export const CLICK_INDICATOR_SIZE = 20; // pixels export const PLAYER_FEET_OFFSET_Y = 30; // Adjust based on your sprite's feet position (64px sprite) +// Sprite dimensions and padding +export const SPRITE_SIZE_ATLAS = 80; // Atlas sprites (PixelLab) are 80x80px +export const SPRITE_SIZE_LEGACY = 64; // Legacy sprites are 64x64px +export const SPRITE_PADDING_BOTTOM_ATLAS = 6; // Atlas sprites have 16px padding at bottom +export const SPRITE_PADDING_BOTTOM_LEGACY = 4; // Legacy sprites have minimal bottom padding + // Room visibility settings export const HIDE_ROOMS_INITIALLY = true; export const HIDE_ROOMS_ON_EXIT = false;