| name | build-game-engine |
| description | Builds the Pixel Office Canvas 2D game engine with sprite animation, tile map rendering, BFS pathfinding, and character state visualization. Pure Canvas 2D, no external game libraries. Triggers when game engine features need building or modification.
|
Pixel Office Game Engine Builder
์ด ์คํฌ์ Pixel Office์ 2D ๊ฒ์ ์์ง์ ๊ตฌ์ถํ๋ค. ์์ Canvas 2D API๋ง ์ฌ์ฉํ๋ค.
์์กด์ฑ
๋ฐ๋์ ๋จผ์ ์ฝ์ด์ผ ํ ํ์ผ:
src/shared/types.ts โ ํ์
๊ณ์ฝ
src/shared/asset-manifest.ts โ ์์
์ ๋ณด (์๋ ๊ฒฝ์ฐ)
src/shared/office-layout.ts โ ์คํผ์ค ๋ ์ด์์ (์๋ ๊ฒฝ์ฐ)
์์
/๋ ์ด์์ ํ์ผ์ด ์์ง ์์ผ๋ฉด ํ์
์ ์๋ง ๊ธฐ์ค์ผ๋ก ๊ฐ๋ฐํ๊ณ , ๋ฐํ์์ ๋์ ๋ก๋ฉ.
Step 1: ๊ฒ์ ๋ฃจํ (src/engine/GameLoop.ts)
export class GameLoop {
private running = false;
private lastTime = 0;
private updateFn: (dt: number) => void;
private renderFn: () => void;
start(): void;
stop(): void;
pause(): void;
}
ํต์ฌ ์์น
requestAnimationFrame ์ฌ์ฉ
- deltaTime (์ด ๋จ์) ์ ๋ฌ๋ก ํ๋ ์ ๋
๋ฆฝ์
- update์ render ๋ถ๋ฆฌ
- ์ต๋ deltaTime ์บก (0.1์ด) โ ํญ ์ ํ ํ ์ ํ ๋ฐฉ์ง
Step 2: ์คํ๋ผ์ดํธ์ํธ (src/engine/SpriteSheet.ts)
export class SpriteSheet {
constructor(url: string, frameWidth: number, frameHeight: number);
load(): Promise<void>;
drawFrame(ctx, frameIndex, x, y, scale?): void;
}
ํ๋ ์ ์ธ๋ฑ์ฑ
- ์ขโ์ฐ, ์โ์๋ ์์
frameIndex = row * columns + col
- columns = image.width / frameWidth
Step 3: ์บ๋ฆญํฐ (src/engine/Character.ts)
export class Character {
agentState: AgentState;
private currentAnimation: SpriteAnimation;
private frameTimer: number;
private currentFrame: number;
private targetPosition: { x: number; y: number } | null;
private path: { x: number; y: number }[] | null;
updateState(state: AgentState): void;
update(dt: number): void;
render(ctx: CanvasRenderingContext2D): void;
}
์ํ๋ณ ์๊ฐ ํํ
idle: ์ ์๋ฆฌ ๋ฏธ์ธ ์์ง์, ๋ฐํฌ๋ช
ZZZ ๋งํ์
typing: ํค๋ณด๋ ํ์ดํ ๋ชจ์
, ์ฑ
์ ์ ๊ณ ์
reading: ๊ณ ๊ฐ ์ข์ฐ ์์ง์ (ํ์ผ ์ฝ๋ ๋๋)
executing: ํ๋ฐํ ๋์, ์์ ๊ธฐ์ด ์์ด์ฝ
waiting: ๋ฌผ์ํ ๋งํ์ ๊น๋นก (์ฃผ์ ํ์!)
done: ์ฒดํฌ๋งํฌ, ํํ๋ก์ด ์์ธ
error: ๋๋ํ ๋งํ์ , ๋ถ์ ๊น๋นก์
์ด๋ ์ ๋๋ฉ์ด์
- ์ข์ ๋ฐฐ์ ๋ณ๊ฒฝ ์ PathFinder๋ก ๊ฒฝ๋ก ๊ณ์ฐ
- ๊ฒฝ๋ก๋ฅผ ๋ฐ๋ผ ํ์ผ ๋จ์ ์ด๋ (์ด๋น 3ํ์ผ)
- ๋ณด๊ฐ(lerp)์ผ๋ก ๋ถ๋๋ฌ์ด ์ด๋
์ด๋ฆ ๋ผ๋ฒจ
- ์บ๋ฆญํฐ ์ 8px์ ์ด๋ฆ ํ์
- ํฐํธ: 8px ํฝ์
ํฐํธ (๋๋ monospace)
- ๋ฐฐ๊ฒฝ: ๋ฐํฌ๋ช
๊ฒ์์ ๋ฐ์ค
๋งํ์
- ์บ๋ฆญํฐ ์ 20px์ ํ์
waiting โ "?" ๋งํ์ (๋
ธ๋์ ํ
๋๋ฆฌ)
error โ "!" ๋งํ์ (๋นจ๊ฐ์ ํ
๋๋ฆฌ)
- ๋ถ๋๋ฌ์ด ๋ฐ์ด์ค ์ ๋๋ฉ์ด์
Step 4: ํ์ผ๋งต (src/engine/TileMap.ts)
export class TileMap {
constructor(layout: OfficeLayout, spriteSheet: SpriteSheet);
render(ctx, camera): void;
isWalkable(tileX, tileY): boolean;
getSeats(): Seat[];
tileToWorld(tileX, tileY): { x: number; y: number };
worldToTile(worldX, worldY): { tileX: number; tileY: number };
}
๋ ๋๋ง ์ต์ ํ
- ์นด๋ฉ๋ผ ๋ทฐํฌํธ ๋ฒ์์ ํ์ผ๋ง ๋ ๋๋ง
- ํ์ผ๋งต์ ๊ฑฐ์ ๋ณํ์ง ์์ผ๋ฏ๋ก ์คํ์คํฌ๋ฆฐ ์บ๋ฒ์ค์ ์บ์ ๊ฐ๋ฅ
Step 5: ๊ฒฝ๋กํ์ (src/engine/PathFinder.ts)
export class PathFinder {
constructor(isWalkable: (x: number, y: number) => boolean, width: number, height: number);
findPath(startX, startY, endX, endY): { x: number; y: number }[];
}
BFS ์๊ณ ๋ฆฌ์ฆ
- 4๋ฐฉํฅ ์ด๋ (์ํ์ข์ฐ)
- ํ ๊ธฐ๋ฐ BFS
- ๊ฒฝ๋ก ์ญ์ถ์
- ๊ฒฝ๋ก ์์ผ๋ฉด ๋น ๋ฐฐ์ด ๋ฐํ
Step 6: ์นด๋ฉ๋ผ (src/engine/Camera.ts)
export class Camera {
x: number; y: number;
zoom: number;
viewportWidth: number;
viewportHeight: number;
pan(dx: number, dy: number): void;
setZoom(zoom: number): void;
worldToScreen(wx, wy): { x: number; y: number };
screenToWorld(sx, sy): { x: number; y: number };
focusOn(worldX, worldY): void;
}
์ ์ฝ
- ์ค ๋ฒ์: 0.5 ~ 3.0
- ํจ๋ ๋ฒ์: ๋งต ๊ฒฝ๊ณ ยฑ ์ฌ์ 100px
Step 7: ์์ง Public API (src/engine/index.ts)
export class PixelOfficeEngine {
constructor(canvas: HTMLCanvasElement);
start(): void;
stop(): void;
updateAgent(state: AgentState): void;
addAgent(state: AgentState): void;
removeAgent(id: string): void;
focusOnAgent(id: string): void;
onAgentClick(callback: (agentId: string) => void): void;
resize(width: number, height: number): void;
}
์ด๊ฒ์ด frontend-builder๊ฐ ์ฌ์ฉํ ์ ์ผํ ์ธํฐํ์ด์ค.
ํ์ง ์ฒดํฌ๋ฆฌ์คํธ