엔티티 및 컴포넌트
Pyxen은 엔티티-컴포넌트 시스템 (ECS)을 사용합니다. Player가 Character를 상속하고 Character가 GameObject를 상속하는 클래스 계층 구조를 사용하는 게임 엔진과는 다릅니다. Pyxen에는 상속이 없습니다. 모든 것이 조합(composition)입니다.
엔티티
엔티티는 게임 오브젝트입니다. 플레이어, 벽, 총알, 파티클 등 무엇이든 될 수 있습니다. 내부적으로 엔티티는 단순한 ID입니다. 자체적으로 데이터를 보유하지 않으며, 대신 컴포넌트를 붙입니다.
player = world.spawn(name="player", x=100, y=50)
이 코드는 위치 (100, 50)에 이름을 가진 엔티티를 생성합니다. 엔티티는 월드에 존재하며 스프라이트가 있으면 렌더링됩니다.
컴포넌트
컴포넌트는 엔티티에 첨부된 데이터 조각입니다. Pyxen에는 두 종류가 있습니다:
내장 컴포넌트
엔진에서 제공하는 컴포넌트입니다:
| 컴포넌트 | 용도 |
|---|---|
| Transform | 위치 (x, y), 회전, 스케일 |
| Sprite | 시각적 외관 — 이미지, 타일, 피벗, 색상 |
| Camera | 줌이 있는 뷰포트 |
| GridBody | 그리드 위의 충돌 바디 |
| GridMap | 행, 열, 타일 데이터가 있는 타일맵 |
| Sound | 효과음 재생 |
| Music | 음악 스트림 재생 |
| Name | 조회용 문자열 식별자 |
| Layer | 렌더링 레이어 (0–255) |
| Parent / Children | 씬 계층 구조 |
내장 컴포넌트는 엔티티 속성이나 생성 시점에 설정합니다:
e = world.spawn(
x=50, y=80,
sprite=Sprite("hero"),
name="player"
)
# 나중에
e.x = 100
e.sprite = Sprite("hero", tile=(16, 0, 16, 16))
커스텀 컴포넌트
어떤 엔티티에든 자신만의 컴포넌트를 붙일 수 있습니다. 커스텀 컴포넌트는 타입이 있는 필드를 가진 딕셔너리입니다:
player.health = {"value": 100, "max": 100}
player.speed = {"value": 2.5}
붙인 뒤에는 필드에 직접 접근할 수 있습니다:
player.health.value -= 10
마커 컴포넌트 — 데이터 없이 태그 역할을 하는 컴포넌트 — 도 사용할 수 있습니다:
player.alive = True
enemy.boss = True
이는 쿼리에 유용합니다: 특정 마커를 가진 모든 엔티티를 찾을 수 있습니다.
필드 타입
컴포넌트 필드는 다음이 될 수 있습니다:
| 타입 | 예시 | 비고 |
|---|---|---|
int | 42 | 32비트 정수 |
float | 3.14 | 32비트 부동소수점 |
bool | True | 불리언 |
str | "hello" | 최대 31자 |
| Entity | player | 다른 엔티티에 대한 참조 |
이러한 제약은 컴포넌트가 C++ 엔진 내부에서 컴팩트하고 캐시 친화적인 레이아웃에 저장되기 때문입니다. 이것이 쿼리를 빠르게 만듭니다.
엔티티 쿼리
world를 통해 컴포넌트로 엔티티를 찾을 수 있습니다:
# "health" 컴포넌트가 있는 모든 엔티티
for e in world.all("health"):
if e.health.value <= 0:
world.destroy(e)
# 조합: "enemy"가 있고, "dead"가 없는 엔티티
for e in world.all("enemy", without=("dead",)):
e.x += e.speed.value
"sprite", "body", "camera", "sound", "music", "map" 같은 내장 컴포넌트로도 쿼리할 수 있습니다:
# 스프라이트가 있는 모든 엔티티
for e in world.all("sprite"):
e.color = (1, 1, 1, 1)
# 스프라이트와 "enemy" 커스텀 컴포넌트가 모두 있는 엔티티
for e in world.all("sprite", "enemy"):
e.color = (1, 0, 0, 1)
쿼리는 Pyxen에서 게임 로직을 작성하는 주요 방법입니다. 각 오브젝트가 스스로를 업데이트하는 대신, 특정 컴포넌트를 가진 엔티티를 순회하는 시스템을 작성합니다.
전체 API 보기: world.all(), Entity
왜 ECS인가?
객체지향 게임 코드에 익숙하다면, ECS가 처음에는 낯설게 느껴질 수 있습니다. 장점은 다음과 같습니다:
- 클래스 계층 구조 없음 — PowerUp이 Collectible이고 Collectible이 Entity인지 미리 결정할 필요가 없습니다. 필요한 컴포넌트만 붙이면 됩니다.
- 동작 조합이 쉬움 — 엔티티가
health,enemy,flying,boss컴포넌트를 동시에 가질 수 있으며, 다중 상속 없이 가능합니다. - 빠른 쿼리 — 엔진이 컴포넌트를 연속된 메모리에 저장하므로, 특정 컴포넌트를 가진 모든 엔티티를 순회하는 것이 매우 효율적입니다.
- 디버깅이 쉬움 — 모든 엔티티는 단순한 데이터 묶음입니다. 프레임 인스펙터가 각 엔티티에 무엇이 붙어 있는지 정확히 보여줍니다.