엔티티 활용: 탱크

Tutorial

intermediate

+10XP

30 mins

9

Unity Technologies

엔티티 활용: 탱크

이 튜토리얼에서는 잡과 엔티티에 대해 새롭게 배운 지식을 활용하여 샘플 프로젝트에서 게임의 일반적인 기능을 구현해 보겠습니다. 목표는 회전하는 포탑에서 포탄을 발사하면서 이동하는 탱크를 생성하는 것입니다. 탱크는 포탄에 맞으면 파괴됩니다. 플레이어는 탱크 하나의 움직임을 제어합니다.

이 튜토리얼에서 배울 내용은 다음과 같습니다.

  • 코드를 사용하여 엔티티를 무작위로 이동시킵니다.
  • 코드를 사용하여 프리팹에서 엔티티를 생성합니다.
  • 코드를 사용하여 생성된 엔티티의 동작을 제어합니다.
  • 엔티티 프로젝트에 사용자 입력 시스템을 추가합니다.

Materials

Languages available:

1. 개요

이 튜토리얼은 HelloCube 및 잡 시스템 튜토리얼의 후속편으로 제작되었으며 Entities 패키지의 핵심적인 요소를 설명합니다.

이 튜토리얼의 목표는 회전하는 포탑에서 포탄을 발사하면서 이동하는 탱크를 생성하는 것입니다. 탱크는 포탄에 맞으면 파괴됩니다. 플레이어는 탱크 하나의 움직임을 제어합니다.

튜토리얼에서는 엔티티, 시스템, 엔티티 쿼리, 엔티티 커맨드 버퍼, 엔티티 잡(IJobEntity), 베이킹, 하위 씬 등 몇 가지 DOTS 기능을 사용합니다.

(이 튜토리얼은 Entities 샘플 저장소에 있는 탱크 튜토리얼의 배리에이션입니다.)

2. 시작하기 전에

이 튜토리얼에서는 여러분이 C#과 게임 오브젝트에 대한 기본 지식이 있고 HelloCube 튜토리얼(엔티티 생성, 하위 씬 같은 엔티티의 기본 사항을 다룸)과 잡 튜토리얼을 완료했다고 가정합니다.

Unity 프로젝트 설정

이 튜토리얼을 위한 Unity 프로젝트를 설정하는 방법은 다음과 같습니다.

1. 3D (URP) 템플릿을 사용하여 새 Unity 프로젝트를 생성합니다.

2. 패키지 관리자를 통해 Entities Graphics 패키지를 설치합니다.

엔티티를 렌더링하려면 Entities Graphics 패키지가 필요하며 이 패키지를 설치하면 종속 관계인 Entities 패키지도 자동으로 설치됩니다.

3. 움직이지 않는 단일 탱크 생성

먼저 이 튜토리얼에서 사용할 탱크를 생성합니다. Tank 게임 오브젝트를 생성하는 방법은 다음과 같습니다.

1. Main 씬에서 새 하위 씬을 생성합니다(이 과정에 대해 다시 알아보려면 HelloCube 튜토리얼을 확인하세요). 하위 씬의 이름은 중요하지 않으므로 기본 이름인 ‘New Sub Scene’도 괜찮습니다.

2. 하위 씬에서 3D 큐브 게임 오브젝트를 생성하고 이름을 ‘Tank’로 지정합니다.

3. Tank의 자식 게임 오브젝트로 3D 구체 게임 오브젝트를 생성하고 이름을 ‘Turret’으로 지정합니다.

4. Turret 게임 오브젝트의 위치를 (0, 0.5, 0)으로 설정합니다.

5. Turret의 자식 게임 오브젝트로 3D 실린더 게임 오브젝트를 생성하고 이름을 ‘Cannon’으로 지정합니다.

6. Cannon 게임 오브젝트의 스케일을 (0.2, 0.5, 0.2)로 설정합니다.

7. Cannon 게임 오브젝트의 위치를 (0, 0.5, 0)으로 설정합니다.

8. Turret 게임 오브젝트의 회전을 (45, 0, 0)으로 설정합니다.

그러면 탱크가 다음과 같은 모양이 됩니다.

HelloCube 튜토리얼에서 설명한 바와 같이, 베이킹은 이러한 게임 오브젝트 각각에 하나의 엔티티를 직렬화하고 각 엔티티에 TransformRendering 컴포넌트를 부여합니다. 런타임 시 씬이 로드되면 게임 오브젝트가 아닌 엔티티가 로드되므로 플레이 모드에서 렌더링된 탱크는 3개의 엔티티로 구성됩니다.

4. 무작위 곡선 경로를 따라 탱크 이동

먼저 베이킹의 각 탱크 엔티티에 Tank 컴포넌트를 추가해 보겠습니다(HelloCube 튜토리얼에서 설명한 바와 같음). TankAuthoring.cs라는 새 스크립트 파일을 생성하고 다음 코드를 추가합니다.

using Unity.Entities;
using UnityEngine;

public class TankAuthoring : MonoBehaviour
{
    public GameObject Turret;
    public GameObject Cannon;
    
    class Baker : Baker<TankAuthoring>
    {
        public override void Bake(TankAuthoring authoring)
        {
            // GetEntity는 게임 오브젝트에서 베이크된 엔티티를 반환합니다.
            var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
            AddComponent(entity, new Tank
            {
                Turret = GetEntity(authoring.Turret, TransformUsageFlags.Dynamic),
                Cannon = GetEntity(authoring.Cannon, TransformUsageFlags.Dynamic)
            });
        }
    }
}

// 모든 탱크의 루트 엔티티에 추가될 컴포넌트입니다.
public struct Tank : IComponentData
{
    public Entity Turret;
    public Entity Cannon;
} 

TankAuthoring 컴포넌트를 Tank 게임 오브젝트에 추가하고, Turret 게임 오브젝트에 Turret 프로퍼티를 설정하고, Cannon 게임 오브젝트에 Cannon 프로퍼티를 설정합니다. 베이킹 시 Tank 컴포넌트는 베이크된 탱크 루트 엔티티에 추가되고, 포탑 엔티티는 ‘Turret’ 필드에 할당되며, 대포 엔티티는 ‘Cannon’ 필드에 할당됩니다. (Turret 및 Cannon 필드는 이후 단계에서 사용됩니다.)

이제 무작위 곡선 경로를 따라 탱크를 이동하는 시스템을 생성해 보겠습니다. ‘TankMovementSystem.cs’라는 새 스크립트 파일을 생성하고 다음 코드를 추가합니다.

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

public partial struct TankMovementSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var dt = SystemAPI.Time.DeltaTime;

        // LocalTransform 및 Tank 컴포넌트가 있는 각 엔티티에서 
        // LocalTransform 및 엔티티 ID에 액세스합니다.
        foreach (var (transform, entity) in
                 SystemAPI.Query<RefRW<LocalTransform>>()
                     .WithAll<Tank>()
                     .WithEntityAccess())
        {
            var pos = transform.ValueRO.Position;

            // 이는 탱크의 실제 위치를 수정하는 것이 아니라
            // 3D 노이즈 함수를 샘플링하는 지점만 수정합니다. 이렇게 하면 각 탱크가 서로 다른 슬라이스를 사용하게 되고
            // 각기 다른 무작위 플로 필드를 따라 이동하게 됩니다.
            pos.y = (float)entity.Index;

            var angle = (0.5f + noise.cnoise(pos / 10f)) * 4.0f * math.PI;
            var dir = float3.zero;
            math.sincos(angle, out dir.x, out dir.z);

            // LocalTransform을 업데이트합니다.
            transform.ValueRW.Position += dir * dt * 5.0f;
            transform.ValueRW.Rotation = quaternion.RotateY(angle);
        }
    }
}

MovementSystem이 모든 프레임을 업데이트합니다. 업데이트에서 시스템은 LocalTransformTank 컴포넌트가 있는 모든 엔티티를 쿼리하고 LocalTransform을 업데이트하여 각 탱크를 무작위 곡선 경로를 따라 이동시킵니다.

5. 무작위 색상으로 다양한 탱크 생성

많은 탱크를 생성하려면 먼저 탱크를 베이크된 프리팹으로 전환해야 합니다. 우선 Tank 게임 오브젝트와 자식 게임 오브젝트를 ‘Tank’라는 이름의 새 프리팹으로 만든 다음 하위 씬에서 원본 탱크를 삭제합니다.

다음으로 Tank 프리팹에 있는 세 가지 게임 오브젝트(Tank, Turret, Cannon 게임 오브젝트) 모두에 URPMateiralPropertyBaseColorAuthoring 컴포넌트를 추가합니다. 그러면 Entities.Rendering.URPMaterialPropertyBaseColor 컴포넌트가 엔티티에 추가됩니다. 런타임에 이 컴포넌트의 값을 설정하면 엔티티 색상이 동적으로 변경됩니다.

프리팹을 엔티티로 베이크하는 방법은 다음과 같습니다.

1. 하위 씬에 새 게임 오브젝트를 추가하고 이름을 ‘Config’로 지정합니다.

2. 다음 코드를 사용하여 ‘ConfigAuthoring.cs’라는 새 스크립트 파일을 생성합니다.

using Unity.Entities;
using UnityEngine;

public class ConfigAuthoring : MonoBehaviour
{
    public GameObject TankPrefab;
    public GameObject CannonBallPrefab;
    public int TankCount;

    class Baker : Baker<ConfigAuthoring>
    {
        public override void Bake(ConfigAuthoring authoring)
        {
            // 설정 엔티티는 Transform 컴포넌트가 필요하지 않으므로
            // TransformUsageFlags.None을 사용합니다.
            var entity = GetEntity(authoring, TransformUsageFlags.None);
            AddComponent(entity, new Config
            {
                // 프리팹을 엔티티로 베이크합니다. GetEntity는 프리팹 계층 구조의 
                // 루트 엔티티를 반환합니다.
                TankPrefab = GetEntity(authoring.TankPrefab, TransformUsageFlags.Dynamic),
                CannonBallPrefab = GetEntity(authoring.CannonBallPrefab, TransformUsageFlags.Dynamic),
                TankCount = authoring.TankCount,
            });
        }
    }
}
public struct Config : IComponentData
{
    public Entity TankPrefab;
    public Entity CannonBallPrefab;
    public int TankCount;
}

3. ConfigAuthoring 인스턴스를 Config 게임 오브젝트에 추가하고 ‘Tank Prefab’ 필드를 Tank 프리팹으로 설정하고 ‘Tank Count’를 10으로 설정합니다. (‘CannonBallPrefab’ 필드는 이후 단계에서 설정됩니다.)

이제 탱크를 생성하고 색상을 설정하려면 다음 코드를 사용하여 TankSpawnSystem.cs라는 새 스크립트 파일을 생성합니다.

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;
using Random = Unity.Mathematics.Random;

public partial struct TankSpawnSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        // RequireForUpdate는 Config 컴포넌트가 있는 엔티티가 
        // 하나 이상 존재하는 경우에만 시스템이 업데이트됨을 의미합니다.
        // 실제로 이 시스템은 Config가 포함된 하위 씬이 
        // 로드될 때까지 업데이트되지 않습니다.
        state.RequireForUpdate<Config>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // 시스템을 비활성화하면 추가 업데이트가 중지됩니다.
        // 첫 번째 업데이트에서 이 시스템을 비활성화하면 
        // 실제로 한 번만 업데이트됩니다.
        state.Enabled = false;

        // ‘singleton’은 인스턴스가 하나만 있는 컴포넌트 유형입니다.
        // GetSingleton<T>는 T 유형의 컴포넌트를 가진 
        // 엔티티가 없거나 2개 이상인 경우 예외 오류가 발생합니다.
        // 이 경우 Config 인스턴스는 하나만 있어야 합니다.
        var config = SystemAPI.GetSingleton<Config>();

        // 하드 코딩된 시드의 무작위 숫자입니다.
        var random = new Random(123);

        for (int i = 0; i < config.TankCount; i++)
        {
            var tankEntity = state.EntityManager.Instantiate(config.TankPrefab);

            // URPMaterialPropertyBaseColor는 Entities.Graphics 패키지의 컴포넌트이며 
            // 렌더링된 엔티티의 기본 렌더링 색상을 설정할 수 있습니다.
            var color = new URPMaterialPropertyBaseColor { Value = RandomColor(ref random) };
           
            // 프리팹에서 인스턴스화된 모든 루트 엔티티는 프리팹 계층 구조(루트 포함)를 구성하는
            // 모든 엔티티의 목록인 LinkedEntityGroup 컴포넌트가 있습니다.

            // (LinkedEntityGroup은 ‘DynamicBuffer’라고 하는 특별한 유형의 컴포넌트이며
            // 단일 구조체 대신 구조체 값으로 구성된 크기 조절 가능 배열입니다.)
            var linkedEntities = state.EntityManager.GetBuffer<LinkedEntityGroup>(tankEntity);
            foreach (var entity in linkedEntities)
            {
                // 여기서는 URPMaterialPropertyBaseColor 컴포넌트를 가진 엔티티에만 해당 컴포넌트를 설정하려 하므로
                // 먼저 확인합니다.
                if (state.EntityManager.HasComponent<URPMaterialPropertyBaseColor>(entity.Value))
                {
                    // 탱크를 구성하는 각 엔티티의 색상을 설정합니다.
                    state.EntityManager.SetComponentData(entity.Value, color);    
                }
            }
        }
    }

    // 시각적으로 구별되는 무작위 색상을 반환합니다.
    // (단순한 무작위성은 좁은 범위의 색상을 중심으로 모여 있는 
    // 색상 분포를 생성합니다. 참고: https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/)
    static float4 RandomColor(ref Random random)
    {
        // 0.618034005f는 황금 비율의 역수입니다.
        var hue = (random.NextFloat() + 0.618034005f) % 1;
        return (Vector4)Color.HSVToRGB(hue, 1.0f, 1.0f);
    }
}

이제 플레이 모드를 실행하면 무작위 색상의 탱크 10개가 서로 다른 방향으로 돌아다니는 것을 볼 수 있습니다.

현재 게임 뷰에서는 탱크를 볼 수 있지만 뷰에서는 볼 수 없습니다. 뷰에 탱크를 표시하려면 Edit > Preferences > Entities > Scene View Mode를 ‘Runtime Data’로 선택합니다.

6. 플레이어 입력으로 하나의 탱크를 움직이고 카메라가 따라가도록 설정

플레이어가 하나의 탱크를 제어하려면 먼저 플레이어의 탱크 엔티티를 다른 탱크와 구별할 방법이 필요합니다. 그럼 이제 새 Player 컴포넌트를 생성한 다음 생성된 탱크 중 하나에 추가해 보겠습니다.

1. 새 파일 Player.cs에 다음 코드를 추가합니다.

using Unity.Entities;

public struct Player : IComponentData
{
}

참고: 보통 엔티티 컴포넌트 유형은 관련 저작(authoring) 컴포넌트와 동일한 파일에 정의되는 경우가 많지만, 이 경우 Player 컴포넌트는 저작 컴포넌트가 필요하지 않습니다.

2. TankSpawnSystem에서 탱크를 생성하는 루프에(프리팹을 인스턴스화하는 라인 뒤) 다음 코드를 추가합니다.

// 첫 번째 탱크에 Player 컴포넌트를 추가합니다.
if (i == 0)
{
    state.EntityManager.AddComponent<Player>(tankEntity);
}

3. 플레이어 탱크가 다른 탱크처럼 무작위 경로를 따라 이동하는 것을 중지하려면 TankMovementSystem의 루프 헤더를 다음 코드와 일치하도록 수정합니다.

foreach (var (transform, entity) in
           SystemAPI.Query<RefRW<LocalTransform>>()
                .WithAll<Tank>()
                .WithNone<Player>()  // 쿼리에서 플레이어 탱크를 제외합니다.
                .WithEntityAccess())

WithNone<Player> 호출은 쿼리에서 Player 컴포넌트가 있는 엔티티를 제외하도록 지정하므로 플레이어 탱크가 더 이상 이 루프에서 이동하지 않습니다.

4. 플레이어와 카메라를 이동하려면 다음 코드를 사용하여 PlayerSystem.cs라는 새 스크립트 파일을 생성합니다.

using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

public partial struct PlayerSystem : ISystem
{
    // OnUpdate가 관리되는 오브젝트(카메라)에 액세스하기 때문에 이 메서드를 
    // 버스트 컴파일할 수 없으므로 여기서는 [BurstCompile] 속성을 사용하지 않습니다.
    public void OnUpdate(ref SystemState state)
    {
        var movement = new float3(
            Input.GetAxis("Horizontal"),
            0,
            Input.GetAxis("Vertical")
        );
        movement *= SystemAPI.Time.DeltaTime;

        foreach (var playerTransform in 
                SystemAPI.Query<RefRW<LocalTransform>>()
                    .WithAll<Player>())
        {
            // 플레이어 탱크를 이동합니다.
            playerTransform.ValueRW.Position += movement;

            // 카메라를 움직여 플레이어를 따라갑니다.
            var cameraTransform = Camera.main.transform;
            cameraTransform.position = playerTransform.ValueRO.Position;
            cameraTransform.position -= 10.0f * (Vector3)playerTransform.ValueRO.Forward();  // 카메라를 플레이어 뒤로 이동합니다.
            cameraTransform.position += new Vector3(0, 5f, 0);  // 카메라를 오프셋만큼 올립니다.
            cameraTransform.LookAt(playerTransform.ValueRO.Position);  // 플레이어를 바라봅니다.
        }
    }
}

이제 플레이 모드에서 첫 번째 탱크를 제어할 수 있으며 카메라가 이를 따라갑니다. 카메라가 함께 움직이기 때문에 게임 뷰에서는 이동 중임을 알기 어려울 수 있습니다.

7. 포탑 끝에서 포탄 생성

탱크에서 포탄을 발사하려면 먼저 CannonBall 컴포넌트가 필요합니다.

1. CannonBallAuthoring.cs라는 새 스크립트를 생성하고 다음 코드를 작성합니다.

using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using UnityEngine;

public class CannonBallAuthoring : MonoBehaviour
{
    class Baker : Baker<CannonBallAuthoring>
    {
        public override void Bake(CannonBallAuthoring authoring)
        {
            var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);

            // 기본적으로 컴포넌트는 초기화되지 않기 때문에
            // CannonBall의 Velocity 필드는 float3.zero가 됩니다.
            AddComponent<CannonBall>(entity);
            AddComponent<URPMaterialPropertyBaseColor>(entity);
        }
    }
}

public struct CannonBall : IComponentData
{
    public float3 Velocity;
}

2. 3D 구체를 렌더링하는 단일 게임 오브젝트로 CannonBall이라는 새 프리팹을 생성하고 스케일을 (0.4, 0.4, 0.4)로 설정합니다.

3. CannonBall 프리팹의 루트 게임 오브젝트에 CannonBallAuthoring 컴포넌트를 추가합니다.

4. 하위 씬의 Config 게임 오브젝트에서 ConfigAuthoring의 ‘CannonBallPrefab’ 필드를 CannonBall 프리팹으로 설정합니다.

5. 포탄을 생성하려면 ShootingSystem.cs라는 새 스크립트 파일을 만들고 다음 코드를 작성합니다.

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

// 이 속성은 업데이트 순서에서 이 시스템을 TransformSystemGroup 앞에 배치합니다.
// ShootingSystem은 포탄의 로컬 트랜스폼만 설정하지만
// TransformSystemGroup의 트랜스폼 시스템은 월드 트랜스폼(LocalToWorld)을 설정합니다.
// ShootingSystem이 프레임에서 TransformSystemGroup 이후에 업데이트되는 경우,
// 생성된 포탄은 단일 프레임의 원점에 렌더링됩니다.
[UpdateBefore(typeof(TransformSystemGroup))]
public partial struct ShootingSystem : ISystem
{
    private float timer;
    
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        // 타이머가 만료된 프레임에서만 발사합니다.
        timer -= SystemAPI.Time.DeltaTime;
        if (timer > 0)
        {
            return; 
        } 
        timer = 0.3f;   // 타이머를 재설정합니다.

        var config = SystemAPI.GetSingleton<Config>();
        
        var ballTransform = state.EntityManager.GetComponentData<LocalTransform>(config.CannonBallPrefab);
        
        // 모든 탱크의 각 포탑에 포탄을 생성하고 초기 속도를 설정합니다.
        foreach (var (tank, transform, color) in
                 SystemAPI.Query<RefRO<Tank>, RefRO<LocalToWorld>, RefRO<URPMaterialPropertyBaseColor>>())
        {
            Entity cannonBallEntity = state.EntityManager.Instantiate(config.CannonBallPrefab);
            
            // 포탄의 색상이 포탄을 쏜 탱크와 일치하도록 설정합니다.
            state.EntityManager.SetComponentData(cannonBallEntity, color.ValueRO);
            
            // 월드 공간에서 대포의 트랜스폼이 필요하므로 LocalTransform 대신 LocalToWorld를 사용합니다.
            var cannonTransform = state.EntityManager.GetComponentData<LocalToWorld>(tank.ValueRO.Cannon);
            ballTransform.Position =  cannonTransform.Position;
            
            // 새 포탄의 위치를 생성 지점과 일치하도록 설정합니다.
            state.EntityManager.SetComponentData(cannonBallEntity, ballTransform);

            // 대포에서 발사되는 포탄의 속도를 설정합니다.
            state.EntityManager.SetComponentData(cannonBallEntity, new CannonBall
            {
                Velocity = math.normalize(cannonTransform.Up) * 12.0f
            });
        }
    }
}

이제 플레이 모드에서 탱크는 일정한 간격으로 포탄을 발사하지만 포탄은 아직 움직이지 않기 때문에 생성된 위치에 그대로 남아 있습니다.

8. 포탑 회전

포탑을 회전하기 위해 다른 시스템을 생성할 수 있지만 TankMovementSystemOnUpdate 메서드 하단에 몇 가지 코드를 추가하는 것이 더 간단합니다.

var spin = quaternion.RotateY(SystemAPI.Time.DeltaTime * math.PI);

foreach (var tank in
         SystemAPI.Query<RefRW<Tank>>())
{
    var trans = SystemAPI.GetComponentRW<LocalTransform>(tank.ValueRO.Turret);
    
    // Y축 주위에 회전을 추가합니다(부모를 기준으로).
    trans.ValueRW.Rotation = math.mul(spin, trans.ValueRO.Rotation);
}

9. 포탄 이동

포탄을 이동하고 땅에 닿을 때 파괴하려면 CannonBallSystem.cs라는 새 스크립트를 생성하고 다음 코드를 추가합니다.

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

public partial struct CannonBallSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();

        var cannonBallJob = new CannonBallJob
        {
            ECB = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged),
            DeltaTime = SystemAPI.Time.DeltaTime
        };

        cannonBallJob.Schedule();
    }
}

// IJobEntity는 소스 생성을 사용하여 
// Execute 메서드의 서명에서 쿼리를 암시적으로 정의합니다.
// 이 경우 암시적 쿼리는 CannonBall 및
// LocalTransform 컴포넌트를 가진 모든 엔티티를 찾습니다. 
[BurstCompile]
public partial struct CannonBallJob : IJobEntity
{
    public EntityCommandBuffer ECB;
    public float DeltaTime;

    // Execute는 CannonBall 및 LocalTransform 컴포넌트를 가진 
    // 모든 엔티티마다 한 번 호출됩니다.
    void Execute(Entity entity, ref CannonBall cannonBall, ref LocalTransform transform)
    {
        var gravity = new float3(0.0f, -9.82f, 0.0f);

        transform.Position += cannonBall.Velocity * DeltaTime;

        // 땅에 닿는 경우
        if (transform.Position.y <= 0.0f)
        {
            ECB.DestroyEntity(entity);
        }

        cannonBall.Velocity += gravity * DeltaTime;
    }
}

작업을 잡으로 이동시키면 해당 작업이 메인 스레드에서 워커 스레드(worker thread)로 이동합니다.

10. 추가로 시도해 볼 만한 작업

몇 가지 게임플레이를 다음과 같이 조정할 수 있습니다.

  • 플레이어에게 포탑을 직접 제어할 수 있는 권한을 부여합니다.
  • 포탄에 맞은 탱크를 파괴합니다.
  • 일정 시간이 경과하면 파괴된 탱크를 재생성합니다.
  • 플레이어가 적 탱크를 모두 파괴하면 승리 메시지를 표시합니다.
  • 제한된 횟수 내에서 플레이어를 리스폰합니다.

11. 다음 단계

Entities 패키지의 모든 기능을 설명하는 튜토리얼과 샘플을 더 살펴보려면 GitHub의 Entities Components 샘플 저장소를 참고하세요.

그리고 DOTS를 자세히 알아보면서 DOTS 베스트 프랙티스 교육 과정도 꼭 확인해 보세요.

Complete this Tutorial