게임 개발에서 성능 최적화는 매우 중요한 작업입니다. Unity의 **ECS(Entity Component System)**는 성능을 극대화할 수 있는 강력한 기술로, 특히 DOTS(Data-Oriented Technology Stack)를 활용한 병렬 처리와 메모리 최적화가 핵심입니다. 이번 글에서는 Unity에서 ECS를 활용하여 게임 성능을 최적화하는 실용적인 방법과 함께 실제 게임에서 사용할 수 있는 실전 코드 사례를 소개하겠습니다.

1. 게임 객체 이동 시스템 구현

게임에서 수많은 객체들이 동시에 이동하는 시스템을 구현할 때, 성능을 최적화하는 것이 중요합니다. ECS를 사용하면 여러 객체를 병렬로 처리할 수 있어 성능이 크게 향상됩니다.

using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Burst;
using Unity.Collections;

[BurstCompile] // 성능 최적화를 위한 어트리뷰트
public partial struct MovementSystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        // 초기화 작업 (필요 시)
    }

    public void OnUpdate(ref SystemState state)
    {
        // DeltaTime을 사용해 프레임 독립적인 이동
        float deltaTime = SystemAPI.Time.DeltaTime;

        // 모든 엔티티의 위치를 이동시키는 코드
        SystemAPI
            .Query<RefRW<Translation>, RefRO<MovementSpeed>>()
            .ForEach((ref Translation translation, in MovementSpeed speed) =>
            {
                // X축으로 이동
                translation.Value.x += speed.Value * deltaTime;
            }).ScheduleParallel(); // 병렬 처리로 성능 향상
    }

    public void OnDestroy(ref SystemState state)
    {
        // 종료 시 처리
    }
}

public struct MovementSpeed : IComponentData
{
    public float Value; // 이동 속도
}

이 코드는 MovementSpeed 컴포넌트를 통해 각 객체의 이동 속도를 관리하고, Translation 컴포넌트를 사용하여 객체의 위치를 업데이트합니다. **ScheduleParallel()**을 통해 병렬 처리로 성능을 극대화하고, BurstCompile을 적용하여 성능을 최적화할 수 있습니다.


2. 충돌 감지 시스템

게임에서 자주 사용되는 충돌 감지 시스템을 ECS로 구현한 예시입니다. 이 시스템은 적과 플레이어 또는 장애물 간의 충돌을 처리하는 데 사용될 수 있습니다. 예를 들어, 적들이 플레이어에게 접근할 때 발생하는 충돌을 처리하는 데 적합합니다.

using Unity.Entities;
using Unity.Physics;
using Unity.Transforms;
using Unity.Collections;

[BurstCompile] 
public partial struct CollisionDetectionSystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        // 초기화 작업 (필요 시)
    }

    public void OnUpdate(ref SystemState state)
    {
        // 충돌 감지 로직
        var physicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>();

        SystemAPI
            .Query<RefRO<Translation>, RefRO<PhysicsCollider>>()
            .ForEach((in Translation translation, in PhysicsCollider collider) =>
            {
                // 물리적인 충돌 감지
                // 예: 충돌 범위 내에 다른 물체가 있는지 체크
                if (PhysicsWorldQueries.QueryOverlap(collider, translation.Value, physicsWorld))
                {
                    // 충돌이 발생했을 경우의 처리 로직
                    // 예: 적이 플레이어와 충돌한 경우
                    HandleCollision();
                }
            }).ScheduleParallel(); // 병렬 처리
    }

    public void HandleCollision()
    {
        // 충돌 처리 로직
        // 예: 플레이어의 체력을 감소시키거나 적을 처치
    }

    public void OnDestroy(ref SystemState state)
    {
        // 종료 시 처리
    }
}

이 코드는 PhysicsWorldQueries.QueryOverlap를 사용하여 충돌 감지 범위 내에 물체가 있는지 확인합니다. 충돌이 발생하면, HandleCollision() 함수에서 필요한 처리를 진행합니다. 병렬 처리로 성능을 향상시키며, 여러 엔티티를 동시에 처리할 수 있습니다.


3. AI 적 행동 시스템

AI 적이 플레이어를 추적하거나 이동하는 행동을 ECS로 구현한 예시입니다. 이 시스템은 적들의 이동 경로를 제어하고, 일정한 거리 내에서 플레이어를 추적하는 로직을 포함합니다.

using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Collections;

public partial struct EnemyAI : ISystem
{
    public void OnCreate(ref SystemState state)
    {
        // 초기화 작업
    }

    public void OnUpdate(ref SystemState state)
    {
        // DeltaTime을 사용하여 AI 적의 행동을 시간 기반으로 처리
        float deltaTime = SystemAPI.Time.DeltaTime;

        SystemAPI
            .Query<RefRW<Translation>, RefRO<EnemyAIComponent>>()
            .ForEach((ref Translation translation, in EnemyAIComponent aiComponent) =>
            {
                // 플레이어 위치를 얻기
                float3 playerPosition = GetPlayerPosition();

                // 거리 계산
                float distance = math.distance(translation.Value, playerPosition);

                // 일정 범위 내에 플레이어가 있으면 추적
                if (distance < aiComponent.chaseRange)
                {
                    // 플레이어 쪽으로 이동
                    MoveTowardsPlayer(ref translation, playerPosition, aiComponent.speed, deltaTime);
                }
            }).ScheduleParallel(); // 병렬 처리
    }

    public float3 GetPlayerPosition()
    {
        // 플레이어의 위치를 반환 (실제 코드에서는 플레이어 객체를 찾는 로직이 필요)
        return new float3(0f, 0f, 0f);
    }

    public void MoveTowardsPlayer(ref Translation translation, float3 playerPosition, float speed, float deltaTime)
    {
        // 플레이어 방향으로 이동
        float3 direction = math.normalize(playerPosition - translation.Value);
        translation.Value += direction * speed * deltaTime;
    }

    public void OnDestroy(ref SystemState state)
    {
        // 종료 시 처리
    }
}

public struct EnemyAIComponent : IComponentData
{
    public float chaseRange; // 추적 범위
    public float speed;      // 속도
}

이 코드는 AI 적이 일정 범위 내에서 플레이어를 추적하는 시스템을 구현합니다. EnemyAIComponent는 AI의 추적 범위와 속도를 설정하며, MoveTowardsPlayer() 함수는 적이 플레이어를 향해 이동하도록 합니다. 병렬 처리로 여러 적이 동시에 추적을 수행할 수 있습니다.


4. 게임 성능 최적화와 관리

ECS 시스템을 활용한 성능 최적화의 핵심은 데이터를 구조적으로 관리하고, 필요할 때만 데이터를 처리하는 것입니다. 위의 예시들에서는 병렬 처리와 BurstCompile을 통해 성능을 극대화할 수 있습니다. 또한 IJob 인터페이스를 사용하여 CPU와 GPU 자원을 효율적으로 분배할 수 있습니다.

장점:

  • 병렬 처리: 여러 객체를 동시에 처리할 수 있어 성능이 크게 향상됩니다.
  • 메모리 최적화: 데이터 중심의 설계로 메모리 사용을 최적화할 수 있습니다.
  • 확장성: 게임이 커짐에 따라 ECS 시스템을 손쉽게 확장할 수 있습니다.

단점:

  • 학습 곡선: 기존의 객체 지향 프로그래밍에서 ECS로 전환하는 것이 어려울 수 있습니다.
  • 초기 설정 복잡도: DOTS와 ECS를 처음 적용할 때 초기 설정이 다소 복잡할 수 있습니다.

마무리

Unity에서 ECS는 게임 성능을 획기적으로 향상시킬 수 있는 강력한 도구입니다. 복잡한 게임 로직을 효율적으로 처리하고, 병렬 처리와 메모리 최적화를 통해 게임의 안정성과 성능을 크게 개선할 수 있습니다. 위의 예시 코드들은 실제 게임에서 사용할 수 있는 시스템들로, 이를 바탕으로 ECS를 더 깊이 이해하고 적용할 수 있을 것입니다. ECS를 활용한 성능 최적화는 게임 개발에서 중요한 기술이므로, 이를 마스터하면 보다 효율적이고 안정적인 게임을 만들 수 있습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다