feat: Agent Office — AI 에이전트 가상 오피스 (#2)
## Summary - Canvas 2D 픽셀아트 오피스 렌더링 (SpriteSheet + TileMap + AgentSprite) - WebSocket 실시간 에이전트 상태 동기화 (useAgentManager) - ChatPanel (명령/승인) + TaskHistory (작업 이력) UI - 다크 테마 + glassmorphism 패널 ## Changes (7 commits) - API helpers + route + Lab entry - Canvas engine: SpriteSheet, TileMap, AgentSprite, OfficeRenderer - React hooks: useAgentManager, useOfficeCanvas - Components: ChatPanel, TaskHistory - Main page + CSS - Code review fixes: claude agent 참조 제거, rejected 배지 추가 Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
84
src/pages/agent-office/canvas/AgentSprite.js
Normal file
84
src/pages/agent-office/canvas/AgentSprite.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { drawAgent, getAnimSpeed } from './SpriteSheet';
|
||||
|
||||
export class AgentSprite {
|
||||
constructor(agentId, waypoints) {
|
||||
this.agentId = agentId;
|
||||
this.waypoints = waypoints;
|
||||
this.state = 'idle';
|
||||
this.detail = '';
|
||||
|
||||
const deskKey = `${agentId}_desk`;
|
||||
const desk = waypoints[deskKey] || { x: 5, y: 3 };
|
||||
this.x = desk.x;
|
||||
this.y = desk.y;
|
||||
this.targetX = desk.x;
|
||||
this.targetY = desk.y;
|
||||
this.deskPos = { x: desk.x, y: desk.y };
|
||||
|
||||
this.frameIndex = 0;
|
||||
this._lastFrameTime = 0;
|
||||
this._moveSpeed = 0.05;
|
||||
}
|
||||
|
||||
setState(newState, detail = '') {
|
||||
this.state = newState;
|
||||
this.detail = detail;
|
||||
this.frameIndex = 0;
|
||||
}
|
||||
|
||||
moveTo(target) {
|
||||
const wp = this.waypoints[target];
|
||||
if (wp) {
|
||||
this.targetX = wp.x;
|
||||
this.targetY = wp.y;
|
||||
}
|
||||
}
|
||||
|
||||
moveToDesk() {
|
||||
this.targetX = this.deskPos.x;
|
||||
this.targetY = this.deskPos.y;
|
||||
}
|
||||
|
||||
update(now) {
|
||||
const speed = getAnimSpeed(this.state);
|
||||
if (now - this._lastFrameTime > speed) {
|
||||
this.frameIndex++;
|
||||
this._lastFrameTime = now;
|
||||
}
|
||||
|
||||
const dx = this.targetX - this.x;
|
||||
const dy = this.targetY - this.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist > 0.1) {
|
||||
const step = Math.min(this._moveSpeed, dist);
|
||||
this.x += (dx / dist) * step;
|
||||
this.y += (dy / dist) * step;
|
||||
} else {
|
||||
this.x = this.targetX;
|
||||
this.y = this.targetY;
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx, renderInfo) {
|
||||
const { scale, offsetX, offsetY, tileSize } = renderInfo;
|
||||
const canvasX = offsetX + this.x * tileSize * scale + (tileSize * scale) / 2;
|
||||
const canvasY = offsetY + this.y * tileSize * scale + (tileSize * scale) / 2;
|
||||
|
||||
const isMoving = Math.abs(this.targetX - this.x) > 0.1 || Math.abs(this.targetY - this.y) > 0.1;
|
||||
const drawState = isMoving ? 'walk' : this.state;
|
||||
|
||||
drawAgent(ctx, this.agentId, canvasX, canvasY, drawState, this.frameIndex, scale * 1.5);
|
||||
}
|
||||
|
||||
hitTest(canvasX, canvasY, renderInfo) {
|
||||
const { scale, offsetX, offsetY, tileSize } = renderInfo;
|
||||
const cx = offsetX + this.x * tileSize * scale + (tileSize * scale) / 2;
|
||||
const cy = offsetY + this.y * tileSize * scale + (tileSize * scale) / 2;
|
||||
const hitW = 20 * scale;
|
||||
const hitH = 30 * scale;
|
||||
|
||||
return canvasX >= cx - hitW && canvasX <= cx + hitW &&
|
||||
canvasY >= cy - hitH && canvasY <= cy + hitH;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user