Component와 그 데이터를 처리하는 System으로 분리하는 아키텍처.
| 요소 | 역할 |
|---|---|
| Entity | 고유 ID. 실체를 구분하는 식별자 |
| Component | 데이터만 가진 조각 |
| System | 특정 Component 조합을 가진 Entity를 처리하는 로직 |
Entity
Entity는 게임 월드에 존재하는 하나의 ID
| Entity | 의미 |
|---|---|
| Player Entity | 플레이어를 나타내는 ID |
| Enemy Entity | 적을 나타내는 ID |
| Bullet Entity | 총알을 나타내는 ID |
| Item Entity | 아이템을 나타내는 ID |
using Entity = uint32;
Entity Player = 1;
Entity Enemy = 2;
Entity Bullet = 3;
Entity 자체는 기능을 가지지 않는다.
Entity가 어떤 Component를 가지고 있느냐에 따라 의미가 결정된다.
Component
Component는 데이터만 가진다.
struct PositionComponent
{
float X;
float Y;
float Z;
};
struct VelocityComponent
{
float X;
float Y;
float Z;
};
struct HealthComponent
{
float CurrentHealth;
float MaxHealth;
};
Component는 가능하면 로직을 가지지 않는다.
데이터를 담고, System이 그 데이터를 처리한다.
System
특정 Component 조합을 가진 Entity들을 찾아서 처리한다.
예를 들어 이동 시스템은 PositionComponent와 VelocityComponent를 가진 Entity만 처리한다.
void MovementSystem(float DeltaTime)
{
for (Entity E : EntitiesWith<PositionComponent, VelocityComponent>())
{
PositionComponent& Position = GetComponent<PositionComponent>(E);
VelocityComponent& Velocity = GetComponent<VelocityComponent>(E);
Position.X += Velocity.X * DeltaTime;
Position.Y += Velocity.Y * DeltaTime;
Position.Z += Velocity.Z * DeltaTime;
}
}
System은 Entity가 어떤 Component를 가지고 있는지를 기준으로 동작.
ECS 예시
플레이어, 적, 총알을 ECS로 표현하면 다음처럼 구성할 수 있다.
| Entity | Component 조합 |
|---|---|
| Player | Position, Velocity, Health, Input, Mesh, Inventory |
| Enemy | Position, Velocity, Health, AI, Mesh |
| Bullet | Position, Velocity, Damage, Collider, Lifetime |
| Item | Position, Mesh, ItemData, Pickup |
상속 구조 없이 Component 조합만으로 대상의 특징을 만든다.
OOP 방식과 ECS 방식
OOP는 기능 조합이 많아질수록 상속 구조가 복잡해지고 메모리가 커진다.
| 방식 | 특징 |
|---|---|
| OOP | 클래스 상속 중심 |
| ECS | Component 조합 중심 |
| OOP 확장 | 새 클래스를 만들기 쉬움 |
| ECS 확장 | 새 Component와 System을 추가하기 쉬움 |
| OOP 문제 | 상속 구조가 깊어질 수 있음 |
| ECS 문제 | 구조가 추상적이고 초기 설계가 어려움 |
ECS 장점
ECS는 상속보다 조합을 중심으로 한다.
기능이 늘어나도 상속 계층을 깊게 만들 필요가 없다.Component는 데이터만 가지고, System은 로직만 가진다.
데이터 구조와 처리 흐름이 명확해진다.ECS는 많은 Entity를 한 번에 처리하는 데 강하다.
OOP 방식에서는 각 Actor가 자기 Tick에서 개별적으로 동작한다.
ECS에서는 System이 모든 Entity를 한 번에 처리한다.ECS는 데이터 중심 구조이기 때문에 CPU 캐시 효율과 대량 처리에 유리하다.
ECS 단점
구조가 직관적이지 않을 수 있음
OOP에서는Enemy->Attack()처럼 객체 중심으로 읽힌다.
ECS에서는AttackSystem이AttackComponent를 가진 Entity들을 처리한다.디버깅이 어려울 수 있음
로직이 여러 System으로 나뉘기 때문에,
하나의 Entity가 왜 특정 상태가 되었는지 추적이 어려울 수 있다.작은 프로젝트에는 과할 수 있음
Entity 수가 적고, 기능이 단순한 프로젝트라면 ECS 구조가 오히려 복잡할 수 있다.
Unreal의 Actor Component와 ECS 차이
Unreal의 Component는 기능을 붙이는 객체,
ECS의 Component는 순수 데이터
Unreal Actor Component
flowchart TD
A[AActor] --> B[UActorComponent]
A --> C[USceneComponent]
C --> D[UStaticMeshComponent]
C --> E[USkeletalMeshComponent]
Unreal의 Actor Component 구조는 전통적인 의미의 ECS와 다르다.
| 구분 | Unreal Actor Component | ECS |
|---|---|---|
| 중심 | Actor 객체 | Entity ID |
| Component | UObject 기반 기능 객체 | 데이터 조각 |
| 로직 위치 | Actor 또는 Component 내부 | System |
| Tick | Actor/Component별 Tick 가능 | System 단위 업데이트 |
| 데이터 배치 | 객체 단위로 흩어짐 | Component 배열 중심 |
| 처리 방식 | 객체 중심 | 데이터 중심 |
Unreal에서 ECS가 필요한 경우
대량의 객체를 처리해야 하는 경우 ECS적 사고가 유용하다.
Actor를 수천 개 Tick시키는 대신, Manager나 Subsystem이 데이터를 모아서 일괄 처리하면 ECS에 가까운 구조가 된다.
Unreal식 ECS 응용
| ECS 원칙 | Unreal 적용 |
|---|---|
| 데이터와 로직 분리 | DataAsset, Struct, Component 분리 |
| System 단위 처리 | Subsystem, Manager Actor 사용 |
| 대량 객체 일괄 갱신 | 개별 Tick 제거 후 Manager Tick 사용 |
| Component 조합 | ActorComponent로 기능 조합 |
| 캐시 친화적 데이터 | TArray 구조체 배열 사용 |
| 이벤트 기반 처리 | GameplayMessageSubsystem, Delegate 사용 |
ECS의 데이터 중심 처리 장점을 일부 가져오는 방식
USTRUCT()
struct FEnemyRuntimeData
{
GENERATED_BODY()
UPROPERTY()
TObjectPtr<AActor> EnemyActor;
FVector Location;
FVector Velocity;
float Health;
float UpdateInterval;
};
void UEnemySimulationSubsystem::UpdateEnemies(float DeltaTime)
{
for (FEnemyRuntimeData& Enemy : Enemies)
{
Enemy.Location += Enemy.Velocity * DeltaTime;
}
}
요약
| 개념 | 요약 |
|---|---|
| Entity | 객체를 나타내는 ID |
| Component | Entity가 가진 데이터 |
| System | Component 조합을 처리하는 로직 |
| ECS 핵심 | 상속보다 조합, 객체보다 데이터 중심 |
| 장점 | 대량 처리, 확장성, 캐시 효율, 병렬화 |
| 단점 | 구조 난이도, 디버깅 난이도, 초기 설계 비용 |
| Unreal Component | 기능 객체에 가까움 |
| ECS Component | 순수 데이터에 가까움 |