feat(agent-office): add SpriteLoader with procedural fallback for Phase 2
This commit is contained in:
77
src/pages/agent-office/canvas/SpriteLoader.js
Normal file
77
src/pages/agent-office/canvas/SpriteLoader.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user