import React, {useEffect, useState} from 'react'; export interface HostileNpcAnimationConfig { start: number; frames: number; fps?: number; } export interface HostileNpcSpriteConfig { id: string; name: string; src: string; frameWidth: number; frameHeight: number; sheetWidth: number; animations: { idle: HostileNpcAnimationConfig; move?: HostileNpcAnimationConfig; attack?: HostileNpcAnimationConfig; die?: HostileNpcAnimationConfig; }; } interface HostileNpcAnimatorProps { hostileNpc: HostileNpcSpriteConfig; animation?: keyof HostileNpcSpriteConfig['animations']; className?: string; flip?: boolean; } export const HostileNpcAnimator: React.FC = ({ hostileNpc, animation = 'idle', className, flip = false, }) => { const [frameOffset, setFrameOffset] = useState(0); const anim = hostileNpc.animations[animation] ?? (animation === 'die' ? hostileNpc.animations.attack : undefined) ?? (animation === 'move' ? hostileNpc.animations.attack : undefined) ?? hostileNpc.animations.idle; const columns = Math.max(1, Math.floor(hostileNpc.sheetWidth / hostileNpc.frameWidth)); const shouldLoop = animation !== 'die' || !hostileNpc.animations.die; useEffect(() => { setFrameOffset(0); if (anim.frames <= 1) { return; } const interval = setInterval(() => { setFrameOffset(prev => { if (!shouldLoop) { return Math.min(prev + 1, anim.frames - 1); } return (prev + 1) % anim.frames; }); }, 1000 / (anim.fps ?? 12)); return () => clearInterval(interval); }, [anim, shouldLoop]); const frameIndex = anim.start + frameOffset; const col = frameIndex % columns; const row = Math.floor(frameIndex / columns); return (
); };