ECS – Component concepts (3)

/posts/ecs-component-concepts-1

/posts/ecs-component-concepts-2

4. 여러 종류의 Component

(6) Chunk components

https://docs.unity3d.com/Packages/[email protected]/manual/components-chunk.html

Chunk Component는 개별 엔터티(Entity)가 아닌 Chunk 단위로 데이터를 저장하는 컴포넌트

즉, Chunk 전체에 대한 공통 데이터를 저장하는 데 사용됨

1) Chunk Component의 주요 특징

https://docs.unity3d.com/Packages/[email protected]/manual/components-chunk-introducing.html

  • Chunk에 데이터를 저장 → 개별 Entity가 아닌 Chunk 자체에 속하는 데이터
  • 빠른 처리 가능 → Chunk 단위로 한 번만 검사하여 성능 최적화
  • 구조적 변경(Structural Change)이 발생하지 않음
  • Shared Component와 비슷하지만, 중복 제거(Deduplication) 없음
  • Unmanaged 타입만 사용 가능

2) Shared Component와 차이점

특징 Shared Component Chunk Component
저장 위치 Chunk 내의 모든 Entity가 같은 값을 공유 Chunk 자체에 값이 저장됨
값 변경 시 새로운 Chunk 로 이동 필요 (구조적 변경 발생) Chunk 내부에서 값 변경 가능 (구조적 변경 없음)
값 공유 방식 같은 값이 있는 Chunk는 중복 제거됨 각 Chunk 가 자신만의 값을 가짐
지원 타입 Managed & Unmanaged 가능 Unmanaged만 가능

Chunk Component는 구조적 변경 없이 특정 Chunk의 데이터를 조작할 때 유용

3) Chunk Component의 사용 예시

Chunk 단위로 바운딩 박스 저장

  • 각 Chunk의 AABB(바운딩 박스)를 저장하여 카메라 시야 안에 있는 Chunk만 처리 가능
  • 최적화 가능:각 Entity를 검사하는 대신 Chunk 단위로 필터링 가능
csharp
using Unity.Entities;
using Unity.Mathematics;

// Chunk 단위로 AABB (Axis-Aligned Bounding Box) 저장
public struct ChunkBounds : IComponentData
{
    public float3 Min;
    public float3 Max;
}

Chunk Component를 추가하는 시스템

  • Chunk 내의 모든 엔터티가 공유하는 바운딩 박스 업데이트 가능
  • Chunk가 카메라 안에 있는 경우만 Entity를 처리하도록 최적화 가능
csharp
using Unity.Entities;
using Unity.Mathematics;

public partial struct ChunkBoundsSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        foreach (var chunk in SystemAPI.QueryBuilder().WithAll<ChunkBounds>().Build().GetChunks(ref state))
        {
            // 예제: 바운딩 박스를 업데이트하는 로직
            chunk.SetChunkComponentData(new ChunkBounds { Min = new float3(-1, -1, -1), Max = new float3(1, 1, 1) });
        }
    }
}

특정 Chunk만 처리하는 시스템

  • Chunk 단위로 화면에 보이는지 확인 후, 보이는 Chunk 만 처리
  • Chunk 내부의 모든 Entity를 개별 검사하지 않고 빠르게 필터링 가능
csharp
using Unity.Entities;
using UnityEngine;

public partial struct ChunkCullingSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (chunkBounds, chunk) in SystemAPI.Query<ChunkBounds>().WithChunkAccess())
        {
            if (IsChunkVisible(chunkBounds))
            {
                Debug.Log("Processing entities in visible chunk");
                // Chunk 안의 Entity 처리 로직
            }
        }
    }

    private bool IsChunkVisible(ChunkBounds bounds)
    {
        // 간단한 AABB 검사 로직 (예제)
        return bounds.Min.x < 10 && bounds.Max.x > -10;
    }
}

4) Chunk Component의 장점과 단점

장점

  • Chunk 단위로 데이터 저장 → 빠른 필터링 가능
  • 구조적 변경이 발생하지 않음 → 성능 최적화
  • 엔터티 개별 검사 없이 Chunk 레벨에서 빠른 판단 가능

단점

  • 각 Chunk마다 별도로 값을 저장하므로 메모리 사용 증가 가능
  • 같은 값이라도 공유되지 않음 (Shared Component처럼 Deduplication 없음)
  • Managed 타입 사용 불가 (Unmanaged 타입만 가능)

5) Chunk Component 활용법

https://docs.unity3d.com/Packages/[email protected]/manual/components-type.html

Chunk Component는 일반적인 IComponentData와는 다른 방식의 API를 사용하여 추가, 제거, 가져오기 및 설정을 수행해야함

특히 EntityManager의 API를 사용할 때 주의

1> Chunk Component 추가 방법

특정 Entity의 Chunk에 Chunk Component 추가

csharp
EntityManager.AddChunkComponentData<ExampleChunkComp>(entity);
  • 특정 Entity가 속한 Chunk에 Chunk Component 추가
  • 이때, Chunk Component는 해당 Chunk의 모든 Entity가 공유하게 됨

2> Chunk Component 조회(Query) 방법

Chunk Component를 포함하는 Chunk를 찾을 때 ComponentType.ChunkComponent<T> 사용

csharp
EntityQuery query = GetEntityQuery(
    typeof(ExampleComponent), 
    ComponentType.ChunkComponent<ExampleChunkComp>() // Chunk Component 쿼리
);
NativeArray<ArchetypeChunk> chunks = query.ToArchetypeChunkArray(Allocator.Temp);
  • ExampleComponent가 포함된 Chunk 중에서 ExampleChunkComp도 있는 Chunk를 찾음

3> Chunk Component 데이터 가져오기 & 설정하기

특정 Chunk의 Chunk Component 값 설정

csharp
EntityManager.SetChunkComponentData<ExampleChunkComp>(
    chunks[0], 
    new ExampleChunkComp { Value = 6 }
);
  • 첫 번째 Chunk에 대해 ExampleChunkComp 값을 6으로 설정

특정 Chunk의 Chunk Component 값 가져오기

csharp
ExampleChunkComp exampleChunkComp = EntityManager.GetChunkComponentData<ExampleChunkComp>(chunks[0]);
Debug.Log(exampleChunkComp.Value); // 6 출력
  • 첫 번째 Chunk의 ExampleChunkComp 값을 가져옴

4> Entity를 통해 Chunk Component 값 가져오기 & 설정하기

특정 Entity가 속한Chunk의 Chunk Component 값을 변경

csharp
EntityManager.SetChunkComponentData<MyChunkComp>(
    entity, 
    new MyChunkComp { Value = 6 }
);
  • 특정 Entity가 속한 Chunk의 MyChunkComp 값을 6으로 설정

특정 Entity가 속한 Chunk의 Chunk Component 값을 가져오기

csharp
MyChunkComp myChunkComp = EntityManager.GetChunkComponentData<MyChunkComp>(entity);
Debug.Log(myChunkComp.Value); // 6 출력
  • 해당 Entity가 속한 Chunk의 MyChunkComp 값을 가져옴

5> Job에서 Chunk Component 사용하기

EntityManager는 Job 내부에서 사용할 수 없으므로 ComponentTypeHandle<T>을 활용해야 한다!

IJobChunk에서 Chunk Component 접근하기

csharp
struct MyJob : IJobChunk
{
    public ComponentTypeHandle<ExampleChunkComponent> ExampleChunkCompHandle;

    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    {
        // 청크의 Chunk Component 데이터 가져오기
        ExampleChunkComponent myChunkComp = chunk.GetChunkComponentData(ExampleChunkCompHandle);

        // 청크의 Chunk Component 데이터 설정
        chunk.SetChunkComponentData(ExampleChunkCompHandle, new ExampleChunkComponent { Value = 7 });
    }
}
  • IJobChunk 내부에서는 chunk.GetChunkComponentData()와 chunk.SetChunkComponentData()를 사용
  • EntityManager 없이 직접 Chunk 데이터를 조작 가능

6> Chunk Component 관련 주의사항

  • Chunk Component는 Chunk에 속하지만, 추가/제거 시 Entity의 아키타입이 변경되어 구조적 변경(Structural Change)이 발생
  • 새로 생성된 Chunk Component는 기본값(Default)으로 초기화됨
  • 읽기 전용으로 사용할 경우 ComponentType.ChunkComponentReadOnly<T>를 사용하면 Job 스케줄링 최적화 가능

정리

  • Chunk Component는 특정 Chunk 자체의 상태를 저장하는데 유용
  • EntityManager의 AddChunkComponentData(), SetChunkComponentData(), GetChunkComponentData()를 사용하여 접근
  • Job에서는 ComponentTypeHandle<T>을 사용하여 접근해야 함
  • 구조적 변경이 발생하지만, 개별 Entity가 아닌 Chunk 단위의 데이터를 관리하는 데 최적

Chunk 단위 데이터 최적화 및 효율적인 필터링에 적합

(7) Enableable Component

https://docs.unity3d.com/Packages/[email protected]/manual/components-enableable-use.html

Enableable Component은 Entity의 특정 컴포넌트를 활성화/비활성화할 수 있도록 하는 기능

이는 구조적 변경(Structural Change) 없이 컴포넌트의 상태를 빠르게 변경할 수 있어, 자주 변하는 상태(State)를 다룰 때 유용

1) Enableable Component의 특징

  • IEnableableComponent을 구현한 IComponentData 또는 IBufferElementData만 사용 가능
  • 활성화/비활성화해도 아키타입(Archetype)이 변경되지 않음 → 구조적 변경 없음
  • 값을 그대로 유지한 채 비활성화 가능
  • 비활성화된 컴포넌트는 EntityQuery에서 존재하지 않는 것처럼 동작
  • 멀티스레드(Job System)에서 안전하게 활성화/비활성화 가능
  • 기본적으로 모든 Enableable Component는 활성화된 상태로 생성됨

2) Enableable Component 생성 방법

Enableable Component를 만들려면 IEnableableComponent 인터페이스를 구현해야 한다.

csharp
using Unity.Entities;

public struct Health : IComponentData, IEnableableComponent
{
    public int Value;
}
  • IEnableableComponent을 추가하면 해당 컴포넌트는 활성화/비활성화 가능
  • 일반적인 IComponentData와 동일하게 사용 가능

3) Enableable Component 활성화/비활성화 방법

특정 Entity의 Enableable Component 상태 확인

csharp
bool isEnabled = EntityManager.IsComponentEnabled<Health>(entity);
Debug.Log(isEnabled);  // true (기본적으로 활성화됨)
  • IsComponentEnabled<T>(Entity)을 사용하여 현재 컴포넌트 상태 확인 가능

특정 Entity의 Enableable Component 비활성화

csharp
EntityManager.SetComponentEnabled<Health>(entity, false);
  • SetComponentEnabled<T>(Entity, bool)을 사용하여 컴포넌트 활성화/비활성화
  • 컴포넌트를 비활성화해도 값은 그대로 유지됨

비활성화된 컴포넌트의 값 가져오기

csharp
Health h = EntityManager.GetComponentData<Health>(entity);
Debug.Log(h.Value);  // 비활성화 상태여도 값은 유지됨
  • 비활성화 상태에서도 값은 유지됨
  • 단, EntityQuery에서는 존재하지 않는 것처럼 동작

4) Enableable Component와 EntityQuery

Enableable Component가 비활성화된 경우, 기본적으로 Query에서 제외된다.

csharp
EntityQuery query = GetEntityQuery(typeof(Health), typeof(Translation));
  • Health가 비활성화된 Entity는 이 쿼리에 포함되지 않음
  • Health가 활성화된 Entity만 결과에 포함됨

Enableable Component가 비활성화된 상태에서도 포함시키는 방법

csharp
EntityQuery queryIgnoreEnableable = GetEntityQuery(new EntityQueryDesc
{
    All = new ComponentType[] { typeof(Health), typeof(Translation) },
    Options = EntityQueryOptions.IgnoreComponentEnabledState
});
  • EntityQueryOptions.IgnoreComponentEnabledState를 사용하면 비활성화된 컴포넌트도 포함
  • 구조적 변경이 발생한 경우에만 동작이 변경되므로 더 효율적

5) Job System에서 Enableable Component 사용하기

Enableable Component는 멀티스레드(Job System)에서 안전하게 활성화/비활성화 가능하다.

Job 내부에서 Enableable Component 사용하기

csharp
struct EnableableJob : IJobEntity
{
    public ComponentLookup<Health> HealthLookup;

    public void Execute(Entity entity)
    {
        if (HealthLookup.IsComponentEnabled(entity))
        {
            // Health가 활성화된 경우
            Health h = HealthLookup[entity];
            h.Value += 10;
            HealthLookup[entity] = h;
        }
    }
}
  • ComponentLookup<T>를 사용하여 Job 내부에서 안전하게 활성화/비활성화 확인 가능
  • IsComponentEnabled<T>(Entity)를 활용하여 조건부 실행 가능

6) Enableable Component 관련 주의사항

  • 비활성화된 컴포넌트는 EntityQuery에서 기본적으로 존재하지 않는 것으로 간주됨
  • 멀티스레드 환경에서 다른 Job이 같은 컴포넌트를 변경할 경우, Race Condition 발생 가능 (경쟁)
  • EntityQueryOptions.IgnoreComponentEnabledState를 사용하면 비활성화 상태도 포함 가능
  • 구조적 변경을 피하기 위해 자주 변경되는 상태(State)는 Enableable Component를 활용하는 것이 유리

정리

  • Enableable Component는 IEnableableComponent을 구현한 IComponentData 또는 IBufferElementData만 사용 가능
  • 비활성화해도 값은 유지되지만, 기본적으로 EntityQuery에서 제외됨
  • IsComponentEnabled<T>(Entity)와 SetComponentEnabled<T>(Entity, bool)을 사용하여 활성화/비활성화 가능
  • 구조적 변경 없이 상태(State)를 관리할 수 있어 성능 최적화에 유리
  • Job System에서도 ComponentLookup<T>를 사용하여 안전하게 활용 가능

상태 변화가 자주 발생하는 경우 Enableable Component를 사용하면 최적화에 도움

(8) Singleton Component

https://docs.unity3d.com/Packages/[email protected]/manual/components-singleton.html

Singleton Component는 특정 월드(World)에서 오직 하나의 인스턴스만 존재하는 컴포넌트를 의미한다.

즉, 한 개의 Entity만 특정 컴포넌트를 가지고 있으면 Singleton Component로 간주된다.

1) Singleton Component의 특징

  • 특정 월드에서 단 하나의 인스턴스만 존재
  • 다른 Entity에 같은 컴포넌트를 추가하면 더 이상 Singleton이 아님
  • 다른 월드에 존재하는 동일한 컴포넌트는 별도로 관리됨 (다른 월드의 Singleton 상태에 영향 없음)
  • System에서 쉽게 접근할 수 있도록 Singleton 전용 API 제공
  • 서버 기반 아키텍처에서 특정 클라이언트 데이터를 관리할 때 유용

2) Singleton Component 생성 방법

Singleton Component는 일반적인 IComponentData와 동일하게 생성하지만, 오직 하나의 Entity만 해당 컴포넌트를 가지도록 관리해야 한다.

csharp
using Unity.Entities;

public struct GameSettings : IComponentData
{
    public float Gravity;
}
  • 일반적인 IComponentData처럼 정의 가능

Singleton Component 추가

csharp
EntityManager.CreateSingleton<GameSettings>();
  • CreateSingleton<T>()을 사용하면 자동으로 단 하나의 Entity에만 추가

3) Singleton Component 접근 방법

Singleton API를 사용하면 특정 Entity를 직접 찾지 않아도 단일 인스턴스를 가져올 수 있음.

Singleton Entity 가져오기

csharp
Entity singletonEntity = EntityManager.GetSingletonEntity<GameSettings>();
  • GetSingletonEntity<T>()을 사용하면 Singleton을 가진 엔터티 직접 가져오기 가능

Singleton 값 가져오기

csharp
GameSettings settings = EntityManager.GetSingleton<GameSettings>();
Debug.Log(settings.Gravity);
  • GetSingleton<T>()을 사용하면 컴포넌트 데이터 직접 가져오기 가능

Singleton 값 변경하기

csharp
GameSettings settings = EntityManager.GetSingleton<GameSettings>();
settings.Gravity = 9.8f;
EntityManager.SetSingleton(settings);
  • SetSingleton<T>()을 사용하여 값 수정 가능

Singleton 여부 확인

csharp
bool exists = EntityManager.HasSingleton<GameSettings>();
Debug.Log(exists);  // true or false
  • HasSingleton<T>()을 사용하여 Singleton이 존재하는지 확인 가능

4) Singleton API 비교

API 설명
CreateSingleton<T>() Singleton Component 생성
GetSingletonEntity<T>() Singleton을 포함한 엔터티 가져오기
GetSingleton<T>() Singleton 데이터 가져오기
SetSingleton<T>() Singleton 데이터 설정하기
HasSingleton<T>() Singleton이 존재하는지 확인
TryGetSingleton<T>(out T value) Singleton을 가져오되, 없을 경우 안전하게 처리
GetSingletonRW<T>() Read/Write 가능한 참조 반환 (주의 필요)

5) Singleton Component와 SystemAPI

Singleton Component는 SystemAPI에서도 편리하게 접근 가능

SystemAPI를 이용한 Singleton 접근

csharp
public partial struct GameSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        GameSettings settings = SystemAPI.GetSingleton<GameSettings>();
        Debug.Log(settings.Gravity);
    }
}
  • SystemAPI.GetSingleton<T>()을 사용하면 더 간결한 코드 가능

6) Singleton 사용 시 주의점

Singleton API는 자동으로 Job Dependency를 해결하지 않음

  • EntityManager.GetComponentData<T>()는 자동으로 Job을 완료하지만, EntityManager.GetSingleton<T>()은 자동으로 Job을 완료하지 않음
  • Job이 실행 중일 때 Singleton에 접근하면 오류 발생 가능
  • 해결 방법: EntityManager.CompleteDependencyBeforeRO() 또는 CompleteDependencyBeforeRW() 사용
csharp
EntityManager.CompleteDependencyBeforeRO<GameSettings>();  
GameSettings settings = EntityManager.GetSingleton<GameSettings>();

GetSingletonRW<T>() 사용 시 주의

  • GetSingletonRW<T>()은 참조(Reference)를 반환하므로, Job과 동시에 접근하면 Race Condition 발생 가능
  • 안전한 사용 방법
    • 네이티브 컨테이너(NativeContainer) 내부 데이터만 수정
    • Jobs Debugger에서 오류가 없는지 반드시 확인

정리

  • Singleton Component는 특정 월드에서 단 하나만 존재하는 컴포넌트
  • SystemAPI 및 EntityManager를 통해 쉽게 접근 가능
  • 구조적 변경 없이 특정 데이터를 쉽게 관리할 수 있음
  • Job System과 함께 사용할 때는 Dependency 관리 필요
  • 서버 기반 시스템이나 특정 설정 값을 관리할 때 매우 유용

GameSettings, PlayerController, ServerTimestamp 등의 전역 설정을 관리할 때 Singleton을 사용하면 편리

Namespace Method
EntityManager CreateSingleton
EntityQuery GetSingletonEntity
GetSingleton
GetSingletonRW
TryGetSingleton
HasSingleton
TryGetSingletonBuffer
TryGetSingletonEntity
GetSingletonBuffer
SetSingleton
SystemAPI GetSingletonEntity
GetSingleton
GetSingletonRW
TryGetSingleton
HasSingleton
TryGetSingletonBuffer
TryGetSingletonEntity
GetSingletonBuffer
SetSingleton