티스토리 뷰

1. Unity ECS와 DOTS 개요

ECS(Entity Component System)

Unity가 도입한 ECS는 “데이터 지향적 설계”를 통해 기존 객체 지향(OOP)의 한계를 극복하려는 접근입니다.

  • Entity: 단순히 ID 값. 그 자체로는 아무 기능도 없음.
  • Component: Entity에 부착되는 순수 데이터. 예: Position, Velocity, Health.
  • System: 특정 컴포넌트 조합을 가진 엔티티를 찾아 로직을 실행. 예: MovementSystem은 Position과 Velocity를 가진 엔티티에 대해 위치를 갱신.

Hot / Cold 데이터 분리

CPU 성능은 캐시 히트율에 크게 좌우됩니다. ECS는 자주 갱신되는 Hot 데이터와 가끔 접근되는 Cold 데이터를 분리해 캐시 효율을 높입니다.

  • Hot: Position, Rotation, Velocity → 매 프레임 사용.
  • Cold: WeaponType, AIConfig → 조건부로만 접근.

이렇게 하면 불필요한 데이터 로딩 없이 필요한 데이터만 연속적으로 메모리에 올릴 수 있어, SIMD 및 멀티스레딩 효율이 극대화됩니다.

DOTS (Data-Oriented Tech Stack)

Unity의 DOTS는 ECS에 Jobs SystemBurst Compiler를 결합한 기술 스택입니다.

  • Jobs: 멀티스레딩 지원.
  • Burst: CPU 아키텍처에 맞춰 SIMD 최적화된 네이티브 코드 생성.
  • ECS: 데이터 지향적 구조로 확장성 확보.

정리하자면, DOTS는 대규모 데이터를 CPU에서 효율적으로 돌리기 위한 Unity만의 병렬화·최적화 풀 스택입니다.

 


 

2. Unreal Mass Entity Framework: 개념과 명칭

언리얼 엔진도 ECS와 같은 데이터 지향적 모델을 필요로 했습니다. 하지만 이미 Actor/Component 체계가 뿌리 깊게 자리잡고 있었기 때문에 혼란을 피하기 위해 Mass Entity Framework라는 독자적인 이름을 사용합니다.

핵심 개념

  • Fragment: Unity ECS의 Component와 유사. 순수 데이터 단위.
  • Tag: 특정 속성을 나타내는 경량 마커. 조건 분기에 사용.
  • Processor: Fragment 조합에 로직을 실행하는 단위.
  • Trait: Fragment, Tag, Processor를 패키징한 개념.
    • 예: CharacterTrait을 붙이면 이동, 렌더링, 입력 등 관련 Fragment와 Processor가 자동 추가됨.

이러한 구조 덕분에, Mass는 프로그래머뿐 아니라 디자이너도 Trait 기반으로 쉽게 엔티티를 구성할 수 있습니다.


3. Movement 구현 예시

실제 Movement를 처리하는 과정을 예로 들어보겠습니다.

Fragment 정의

struct FTransformFragment : public FMassFragment
{
    FTransform Transform;
};

struct FVelocityFragment : public FMassFragment
{
    FVector Velocity;
};

Processor 구현

UCLASS()
class UMassMovementProcessor : public UMassProcessor
{
    GENERATED_BODY()

public:
    UMassMovementProcessor()
    {
        ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    }

protected:
    virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override
    {
        auto TransformList = Context.GetMutableFragmentView<FTransformFragment>();
        auto VelocityList  = Context.GetFragmentView<FVelocityFragment>();

        for (int32 i = 0; i < Context.GetNumEntities(); i++)
        {
            TransformList[i].Transform.AddToTranslation(
                VelocityList[i].Velocity * Context.GetDeltaTimeSeconds()
            );
        }
    }
};

SIMD 적용 예시

#include <immintrin.h> // AVX Intrinsics

UCLASS()
class UMassSIMDGravityProcessor : public UMassProcessor
{
    GENERATED_BODY()

public:
    UMassSIMDGravityProcessor()
    {
        ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    }

protected:
    virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override
    {
        auto Velocities = Context.GetMutableFragmentView<FVelocityFragment>();
        const int32 NumEntities = Context.GetNumEntities();

        const float DeltaTime = Context.GetDeltaTimeSeconds();
        __m128 Gravity = _mm_set_ps(0.f, 0.f, -980.f * DeltaTime, 0.f); // [X, Y, Z, Padding]

        for (int32 i = 0; i < NumEntities; i += 4) // 4개 단위로 SIMD 처리
        {
            // Velocities[i] ~ Velocities[i+3]를 로드
            __m128 vx = _mm_loadu_ps(&Velocities[i + 0].Velocity.X);
            __m128 vy = _mm_loadu_ps(&Velocities[i + 1].Velocity.X);
            __m128 vz = _mm_loadu_ps(&Velocities[i + 2].Velocity.X);
            __m128 vw = _mm_loadu_ps(&Velocities[i + 3].Velocity.X);

            // Z 축에만 중력 적용 (X, Y는 그대로)
            vx = _mm_add_ps(vx, Gravity);
            vy = _mm_add_ps(vy, Gravity);
            vz = _mm_add_ps(vz, Gravity);
            vw = _mm_add_ps(vw, Gravity);

            // 다시 저장
            _mm_storeu_ps(&Velocities[i + 0].Velocity.X, vx);
            _mm_storeu_ps(&Velocities[i + 1].Velocity.X, vy);
            _mm_storeu_ps(&Velocities[i + 2].Velocity.X, vz);
            _mm_storeu_ps(&Velocities[i + 3].Velocity.X, vw);
        }
    }
};

ISPC 적용 예시 (더 간단한 SIMD 활용법)

// Gravity.ispc
export void ApplyGravity(uniform float3* velocities, uniform int N, uniform float dt)
{
    foreach (i = 0 ... N)
    {
        velocities[i].z += -980.0f * dt; // SIMD 병렬화 자동 적용
    }
}

C++ Processor에서는 ISPC 함수를 호출하기만 하면 됩니다:

extern void ApplyGravity_ispc(FVector* Velocities, int N, float dt);

virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override
{
    auto Velocities = Context.GetMutableFragmentView<FVelocityFragment>();
    ApplyGravity_ispc(&Velocities[0].Velocity, Context.GetNumEntities(), Context.GetDeltaTimeSeconds());
}

실무 팁

  • GetMutableFragmentView를 사용하면 성능 오버헤드 없이 대규모 연속 데이터 접근 가능.
  • Processor는 Chunk 단위 병렬 실행되므로, SIMD 최적화 효과가 큼.
  • 필요시 ParallelForEachEntityChunk를 이용해 코어 활용 극대화 가능.

4. Fragment 접근 및 수정 방식

SharedPtr

  • 장점: 데이터 소유권 관리에 유리.
  • 단점: 참조 카운트 연산 비용 때문에 루프 내부에서는 비효율.
  • 사용 예시: 외부 모듈과 데이터 공유, 긴 생명주기를 가진 대형 오브젝트 관리.

ArrayView / ConstArrayView

  • 장점: 오버헤드가 거의 없는 뷰 타입.
  • 단점: 소유권 없음.
  • 사용 예시: Processor 내부에서 대규모 시뮬레이션 처리 시 권장.

결론: 성능 중심 로직 = ArrayView, 메모리 관리 필요 = SharedPtr


 

5. Mass Entity Framework 플러그인

Mass는 다양한 플러그인과 결합해 기능을 확장할 수 있습니다.

  • MassGameplay: 캐릭터, NPC, 일반적인 게임 플레이 지원.
  • MassCrowd: 수천 명 이상의 군중 시뮬레이션.
  • MassTraffic: 차량, 교통 네트워크, 신호등 제어.
  • MassLOD: 엔티티 단위 LOD 최적화.
  • MassReplication: 네트워크 복제 최적화.
  • MassSmartObjects: NPC와 상호작용 가능한 오브젝트 시스템.
  • MassDebug/Visualizer: 시각화 및 디버깅 도구.

이 플러그인들을 조합하면 단순한 시뮬레이션을 넘어 도시 전체 생태계까지 구성할 수 있습니다.


6. Mass가 적합한 게임 장르

Mass는 특히 대규모 단순 객체 처리에서 강력합니다.

  • RTS/전략 게임: 수백~수천 개 유닛 동시 제어.
  • 군중/생태계 시뮬레이션: 도시 인구, 동물 무리, 생태계.
  • 트래픽/레이싱 게임: 도로 네트워크, NPC 차량.
  • 샌드박스/서바이벌: 월드 내 자원, 몬스터, 동물 개체.

실무에서는 Actor + Mass 하이브리드 구조를 쓰는 경우가 많습니다.

  • Mass: 단순하고 대량의 NPC/오브젝트.
  • Actor: 복잡한 개체(보스 몬스터, 플레이어 캐릭터).

7. Mass 내부 구조 이해하기

Mass의 성능 핵심은 Archetype-Chunk 메모리 구조입니다.

  • Archetype: 특정 Fragment 조합을 가진 Entity 그룹.
  • Chunk: 동일 Archetype 엔티티들을 묶어 저장하는 단위.
  • Chunk는 Struct-of-Arrays(SoA) 형식으로 데이터를 저장해 캐시 효율이 뛰어남.
  • Processor는 Query를 통해 대상 Chunk를 찾아내고, 이를 한 번에 처리.

즉, CPU는 연속 메모리에 정렬된 데이터만 읽으므로 캐시 미스가 최소화되고, SIMD 활용률이 높아집니다.


마무리

Unity DOTS와 Unreal Mass는 모두 데이터 지향적 패러다임을 기반으로 하지만, 구현 철학이 다릅니다.

  • Unity: ECS를 “게임 개발의 기본 모델”로 삼고 독립적 기술 스택 구축.
  • Unreal: 기존 Actor/Component 시스템과 공존 가능한 별도 프레임워크로 Mass 개발.

Mass의 강점은 다음과 같습니다.

  1. Trait 기반으로 손쉬운 구성.
  2. 대규모 시뮬레이션에 적합한 고성능 Chunk 구조.
  3. 플러그인 생태계를 통한 확장성.

앞으로 대규모 NPC, 군중, 트래픽, 생태계 시뮬레이션을 구현하고 싶다면, Mass Entity Framework는 반드시 고려해볼 가치가 있는 강력한 도구입니다.

 

참고

https://github.com/Megafunk/MassSample/blob/main/README.md

 

MassSample/README.md at main · Megafunk/MassSample

My understanding of Unreal Engine 5's experimental ECS plugin with a small sample project. - Megafunk/MassSample

github.com

https://contents.premium.naver.com/unrealstudy/unreal/contents/250524153826875lb