// src/pages/agent-office/canvas/SpriteLoader.js import { ProceduralSprite } from './ProceduralSprite.js'; /** * 스프라이트 로더 — PNG 스프라이트시트가 있으면 사용, 없으면 프로시저럴 폴백 * * 스프라이트시트 규격 (Phase 2): * - 프레임 크기: 16×32px * - 행: 방향 (0=down, 1=up, 2=right) * - 열: 상태별 프레임 (idle 2 | walk 4 | type 2 | wait 2 | break 2 = 12열) */ export class SpriteLoader { constructor() { this.sprites = new Map(); // agentId → { image: Image, loaded: boolean } } /** PNG 스프라이트시트 로드 시도 */ async tryLoad(agentId, url) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { this.sprites.set(agentId, { image: img, loaded: true }); resolve(true); }; img.onerror = () => { resolve(false); // 폴백 사용 }; img.src = url; }); } hasSprite(agentId) { return this.sprites.has(agentId) && this.sprites.get(agentId).loaded; } /** * 에이전트 1프레임 그리기 (스프라이트 또는 프로시저럴) */ draw(ctx, agentId, state, direction, frame, x, y, scale) { if (this.hasSprite(agentId)) { this._drawFromSheet(ctx, agentId, state, direction, frame, x, y, scale); } else { ProceduralSprite.draw(ctx, agentId, state, direction, frame, x, y, scale); } } _drawFromSheet(ctx, agentId, state, direction, frame, x, y, scale) { const { image } = this.sprites.get(agentId); const frameW = 16; const frameH = 32; // 방향 → 행 const dirRow = direction === 'up' ? 1 : direction === 'right' || direction === 'left' ? 2 : 0; // 상태 → 열 오프셋 const stateOffsets = { idle: 0, walk: 2, type: 6, wait: 8, break_anim: 10 }; const mappedState = state === 'working' ? 'type' : state === 'waiting' ? 'wait' : state === 'reporting' ? 'type' : state === 'break' ? 'break_anim' : state === 'walk' ? 'walk' : 'idle'; const colOffset = stateOffsets[mappedState] || 0; const srcX = (colOffset + frame) * frameW; const srcY = dirRow * frameH; const destW = frameW * scale; const destH = frameH * scale; ctx.save(); if (direction === 'left') { ctx.translate(x, 0); ctx.scale(-1, 1); ctx.translate(-x, 0); } ctx.drawImage(image, srcX, srcY, frameW, frameH, x - destW / 2, y - destH, destW, destH); ctx.restore(); } }