ECS(Entity Component System)는 컴포지션을 통해 게임 객체를 구성하는 데이터 지향 아키텍처 패턴이다. 상속 대신 조합으로 유연성을 높이고, 메모리 레이아웃 최적화로 캐시 효율을 극대화한다.
ECS 구성요소
Entity: 고유 ID만 가진 컨테이너 (int 또는 uint64)
Component: 순수 데이터 (Transform, Velocity, Health 등)
System: 특정 컴포넌트를 가진 Entity를 처리하는 로직
전통 OOP vs ECS 비교
| 항목 | 전통 OOP | ECS |
|---|
| 설계 방식 | 상속 계층 | 조합 |
| 메모리 배치 | 분산 (캐시 미스) | 연속 배열 (캐시 최적) |
| 유연성 | 낮음 (다중상속 문제) | 높음 (런타임 조합) |
| 병렬화 | 어려움 | 쉬움 (데이터 분리) |
| 사용 예 | 일반 게임 | AAA 게임, Unity DOTS |
typescript
// 컴포넌트 정의 (순수 데이터)
interface Position { x: number; y: number; }
interface Velocity { vx: number; vy: number; }
interface Health { current: number; max: number; }
interface Renderable { sprite: string; layer: number; }
class World {
private entities = new Map<number, Set<string>>();
private components = new Map<string, Map<number, any>>();
private nextId = 0;
createEntity(): number {
const id = this.nextId++;
this.entities.set(id, new Set());
return id;
}
addComponent<T>(entity: number, type: string, data: T): void {
if (!this.components.has(type))
this.components.set(type, new Map());
this.components.get(type)!.set(entity, data);
this.entities.get(entity)!.add(type);
}
getComponent<T>(entity: number, type: string): T | undefined {
return this.components.get(type)?.get(entity);
}
query(...types: string[]): number[] {
return [...this.entities.entries()]
.filter(([_, comps]) => types.every(t => comps.has(t)))
.map(([id]) => id);
}
}
// 시스템 정의
class MovementSystem {
update(world: World, dt: number): void {
const entities = world.query('Position', 'Velocity');
for (const entity of entities) {
const pos = world.getComponent<Position>(entity, 'Position')!;
const vel = world.getComponent<Velocity>(entity, 'Velocity')!;
pos.x += vel.vx * dt;
pos.y += vel.vy * dt;
}
}
}
// 사용
const world = new World();
const player = world.createEntity();
world.addComponent(player, 'Position', { x: 0, y: 0 });
world.addComponent(player, 'Velocity', { vx: 100, vy: 0 });
world.addComponent(player, 'Health', { current: 100, max: 100 });
관련 문서