import { Container, Sprite } from 'pixi.js'; import type { GameStore } from '../../game/GameStore'; import { CHARACTER_SPRITE } from '../../game/sprites'; import { chopShakeOffset, walkBobOffsetY } from '../animations'; import { createHealthBar, setHealthBarPosition, updateHealthBar } from '../healthBar'; import { createShadow } from '../shadow'; import { getSpriteTexture } from '../textures'; const SHADOW_OFFSET_Y = 11; const SHADOW_RADIUS_X = 8; const SHADOW_RADIUS_Y = 4; /** How far above the character's own position the hp bar floats. */ const HEALTH_BAR_OFFSET_Y = 10; /** Renders the player character: shadow, sprite (with walk-bob/hit-shake), and an hp bar shown * only while damaged — mirrors CharacterEntity.tsx. */ export class CharacterView { private readonly shadow = createShadow(SHADOW_RADIUS_X, SHADOW_RADIUS_Y); private readonly sprite: Sprite; private readonly healthBar = createHealthBar(); constructor(entities: Container, healthBars: Container) { const info = getSpriteTexture(CHARACTER_SPRITE); this.sprite = new Sprite(info.texture); this.sprite.anchor.set(0.5); entities.addChild(this.shadow, this.sprite); healthBars.addChild(this.healthBar); } sync(store: GameStore, elapsedSeconds: number): void { const { x, y } = store.character; this.shadow.position.set(x, y + SHADOW_OFFSET_Y); this.shadow.zIndex = y - 1; this.sprite.zIndex = y; if (store.characterHitFlashTimer > 0) { const shake = chopShakeOffset(elapsedSeconds); this.sprite.position.set(x + shake.x, y + shake.y); this.sprite.rotation = shake.rotation; } else { const bobY = store.isMoving ? walkBobOffsetY(elapsedSeconds) : 0; this.sprite.position.set(x, y + bobY); this.sprite.rotation = 0; } const damaged = store.characterHp < store.characterMaxHp; this.healthBar.visible = damaged; if (damaged) { updateHealthBar(this.healthBar, store.characterHp / store.characterMaxHp); setHealthBarPosition(this.healthBar, x, y - HEALTH_BAR_OFFSET_Y); } } }