Skip to content

유니티 ECS와 GameObject 연동 방법

Unity ECS (Entity Component System) - GameObjects와의 통합 및 마이그레이션

Section titled “Unity ECS (Entity Component System) - GameObjects와의 통합 및 마이그레이션”

Unity 6.4부터 Entity Component System(ECS)가 코어 엔진 패키지로 통합되면서, 기존의 GameObject 기반 프로젝트와 ECS를 함께 사용할 수 있게 되었습니다. 이 문서는 ECS의 핵심 개념, GameObject와의 통합 방법, 그리고 실무 적용 전략을 다룹니다.


ECS는 데이터 지향적 설계(Data-Oriented Design) 패턴으로, 세 가지 핵심 개념으로 구성됩니다:

  • Entity (엔티티): 게임 객체의 정체성을 나타내는 고유 ID. 데이터 컨테이너 역할을 하며, 자체적으로 동작을 가지지 않습니다.
  • Component (컴포넌트): 엔티티에 붙어있는 데이터 구조체. ECS 컴포넌트는 객체지향의 MonoBehaviour와 달리, 순수한 데이터만 저장합니다.
  • System (시스템): 컴포넌트의 데이터를 읽고 처리하는 로직. 특정 컴포넌트 조합을 가진 엔티티들을 쿼리하여 배치 처리합니다.
항목GameObjectECS Entity
구조객체지향 (OOP)데이터 지향 (Data-Oriented)
MonoBehaviour데이터 + 동작 혼재데이터만 보유
처리 방식개별 Update() 호출배치 처리 (시스템)
메모리 배치분산선형 (Cache-friendly)
성능중소 규모대규모 처리에 최적
  1. 성능 향상: 선형 메모리 배치로 CPU 캐시 효율성 증대
  2. 확장성: 수천 개의 엔티티도 효율적으로 처리
  3. 결정론적 실행: 시스템 실행 순서가 명확하여 디버깅이 용이
  4. 병렬 처리: C# Job System과 Burst Compiler를 활용한 고성능 멀티스레딩
  5. 메모리 효율: 불필요한 메모리 오버헤드 제거

기존의 GameObject 기반 프로젝트를 ECS로 마이그레이션하는 가장 실용적인 방식은 Conversion 시스템입니다:

// GameObject를 Editor에서 설계한 후, 런타임에 ECS 엔티티로 변환
// 이 과정은 GameObjectConversionSystem이 자동으로 처리

Conversion 과정:

  1. Scene에 GameObject를 배치
  2. 특정 Component를 GameObject에 추가 (예: IConvertGameObjectToEntity)
  3. 게임 실행 시 ConversionSystem이 자동으로 GameObject를 ECS 엔티티로 변환
  4. 변환된 엔티티는 ECS 시스템에 의해 관리됨

2.2 Hybrid ECS (GameObject + Entity 혼용)

Section titled “2.2 Hybrid ECS (GameObject + Entity 혼용)”

Unity 6.4부터는 하이브리드 모드에서 GameObject와 ECS 엔티티를 동시에 사용할 수 있습니다:

using Unity.Entities;
using UnityEngine;
// GameObject는 그대로 사용
public class PlayerController : MonoBehaviour
{
void Update()
{
// 기존 GameObject 로직
}
}
// 동시에 ECS 엔티티도 사용 가능
public partial struct PlayerMovementSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// ECS 기반 대량 데이터 처리
}
}

이점:

  • 기존 코드 유지 가능
  • 성능이 필요한 부분부터 ECS로 마이그레이션
  • 점진적 전환 가능

2.3 Unified Transforms (통합 트랜스폼)

Section titled “2.3 Unified Transforms (통합 트랜스폼)”

Unity 6.4의 큰 변화는 ECS와 GameObject의 트랜스폼을 통합하는 것입니다:

  • ECS 엔티티의 LocalTransform 컴포넌트와 GameObject의 Transform 컴포넌트가 동기화
  • GameObject 계층 구조를 ECS 엔티티 계층으로 직접 매핑 가능
  • Parent 컴포넌트를 통한 엔티티 간 부모-자식 관계 구현

LocalTransform은 엔티티의 상대적 위치, 회전, 스케일을 정의합니다:

using Unity.Transforms;
using Unity.Mathematics;
// LocalTransform 생성
LocalTransform transform = LocalTransform.FromPosition(1, 2, 3);
// 속성에 직접 접근
float3 position = transform.Position;
quaternion rotation = transform.Rotation;
float scale = transform.Scale;
// 변환 적용 (함수형 - 새로운 값 반환)
transform = transform.RotateZ(math.radians(45f));
transform.Position += math.up();

중요: ECS 트랜스폼은 값 타입이므로, 변경 후 반드시 할당해야 합니다.

엔티티 계층을 구성하려면:

using Unity.Entities;
using Unity.Transforms;
// 자식 엔티티에 Parent 컴포넌트 추가
ecb.AddComponent(childEntity, new Parent { Value = parentEntity });
// ParentSystem이 자동으로 실행되어 계층 관계 유지
// 주의: 계층이 크면 성능이 저하될 수 있음

성능 최적화 팁:

  • 너무 깊은 계층 피하기
  • 정적 엔티티는 플래그로 표시하여 캐시 효율성 증대
  • 여러 개의 루트 레벨 트랜스폼으로 분산
using Unity.Entities;
using Unity.Transforms;
using Unity.Burst;
[BurstCompile]
public partial struct MoveSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new MoveJob().ScheduleParallel();
}
[BurstCompile]
private partial struct MoveJob : IJobEntity
{
public void Execute(ref LocalTransform transform)
{
// 모든 LocalTransform 컴포넌트를 병렬로 처리
transform.Position.y += 1.0f;
}
}
}

4. GameObject에서 ECS로의 마이그레이션 가이드

Section titled “4. GameObject에서 ECS로의 마이그레이션 가이드”

Phase 1: 분석 및 계획

  1. 기존 MonoBehaviour의 역할 분석
  2. ECS 컴포넌트 구조 설계
  3. 성능 이득이 큰 부분 우선 선정

Phase 2: 하이브리드 모드 도입

  1. ECS 패키지 설치
  2. 핵심 로직부터 ECS 시스템으로 재작성
  3. GameObject와 ECS 엔티티 간 상호작용 구현

Phase 3: 점진적 전환

  1. 한 번에 하나의 기능 단위로 전환
  2. 각 단계마다 테스트
  3. 성능 프로파일링으로 이득 확인

Phase 4: 완전 ECS 전환

  1. 모든 핵심 로직을 ECS 기반으로 구현
  2. GameObject 의존성 제거
  3. 성능 최적화

4.2 일반적인 마이그레이션 패턴

Section titled “4.2 일반적인 마이그레이션 패턴”

MonoBehaviour 로직 → ECS

// Before: MonoBehaviour 기반
public class EnemyController : MonoBehaviour
{
private float speed = 5f;
void Update()
{
transform.position += Vector3.forward * speed * Time.deltaTime;
}
}
// After: ECS 기반
public struct EnemySpeed : IComponentData
{
public float Value;
}
[BurstCompile]
public partial struct EnemyMovementSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
foreach (var (transform, speed) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<EnemySpeed>>())
{
transform.ValueRW.Position += new float3(0, 0, 1) * speed.ValueRO.Value * deltaTime;
}
}
}

5. 실전 예제: 플레이어와 적 시스템

Section titled “5. 실전 예제: 플레이어와 적 시스템”
using Unity.Entities;
using Unity.Mathematics;
// 플레이어 태그
public struct Player : IComponentData {}
// 적 태그
public struct Enemy : IComponentData {}
// 속도 데이터
public struct Velocity : IComponentData
{
public float3 Value;
}
// 체력 데이터
public struct Health : IComponentData
{
public int CurrentHP;
public int MaxHP;
}
// 공격력 데이터
public struct Damage : IComponentData
{
public float Value;
}
using Unity.Burst;
using Unity.Entities;
using Unity.Transforms;
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct MovementSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
new MovementJob { DeltaTime = deltaTime }
.ScheduleParallel();
}
[BurstCompile]
private partial struct MovementJob : IJobEntity
{
public float DeltaTime;
private void Execute(ref LocalTransform transform, in Velocity velocity)
{
transform.Position += velocity.Value * DeltaTime;
}
}
}
using Unity.Burst;
using Unity.Entities;
[BurstCompile]
public partial struct HealthSystem : ISystem
{
private EntityQuery destroyQuery;
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (health, entity) in
SystemAPI.Query<RefRW<Health>>().WithEntityAccess())
{
if (health.ValueRO.CurrentHP <= 0)
{
ecb.DestroyEntity(entity);
}
}
ecb.Playback(state.EntityManager);
}
}

  1. 컴포넌트 크기 최소화: 필요한 데이터만 저장
  2. 캐시 효율성: 자주 함께 접근하는 컴포넌트를 가까이 배치
  3. 정적 컴포넌트 표시: IEnableableComponent로 활성/비활성 관리
// 시스템 그룹을 활용한 실행 순서 제어
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct MySystem : ISystem { }
// [BurstCompile] 특성으로 성능 극대화
[BurstCompile]
public partial struct OptimizedSystem : ISystem
{
// Burst로 컴파일되는 C# 코드
// C# Job System 사용 가능
}

  1. 변환 후 할당 누락: transform.Position 변경 후 할당 필수
  2. 부모 컴포넌트 누락: 계층 구조가 필요하면 반드시 Parent 추가
  3. 시스템 실행 순서 문제: 의존성이 있는 시스템들은 올바른 순서로 실행되어야 함
  4. 메모리 누수: EntityCommandBuffer를 제때 해제하지 않으면 누수 발생
// Entity 디버깅 출력
Debug.Log($"Entity: {entity}");
// 컴포넌트 확인
if (SystemAPI.HasComponent<Health>(entity))
{
var health = SystemAPI.GetComponent<Health>(entity);
Debug.Log($"Health: {health.CurrentHP}/{health.MaxHP}");
}

  • Unity Learn: “Basics of DOTS: Jobs and Entities”
  • Entity Component System Samples (GitHub)
  • HelloCube 튜토리얼
  • Unity Discussions - Entities 카테고리
  • Official Unity Discord - DOTS 채널

Unity 6.4의 ECS 코어 패키지 통합으로 GameObject와 ECS를 함께 사용할 수 있게 되었습니다. 기존의 객체지향 게임 개발 방식에서 데이터 지향적 설계로 점진적으로 전환할 수 있으며, 이를 통해 성능, 확장성, 그리고 유지보수성을 크게 향상시킬 수 있습니다.

성능이 중요한 프로젝트라면 ECS 도입을 강력히 권장하며, 단계적 마이그레이션을 통해 리스크를 최소화할 수 있습니다.