콘텐츠로 이동

Unity DOTS ECS 심화

DOTS(Data-Oriented Technology Stack) ECS는 데이터(Component)와 로직(System)을 분리해 캐시 친화적 메모리 레이아웃을 구성합니다.

  • Entity: 고유 ID만 가진 경량 식별자
  • Component: 순수 데이터 구조체 (IComponentData)
  • System: 컴포넌트를 처리하는 로직 단위
  • Archetype: 동일한 컴포넌트 조합 → 같은 Chunk에 연속 배치
// 작고 단순하게 — 하나의 관심사만
public struct Health : IComponentData {
public float Value;
public float Max;
}
public struct Velocity : IComponentData {
public float3 Linear;
public float3 Angular;
}
// 태그 컴포넌트: 데이터 없이 분류만
public struct IsEnemy : IComponentData {}
public struct IsDead : IComponentData {}

컴포넌트는 Blittable 타입(포인터·참조 없음)이어야 Chunk에 연속 배치됩니다.

// ISystem: 비관리(unmanaged), 성능 최우선
public partial struct MovementSystem : ISystem {
public void OnUpdate(ref SystemState state) {
foreach (var (velocity, transform) in
SystemAPI.Query<RefRO<Velocity>, RefRW<LocalTransform>>()) {
transform.ValueRW.Position += velocity.ValueRO.Linear * SystemAPI.Time.DeltaTime;
}
}
}
// SystemBase: 관리(managed), 코루틴/MonoBehaviour 연동 시
public partial class SpawnSystem : SystemBase {
protected override void OnUpdate() {
// Entities.ForEach 또는 Job 활용
}
}

ISystem은 Burst 컴파일 가능하고 오버헤드가 낮습니다. 새 코드는 ISystem을 권장합니다.

[BurstCompile]
public partial struct PhysicsJob : IJobEntity {
public float DeltaTime;
public void Execute(ref LocalTransform transform, in Velocity vel) {
transform.Position += vel.Linear * DeltaTime;
}
}
public partial struct PhysicsSystem : ISystem {
[BurstCompile]
public void OnUpdate(ref SystemState state) {
var job = new PhysicsJob { DeltaTime = SystemAPI.Time.DeltaTime };
job.ScheduleParallel();
}
}

IJobEntity는 소스 제너레이터가 Archetype 쿼리와 Chunk 순회 코드를 자동 생성합니다.

EntityQuery _query;
public void OnCreate(ref SystemState state) {
_query = SystemAPI.QueryBuilder()
.WithAll<Health, Velocity>()
.WithNone<IsDead>()
.WithChangeFilter<Health>() // Health가 변경된 Chunk만
.Build();
}

WithChangeFilter는 변경된 Chunk만 처리해 불필요한 순회를 줄입니다.

씬의 MonoBehaviour를 ECS 데이터로 변환합니다.

// Authoring 컴포넌트 (MonoBehaviour)
public class HealthAuthoring : MonoBehaviour {
public float MaxHealth = 100f;
}
// Baker: 에디터/빌드 시 변환
class HealthBaker : Baker<HealthAuthoring> {
public override void Bake(HealthAuthoring authoring) {
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new Health {
Value = authoring.MaxHealth,
Max = authoring.MaxHealth,
});
}
}
// 같은 Material을 공유하는 Entity를 동일 Chunk에 모음
public struct RenderMaterial : ISharedComponentData, IEquatable<RenderMaterial> {
public Material Value;
public bool Equals(RenderMaterial other) => Value == other.Value;
public override int GetHashCode() => Value.GetHashCode();
}

동일 ISharedComponentData 값을 가진 Entity는 같은 Chunk에 배치되어 GPU 인스턴싱 배치 효율이 높아집니다.

  • IComponentData는 작고 Blittable하게 설계해 Chunk 연속성 유지
  • 신규 시스템은 ISystem + IJobEntity + [BurstCompile] 조합 권장
  • EntityQueryWithChangeFilter로 불필요한 순회 차단
  • Baking으로 MonoBehaviour 기반 씬 설계와 ECS 런타임 분리
  • ISharedComponentData로 동일 재질 오브젝트를 같은 Chunk에 모아 인스턴싱 최적화