원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

Step 5 - 대포알 이동(Cannon ball movement)

병렬 job에 대한 소개

  1. "Scripts/Aspects" 폴더에 "CannonBallAspect.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  2. using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    
    readonly partial struct CannonBallAspect : IAspect
    {
        // 외관(aspect)의 엔티티 필드는 엔티티 자체에 대한 액세스를 제공합니다.
        // 이것은 예를 들어 EntityCommandBuffer에 명령을 등록하는 데 필요합니다.
        public readonly Entity Self;
    
        // 외관(aspect)들은 다른 외관(aspect)을 격납할 수 있습니다.
        readonly TransformAspect Transform;
    
        // RefRW 필드는 컴퍼넌트에 대한 읽기 쓰기 액세스 권한을 제공합니다.
        // 외관(aspect)이 "in" 매개 변수로 사용되는 경우 필드는 RefRO인 것처럼 동작하며 쓰기 시도 시 예외를 발생시킵니다.
        readonly RefRW<CannonBall> CannonBall;
    
        // 이와 같은 프로퍼티는 필수가 아니고, 대신 트랜스폼 필드를 공개할 수도 있습니다.
        // 그러나 "aspect.aspect.aspect.component.value.value" 같은 체인을 피함으로써 가독성을 향상시킵니다.
        public float3 Position
        {
            get => Transform.Position;
            set => Transform.Position = value;
        }
    
        public float3 Speed
        {
            get => CannonBall.ValueRO.Speed;
            set => CannonBall.ValueRW.Speed = value;
        }
    }
  3. "Scripts/Systems" 폴더에 "CannonBallSystem.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  4. using Unity.Burst;
    using Unity.Entities;
    using Unity.Mathematics;
    
    [BurstCompile]
    // IJobEntity는 소스 생성에 의존하여 실행 함수의 서명 시에 암시적으로 쿼리를 정의합니다. 
    partial struct CannonBallJob : IJobEntity
    {
        // 일반 EntityCommandBuffer는 병렬로 사용할 수 없으며, ParallelWriter를 명시적으로 사용해야 합니다.
        public EntityCommandBuffer.ParallelWriter ECB;
        // job에서 직접 시간에 액세스할 수 없으므로 DeltaTime을 매개 변수로 전달해야 합니다.
        public float DeltaTime;
    
        // ChunkIndexInQuery 특성은 청크 인덱스를 int 매개 변수에 매핑합니다.
        // 각 chunk는 단일 스레드에서만 처리할 수 있으므로 이러한 인덱스는 각 스레드마다 고유합니다.
        // 또한 병렬 처리의 양에 관계없이 완전히 결정론적입니다.
        // 따라서 이러한 인덱스는 EntityCommandBuffer에서 명령을 기록할 때 정렬 키로 사용되므로,
        // 명령 재생이 항상 결정론적임을 보장합니다.
        void Execute([ChunkIndexInQuery] int chunkIndex, ref CannonBallAspect cannonBall)
        {
            var gravity = new float3(0.0f, -9.82f, 0.0f);
            var invertY = new float3(1.0f, -1.0f, 1.0f);
    
            cannonBall.Position += cannonBall.Speed * DeltaTime;
            if (cannonBall.Position.y < 0.0f)
            {
                cannonBall.Position *= invertY;
                cannonBall.Speed *= invertY * 0.8f;
            }
    
            cannonBall.Speed += gravity * DeltaTime;
    
            var speed = math.lengthsq(cannonBall.Speed);
            if (speed < 0.1f) ECB.DestroyEntity(chunkIndex, cannonBall.Self);
        }
    }
    
    [BurstCompile]
    partial struct CannonBallSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
        }
    
        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {
        }
    
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
            var cannonBallJob = new CannonBallJob
            {
                // EntityCommandBuffer의 병렬 기록기를 가져오는 데 필요한 함수 호출을 기록합니다.
                ECB = ecb.AsParallelWriter(),
                // job에서 직접 시간에 액세스할 수 없으므로 DeltaTime을 매개 변수로 전달해야 합니다.
                DeltaTime = SystemAPI.Time.DeltaTime
            };
            cannonBallJob.ScheduleParallel();
        }
    }
  5. 플레이 모드로 들어가면 대포알이 탱크에서 떨어져 땅 위로 튀어오릅니다.
블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

Step 4 - 대포알

다른 엔터티의 엔터티를 참조하는 프리팹을 만드는 중입니다.

  1. "Scripts/Components" 폴더에 "CannonBall.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  2. using Unity.Entities;
    using Unity.Mathematics;
    
    // 대포알에 대한 동일한 접근 방식으로, 우리는 엔티티를 식별하기 위한 구성요소를 만들고 있습니다.
    // 그러나 이번에는 속도 필드라는 데이터가 포함되어 있기 때문에 태그 컴퍼넌트(비어 있는 컴퍼넌트)가 아닙니다.
    // 그것은 즉시 사용되지는 않겠지만, 우리가 모션을 구현할 때 관련성이 있을 것입니다.
    struct CannonBall : IComponentData
    {
        public float3 Speed;
    }
  3. "Scripts/Authoring" 폴더에 "CannonBallAuthoring.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  4. using Unity.Entities;
    using Unity.Rendering;
    
    class CannonBallAuthoring : UnityEngine.MonoBehaviour
    {
    }
    
    class CannonBallBaker : Baker<CannonBallAuthoring>
    {
        public override void Bake(CannonBallAuthoring authoring)
        {
    		// 기본적으로 컴퍼넌트들은 0으로 초기화됩니다.
            // 따라서 이 경우 대포알의 스피드 필드는 float3.zero가 됩니다.
            AddComponent<CannonBall>();
        }
    }
  5. Hierarchy 창에서 "SampleScene"을 마우스 오른쪽 버튼으로 클릭하고 GameObject > 3D Object > Sphere를 선택한 후 새로운 GameObject의 이름을 "CannonBall"로 지정합니다. Position을 (0,0,0), Rotation을 (0,0,0), Scale을 (0.2,0.2,0.2)로 설정합니다.
  6. "CannonBallAuthoring" 컴퍼넌트를 "CannonBall" GameObject에 추가합니다.
  7. "CannonBall" 게임 오브젝트에서 "Sphere Collider" 컴퍼넌트를 제거합니다.
  8. "CannonBall" GameObject를 Project 창의 "Prefabs" 폴더로 드래그 앤 드롭합니다.
  9. "SampleScene"에서 "CannonBall" GameObject(현재는 프리팹 인스턴스)를 삭제합니다.
  10. 다음과 같이 "Scripts/Components" 폴더에 있는 "Turret.cs" 파일의 내용을 수정합니다.
  11.  using Unity.Entities;
     
     struct Turret : IComponentData
     {
    +    // 이 엔티티는 대포알이 생성되어야 하는 대포의 노즐을 참조할 것입니다.
    +    public Entity CannonBallSpawn;
     
    +    // 이 엔티티는 대포알이 발사될 때마다 생성될 프리팹을 참조할 것입니다.
    +    public Entity CannonBallPrefab;
     }
  12. "Scripts/Authoring" 폴더에 있는 "TurretAuthoring.cs" 파일의 내용을 다음과 같이 수정합니다.
  13.  using Unity.Entities;
     
     class TurretAuthoring : UnityEngine.MonoBehaviour
     {
    +    public UnityEngine.GameObject CannonBallPrefab;
    +    public UnityEngine.Transform CannonBallSpawn;
     }
     
     class TurretBaker : Baker<TurretAuthoring>
     {
         public override void Bake(TurretAuthoring authoring)
         {
    -        AddComponent<Turret>();
    +        AddComponent(new Turret
    +        {
    +            // 기본적으로 각 GameObject 작성은 엔티티로 바뀝니다.
    +            // GameObject(또는 컴퍼넌트 작성)가 지정되면 GetEntity는 결과 엔티티를 검색합니다.
    +            CannonBallPrefab = GetEntity(authoring.CannonBallPrefab),
    +            CannonBallSpawn = GetEntity(authoring.CannonBallSpawn)
    +        });
         }
     }
  14. "Turret" GameObject를 선택하고, "Turret Authoring" 컴퍼넌트 요소의 새 필드 "CannonBallPrefab"과 "CannonBallSpawn"을 각각 Project 폴더의 "CannonBall" 프리팹(드래그 & 드롭)과 "SpawnPoint" GameObject(Hierarchy 창에서 드래그 & 드롭)로 설정합니다.
  15. "Scripts/Aspects" 폴더에 "TurretAspect.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  16. using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Rendering;
    
    // Turret component에 직접 접근하는 것이 아니라 외관(Aspect)을 만들고 있습니다.
    // 외관(Aspect)을 사용하면 구성 요소에 액세스하기 위한 사용자 지정 API를 제공할 수 있습니다.
    readonly partial struct TurretAspect : IAspect
    {
        // 이 참조는 Turret 구성 요소에 대한 읽기 전용 액세스를 제공합니다.
        // 읽기 전용 참조에서 ValueRW(ValueRO 대신)를 사용하려고 하면 오류가 발생합니다.
        readonly RefRO<Turret> m_Turret;
    
        // 다음 속성들의 ValueRo값의 사용에 유의하십시오.
        public Entity CannonBallSpawn => m_Turret.ValueRO.CannonBallSpawn;
        public Entity CannonBallPrefab => m_Turret.ValueRO.CannonBallPrefab;
    }
  17. 📝 NOTE
    다음 단계에서는 타입이 지정된 구성 요소에 임의로 접근할 수 있는 ComponentDataFromEntity<T>를 사용합니다. 이 기능에 대한 자세한 내용은 API Documentation를 참조하십시오.
    "Scripts/Systems" 폴더에 "TurretShootingSystem.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  18. using Unity.Burst;
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Rendering;
    using Unity.Transforms;
    
    [BurstCompile]
    partial struct TurretShootingSystem : ISystem
    {
        // ComponentLookup은 컴퍼넌트(엔티티 검색)에 대한 임의 접근을 제공합니다.
        // 우리는 이것을 사용하여 스폰 포인트(대포 노즐)의 월드 공간 좌표와 방향을 추출할 것입니다.
        ComponentLookup<LocalToWorldTransform> m_LocalToWorldTransformFromEntity;
    
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            // ComponentLookup 구조체는 한 번 초기화해야 합니다.
            // 매개 변수는 검색들을 읽기 전용으로 할지 또는 쓰기를 허용할지 여부를 지정합니다.
            m_LocalToWorldTransformFromEntity = state.GetComponentLookup<LocalToWorldTransform>(true);
        }
    
        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {
        }
    
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            // ComponentLookup 구조체들은 모든 프레임마다 업데이트 되어야 합니다.
            m_LocalToWorldTransformFromEntity.Update(ref state);
    
            // 인스턴스화에 필요한 구조 변경을 지연시키기 위해 EntityCommandBuffer를 제작합니다.
            var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
    
            // Job의 인스턴스를 만드는 중입니다.
            // 그것을 통과하면서 ComponentLookup은 스폰 포인트의 월드 트랜스폼을 요구합니다.
            // 또한 Job이 쓸 수 있는 엔티티 명령 버퍼도 있습니다.
            var turretShootJob = new TurretShoot
            {
                LocalToWorldTransformFromEntity = m_LocalToWorldTransformFromEntity,
                ECB = ecb
            };
    
            // 싱글 스레드에서 실행을 예약하고 메인 스레드를 차단하지 않습니다.
            turretShootJob.Schedule();
        }
    }
    
    [BurstCompile]
    partial struct TurretShoot : IJobEntity
    {
        [ReadOnly] public ComponentLookup<LocalToWorldTransform> LocalToWorldTransformFromEntity;
        public EntityCommandBuffer ECB;
    
        // TurretAspects 매개변수는 읽기 전용으로 선언하는 "in"입니다.
        // 이 경우 "ref"(읽기-쓰기)로 만들면 차이가 없지만 
    	// 잠재적으로 경합 상태(Race conditions)가 안전 시스템(Safety System)을 건드는 상황에 직면하게 됩니다.
        // 그래서 일반적으로 가능한 모든 곳에서 "in"을 사용하는 것이 좋은 원칙입니다.
        void Execute(in TurretAspect turret)
        {
            var instance = ECB.Instantiate(turret.CannonBallPrefab);
            var spawnLocalToWorld = LocalToWorldTransformFromEntity[turret.CannonBallSpawn];
            var cannonBallTransform = UniformScaleTransform.FromPosition(spawnLocalToWorld.Value.Position);
    
            // 새 인스턴스의 트랜스폼을 덮어쓰려고 합니다. 
    		// 비율을 명시적으로 복사하지 않으면 1로 재설정되고 크기가 너무 큰 캐논 볼을 갖게 됩니다.
            cannonBallTransform.Scale = LocalToWorldTransformFromEntity[turret.CannonBallPrefab].Value.Scale;
            ECB.SetComponent(instance, new LocalToWorldTransform
            {
                Value = cannonBallTransform
            });
            ECB.SetComponent(instance, new CannonBall
            {
                Speed = spawnLocalToWorld.Value.Forward() * 20.0f
            });
        }
    }
  19. 플레이 모드로 들어가면 탱크 뒤에 대포알의 흔적이 남아 있는 것을 볼 수 있습니다.

블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

Step 3 - 탱크 이동

SystemBase 및 병렬 Entities.ForEach를 소개합니다.

  1. "Scripts/Components" 폴더에 "Tank.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다:
  2. using Unity.Entities;
    
    // Turret에서 했던 것처럼 Tag component를 생성하여 Tank(큐브)를 식별합니다.
    struct Tank : IComponentData
    {
    }
  3. "Scripts/Authoring" 폴더에 "TankAuthoring.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  4. using Unity.Entities;
    
    class TankAuthoring : UnityEngine.MonoBehaviour
    {
    }
    
    class TankBaker : Baker<TankAuthoring>
    {
        public override void Bake(TankAuthoring authoring)
        {
            AddComponent<Tank>();
        }
    }
  5. "TankAuthoring" 컴퍼넌트를 "Tank" GameObject에 추가합니다.
  6. "Scripts/Systems" 폴더에 "TankMovementSystem.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  7. using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    
    // ISystem과 달리 SystemBase 시스템은 클래스입니다. 
    // Burst 컴파일되지 않으며 관리되는 코드(Managed Code)를 사용할 수 있습니다.
    partial class TankMovementSystem : SystemBase
    {
        protected override void OnUpdate()
        {
            // Entities.ForEach 아래는 각 항목에 대해 (암시적으로) Burst 컴파일됩니다.
            // 그리고 시간은 관리되는 유형(클래스)인 SystemBase의 멤버입니다.
            // 이것은 그곳에서 직접 시간에 접근하는 것이 불가능하다는 것을 의미한다.
            // 따라서 필요한 값(DeltaTime)을 로컬 변수에 복사해야 합니다.
            var dt = SystemAPI.Time.DeltaTime;
    
            // Entities.ForEach는 쿼리를 처리하는 오래된 접근 방식입니다. 
    		// 이 사용은 권장되지 않지만, 우리가 IFE와 동등한 기능을 얻을 때까지는 편리한 방법입니다.
            Entities
                .WithAll<Tank>()
                .ForEach((TransformAspect transform) =>
                {
                    // 이 값은 ForEach에 매개 변수로 전달되는 람다식입니다.
                    var pos = transform.Position;
    
                    // Unity.Mathematics.noise는 여러 유형의 노이즈 함수들을 제공합니다.
                    // 여기서는 고전적인 펄린 소음(cnoise)을 사용합니다.
                    // Perlin 노이즈에서 흐름장을 생성하는 방법에 관한 상세 내용은 여기에 있습니다:
                    // // https://www.bit-101.com/blog/2021/07/mapping-perlin-noise-to-angles/
                    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);
                    transform.Position += dir * dt * 5.0f;
                    transform.Rotation = quaternion.RotateY(angle);
    
                    // Entities.ForEach 시퀀스의 마지막 함수 호출은 코드가 실행되는 방법을 제어합니다. 
    			// Run (main thread), Schedule (single thread, async) 또는  ScheduleParallel (multiple threads, async).
                // Entities.ForEach는 기본적으로 job 발생기이며, 병렬 jobs를 매우 쉽게 만들 수 있습니다. 
    			// 이는 불행히도 복잡성 비용과 이상한 임의적 제약 조건이 수반되며, 이는 보다 명시적인 접근 방식이 선호되는 이유이다.
    			// 이러한 명시적 접근법(IJobEntity)은 이 튜토리얼의 뒷부분에서 다룹니다.
                }).ScheduleParallel();
        }
    }
     
  8. 플레이 모드로 들어가면 탱크가 흘러가는 영역을 따라 이동하기 시작해야 합니다.
  9. 플레이 모드를 나갑니다.

블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

Step 2 - 터렛 회전

관리되지 않는(unmanaged) 시스템(ISystem), 쿼리, 관용적인(idiomatic) foreach의 개념들을 소개합니다.

  1. "Scripts/Systems" 폴더에 "TurretRotationSystem.cs"이라는 새 파일을 만들고 다음 내용을 넣습니다.
  2. using Unity.Burst;
    using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    
    // Unmanaged systems based on ISystem can be Burst compiled, but this is not yet the default.
    // So we have to explicitly opt into Burst compilation with the [BurstCompile] attribute.
    // It has to be added on BOTH the struct AND the OnCreate/OnDestroy/OnUpdate functions to be
    // effective.
    [BurstCompile]
    partial struct TurretRotationSystem : ISystem
    {
        // Every function defined by ISystem has to be implemented even if empty.
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
        }
    
        // Every function defined by ISystem has to be implemented even if empty.
        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {
        }
    
        // See note above regarding the [BurstCompile] attribute.
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            // The amount of rotation around Y required to do 360 degrees in 2 seconds.
            var rotation = quaternion.RotateY(SystemAPI.Time.DeltaTime * math.PI);
    
            // The classic C# foreach is what we often refer to as "Idiomatic foreach" (IFE).
            // Aspects provide a higher level interface than directly accessing component data.
            // Using IFE with aspects is a powerful and expressive way of writing main thread code.
            foreach (var transform in SystemAPI.Query<TransformAspect>())
            {
                transform.RotateWorld(rotation);
            }
        }
    }
  3. 플레이 모드로 들어가면 모든 것이 재미있게 회전하고 있음을 알 수 있습니다(대포(Cannon)가 다른 것들로부터 점진적으로 분리되어 있으며, 아래 애니메이션은 그것을 잠시 실행시킨 후의 상황을 보여줍니다)
    문제는 우리가 만든 foreach가 각각의 transform이 있는 것과 매칭된다는 것입니다. 이것은 탱크 계층의 모든 부분을 회전시킵니다. 포탑의 회전에만 영향을 미치도록 제한해야 합니다.
  4. 플레이 모드에서 나가세요.
  5. "Scripts/Components" 폴더에 "Turret.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  6. using Unity.Entities;
    
    // 빈 구성 요소를 "tag component"라고 합니다.
    struct Turret : IComponentData
    {
    }
  7. "Scripts/Authoring" 폴더에 "TurretAuthoring.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다.
  8. using Unity.Entities;
    
    // MonoBehavior 작성은 일반적인 GameObject 컴퍼넌트입니다.
    // 그것들은 ECS 데이터를 생성하는 Baking 시스템의 입력을 구성합니다.
    class TurretAuthoring : UnityEngine.MonoBehaviour
    {
    }
    
    // Baker는 MonoBehaviours를 작성을 엔티티와 컴퍼넌트로 변환합니다.
    class TurretBaker : Baker<TurretAuthoring>
    {
        public override void Bake(TurretAuthoring authoring)
        {
            AddComponent<Turret>();
        }
    }
  9. TurretAuthoring.cs 파일을 Turret Game Object에 끌어다 놓거나 Game Object 검사기의 Add Component 버튼을 사용하여 "TurretAuthoring" 컴퍼넌트를 "Turret" GameObject에 추가합니다.
  10. "Turret" GameObject가 선택되어 있는 동안 "Entity Conversion" 패널에서 "Turret" 컴퍼넌트가 엔티티에 있는지 확인합니다(위로 끌어 확장해야 할 수도 있습니다).
  11. 다음과 같이 "Scripts/Systems" 폴더에 있는 "TurretRotationSystem.cs" 파일의 내용을 수정합니다.
  12.  using Unity.Burst;
     using Unity.Entities;
     using Unity.Mathematics;
     using Unity.Transforms;
     
     [BurstCompile]
     partial struct TurretRotationSystem : ISystem
     {
         [BurstCompile]
         public void OnCreate(ref SystemState state)
         {
         }
     
         [BurstCompile]
         public void OnDestroy(ref SystemState state)
         {
         }
     
         [BurstCompile]
         public void OnUpdate(ref SystemState state)
         {
             var rotation = quaternion.RotateY(SystemAPI.Time.DeltaTime * math.PI);
     
    +        // WithAll adds a constraint to the query, specifying that every entity should have such component.
    +        foreach (var transform in SystemAPI.Query<TransformAspect>().WithAll<Turret>())
    -        foreach (var transform in SystemAPI.Query<TransformAspect>())
             {
                 transform.RotateWorld(rotation);
             }
         }
     }
  13. 플레이 모드로 들어가면 포탑만 회전합니다.
  14. 플레이 모드에서 나옵니다.
블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

Step 1 - Scene 작성

ECS 데이터를 구성할 Scene을 작성합니다.

  1. Scenes 폴더의 SampleScene이 현재 열려 있는지 확인합니다. 다음 단계는 파일로 저장된 씬(Scene)에서만 작동합니다.
  2. Hierarchy 창에서 마우스 오른쪽 버튼을 클릭하고 New Subscene > Empty Scene...을 선택합니다. 새 씬(Scene)의 이름을 "EntityScene"으로 지정하고 Scenes/SampleScene에 배치합니다.
  3. Hierarchy 창에서 "EntityScene"을 마우스 오른쪽 버튼으로 클릭하고 GameObject > 3D Object > Cube를 선택한 후 새로운 GameObject의 이름을 "Tank"로 지정합니다. Position을 (0,0,0), Rotation을 (0,0,0), Scale을 (1,1,1)로 설정합니다.
  4. Hierarchy 창에서 "Tank"를 마우스 오른쪽 단추로 클릭하고 3D Object > Sphere를 선택한 후 새 게임 개체의 이름을 "Turret"으로 지정합니다. Position을 (0,0.5,0), Rotation을 (45,0,0), Scale을 (1,1,1)로 설정합니다.
  5. Hierarchy 창에서 "Turret"을 마우스 오른쪽 단추로 클릭하고 3D Object > Cylinder를 선택한 후 새 게임 개체의 이름을 "Cannon"으로 지정합니다. Position을 (0,0.5,0), Rotation을 (0,0,0), Scale을 (0.2,0.5,0.2)로 설정합니다.
  6. Hierarchy 창에서 "Cannon"을 마우스 오른쪽 단추로 클릭하고 "Create Empty"를 선택한 후 새 게임 개체의 이름을 "SpawnPoint"로 지정합니다. Position을 (0,1,0), Rotation을 (-90,0,0), Scale을 (1,1,1)로 설정합니다.
  7. 이제 다음 스크린샷과 유사한 내용이 표시되어야 합니다.
  8. 그 작은 탱크에 대한 마지막 한 가지는 각각의 도형들이 기본적으로 컬라이더를 가지고 있다는 것입니다. 우리는 이러한 컬라이더를 사용하지 않을 것이며, 프로젝트에 DOTS 호환 물리 엔진이 없기 때문에 어쨌든(엔티티로 변환) Baking 중에 폐기됩니다.
    그러나 DOD의 정신으로, 쓸모없는 데이터를 제거합시다: "Tank"에서 박스 컬라이더 구성 요소를 제거하고, "Turret"에서 스피어 컬라이더 구성 요소를 제거하고, "Cannon"에서 캡슐 컬라이더를 제거합니다. "Spawn Point"에는 컬라이더가 없으므로 거기서는 할 일이 없습니다.
블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

타입들의 접근성

이 튜토리얼에서는 모든 클래스(class)와 구조체(struct)를 내부로 만드는 관례를 따릅니다. 내부 타입 또는 멤버 변수들은 동일한 어셈블리의 파일 내에서만 액세스할 수 있으며, 이는 C#의 기본 접근성 수준입니다.
여러 어셈블리에 걸쳐 분할된 대규모 프로젝트에서 작업할 때는 명시적인 접근성 수정자가 필요합니다. 이에 대해 논의하는 것은 이 튜토리얼의 범위를 벗어나며, 어떤 식으로든 DOTS에만 국한되지 않습니다.

블로그 이미지

RIsN

,

원본:
https://blog.unity.com/technology/enter-play-mode-faster-in-unity-2019-3

Baking을 통해 데이터 변환

Baking은 Editor(데이터 작성: authoring data)의 GameObject 데이터를 Entity Scenes(런타임 데이터: runtime data)에 기록된 엔티티로 변환하는 시스템을 제공합니다.
Baking은 여러 단계로 나뉘지만, 그 핵심에는 두 가지 핵심 단계가 있습니다: Baker와 Baking Systems.
점진적 Baking(Incremental Baking)은 Sub Scene이 열려 있고 작성 개체(authoring objects)를 편집할 때도 발생합니다. ECS는 사용자가 변경한 내용을 감지하고 이 변경으로 인해 다시 실행해야 하는 베이커의 최소량을 식별합니다. 이 결과는 편집 모드 및 재생 모드 중에 편집기의 엔티티 월드에 패치됩니다.

Baker 클래스

Baker 클래스를 사용해서 컴퍼넌트 작성(authoring components)과 같이 Unity 개체와 직접 상호작용 할 수 있습니다. Baker들은 또한 의존성이 암묵적으로 또는 명시적으로 파일화(혹은 점유? Captured)되는 곳이며, 베이커가 다시 실행될 경우 추가된 모든 구성 요소가 자동으로 되돌아갈 수 있습니다. Baker는 Baking 중인 기본 엔티티와 자체적으로 생성된 추가 엔터티에만 컴퍼넌트를 추가할 수 있습니다.
예:

public class MyMonoBehaviour : MonoBehaviour
{
   public float value;
}

public class MyBaker : Baker<MyMonoBehaviour>
{
   public override void Bake(MyMonoBehaviour authoring)
   {
       AddComponent(new MyComponent {Value = authoring.value} );
   }
}

Baker에서 다른 데이터 소스에 접근하기

점진적 Baking(Incremental Baking)이 계속 작동하려면 Baker에서 GameObject를 변환하는 데 사용되는 데이터를 추적해야 합니다. 작성 컴퍼넌트(authoring component)의 모든 필드가 자동으로 추적되고 해당 데이터가 변경되면 베이커가 다시 실행됩니다.
다른 작성 컴퍼넌트(authoring component)의 정보는 자동으로 추적되지 않으므로 추적하려면 해당 구성요소에 종속성을 추가해야 합니다. 이렇게 하려면 게임 오브젝트에서 제공하는 기능 대신 Baker가 제공하는 기능을 사용하여 다른 구성 요소에 액세스 해야 합니다:

public struct MyComponent : IComponentData
{
   public float Value;
}

public class MyMonoBehaviour : MonoBehaviour
{
   public GameObject otherGO;
}

public class MyBaker : Baker<MyMonoBehaviour>
{
   public override void Bake(MyMonoBehaviour authoring)
   {
       var transform = GetComponent<Transform>(authoring.otherGO);
       AddComponent(new MyComponent {Value = transform.position.x} );
   }
}

마찬가지로 에셋(asset)에서 데이터에 액세스하는 경우 해당 자산에 대한 종속성을 생성해야 하므로 에셋(asset)이 변경되면 Baker가 다시 실행됩니다.

public struct MyComponent : IComponentData
{
   public int Value;
}

public class MyMonoBehaviour : MonoBehaviour
{
   public Mesh mesh;
}

public class MyBaker : Baker<MyMonoBehaviour>
{
   public override void Bake(MyMonoBehaviour authoring)
   {
       // We want to rebake if anything changes in the mesh itself
       DependsOn(authoring.mesh);
       AddComponent(new MyComponent { Value = authoring.mesh.vertexCount } );
   }
}

Baker 안의 프리팹(Prefabs)

Prefabs를 선언하고 변환시키려면, Baker 안에 GetEntity를 부르면 됩니다:

public struct MyComponent : IComponentData
{
   public Entity Prefab;
}

public class MyMonoBehaviour : MonoBehaviour
{
   public GameObject prefab;
}

public class MyBaker : Baker<MyMonoBehaviour>
{
   public override void Bake(MyMonoBehaviour authoring)
   {
       AddComponent(new MyComponent { Prefab = GetEntity(authoring.prefab) } );
   }
}

GetEntity는 엔티티 Prefab을 만드는 데 사용된 엔티티를 반환하지만 이 시점에서는 변환되지 않았습니다. 이것은 나중에 별도의 패스에서 발생합니다.

Baking 시스템

Baking 시스템은 베이커가 생산하는 결과를 처리하는 정규 systems 입니다. 예를 들어 결과를 결합하는 등. 즉, Baking 시스템은 엔티티 데이터로만 작동해야 하며 게임 오브젝트 및 컴퍼넌트와 같이 관리되는 작성 유형(authoring types)으로는 작동하지 않아야 합니다. 이는 Baking 시스템이 Burst 및 Jobs을 사용하여 데이터를 처리할 수 있음을 의미하기도 합니다.
Baking 시스템을 만들려면 [WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)] 특성을 표시합니다. 이것은 Baking이 그것들을 발견하고 Baking 월드에 그것들을 추가할 수 있게 해줍니다. Baking 시스템은 모든 Baking 패스에서 업데이트됩니다.

[WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)]
partial class BakingOnlyEntityAuthoringBakingSystem : SystemBase
{
   protected override void OnUpdate()
   {
      // … Your code here …
   }
}

Bakers는 일반적으로 필요하지만, Baking 시스템은 선택 사항이며 고급 사용 사례에만 필요합니다.


블로그 이미지

RIsN

,

원본: https://blog.unity.com/technology/enter-play-mode-faster-in-unity-2019-3

관련 항목

Unity 2019.3에서 더 빨리 Play 모드로 들어가세요.

Play 모드는 Unity를 즐겁게 만드는 핵심 요소입니다. 그러나 프로젝트가 복잡해지면서 시작하는 데 시간이 걸릴 수 있습니다. Play 모드로 들어가고 나갈 수 있는 속도가 빠를수록 변경 사항을 더 빨리 수정하고 테스트할 수 있습니다. 그렇기 때문에 실험적인 기능으로 Unity 2019.3 beta 에서 설정 가능한 Enter Play Mode를 도입하고 있습니다.

현재 Editor에서 Play Mode(재생 모드)를 시작하면, Unity는 스크립팅 상태(도메인 다시 로드)를 재설정하고 씬(scene)을 다시 로드하는 두 가지 작업을 수행합니다. 이 작업은 시간이 걸리고, 프로젝트가 복잡해질수록 재생 모드에서 새로운 변경 사항을 테스트하기 위해 더 오래 기다려야 합니다. 그러나 Unity 2019.3 beta부터는 "도메인 다시 로드" 및 "씬 다시 로드" 작업 중 하나 또는 둘 다를 비활성화할 수 있습니다.

테스트 결과에 따르면, 프로젝트에 따라 대기 시간을 최대 50-90%까지 절약할 수 있습니다.

 

 

우리는 AA 타이틀(Production title), 우리의 FPS 샘플, 메가시티, 그리고 빈 프로젝트로 구성 가능한 플레이 모드를 테스트 했습니다. 그래프는 편집기가 재생 모드로 전환하는 데 걸린 시간(초)을 나타냅니다. 해당 숫자가 작을수록 좋습니다.

File > Project Settings > Editor에서 Enter Play Mode Options를 활성화하면 Domain을 다시 로드하고 Scene을 다시 로드하는 옵션을 사용할 수 있습니다. 좀 더 상세한 내용을 원하면 어떻게 플레이 모드를 설정하나?를 확인하세요.

 

이 옵션을 사용하면 코드 변경이 없을 때 플레이 모드 시작 프로세스에서 Domain 및/또는 Scene 다시 로드를 비활성화할 수 있습니다. 플레이 모드에 들어가기 전에 게임 상태를 재설정하려면 API와 Callback을 통해 이 기능에 액세스할 수도 있습니다.

아래 표는 도메인 리로드 및 Scene 리로드를 비활성화 하기 전과 후의 플레이 모드 시작 프로세스를 보여 줍니다:

 

Unity가 플레이 모드로 전환할 때 수행하는 프로세스에 대한 자세한 내용은 문서를 참조하십시오.

이 기능은 현재 실험적이며 모든 Unity 패키지들이 비활성화된 도메인 및 Scene 리로드와 함께 작동하도록 검증되지는 않습니다. 문제가 생기면 포럼에서 알려주세요!

도메인 리로드를 사용하지 않도록 설정한 경우 스크립트를 올바르게 수정하는 방법

보시다시피 도메인 리로드를 방지하는 것은 매우 간단하지만 비용이 많이 듭니다. 플레이 모드가 시작될 때 스크립트 상태가 올바르게 재설정 되도록 스크립트의 정적 필드 및 정적 이벤트 핸들러를 조정해야 합니다.

다음 코드 예제에는 플레이어가 점프 버튼을 누를 때 올라가는 카운터가 있습니다. 도메인 리로드가 활성화된 경우, 플레이 모드로 들어갈 때 카운터가 자동으로 0으로 재설정 됩니다. 도메인 리로드를 사용하지 않도록 설정한 후에는 카운터가 재설정 되지 않고 값이 플레이 모드에 있거나 플레이 모드에서 해제됩니다. 즉, Editor에서 프로젝트의 두 번째 실행에서 카운터가 이전 실행에서 변경된 경우 카운터가 0이 아닐 수 있습니다.

public class StaticCounterExample : MonoBehaviour
{
    // 도메인 리로드를 사용하지 않도록 설정한 경우 이 카운터가 0으로 재설정 되지 않습니다.
	static int counter = 0;

	// Update is called once per frame
	void Update()
	{
    	if (Input.GetButtonDown("Jump"))
    	{
        	counter++;
        	Debug.Log("Counter: " + counter);
    	}
	}
}

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] 특성을 사용하고, 값을 명시적으로 재설정하여 도메인 리로드가 비활성화된 경우 카운터가 올바르게 재설정되도록 합니다. 예:

using UnityEngine;

public class StaticCounterExampleFixed : MonoBehaviour
{
	static int counter = 0;

	[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
	static void Init()
	{
    	Debug.Log("Counter reset.");
    	counter = 0;
	}

	// 프레임당 한 번씩 업데이트가 호출됩니다.
	void Update()
	{
    	if (Input.GetButtonDown("Jump"))
    	{
        	counter++;
        	Debug.Log("Counter: " + counter);
    	}
	}
}

도메인 리로드를 사용 불가능으로 설정한 후 플레이 모드를 종료할 때 Unity는 정적 이벤트 핸들러에서 메서드 등록을 취소하지 않습니다. 정적 이벤트 핸들러에 메서드를 등록하는 코드가 있는 경우 이로 인해 문제가 발생할 수 있습니다. 예를 들어, Editor에서 프로젝트의 첫 번째 Play(재생)에서 메소드는 정상으로 등록됩니다. 그러나 프로젝트의 두 번째 재생에서는 이러한 메소드가 첫 번째 메소드 외에 두 번째로 등록되므로 이벤트가 발생할 때 두 번 호출됩니다.

다음 코드는 정적 이벤트 핸들러에 Application.quitting을 등록합니다:

using UnityEngine;
public class StaticEventExample : MonoBehaviour
{
	void Start()
	{
    	Debug.Log("Registering quit function");
    	Application.quitting += Quit;
	}

	static void Quit()
	{
    	Debug.Log("Quitting!");
	}
}

도메인 리로드를 사용하지 않도록 설정한 경우 위의 예에서는 플레이 모드에 들어갈 때마다 '종료' 메서드를 다시 추가합니다. 이렇게 하면 플레이 모드를 종료할 때마다 "종료" 메시지가 추가로 표시됩니다.

[RuntimeInitializeOnLoadMethod] 특성을 사용해, 메서드를 두 번 추가하지 않도록 명시적으로 등록 취소합니다:

using UnityEngine;
public class StaticEventExampleFixed : MonoBehaviour
{
	[RuntimeInitializeOnLoadMethod]
	static void RunOnStart()
	{
    	Debug.Log("Unregistering quit function");
    	Application.quitting -= Quit;
	}

	void Start()
	{
    	Debug.Log("Registering quit function");
    	Application.quitting += Quit;
	}

	static void Quit()
	{
    	Debug.Log("Quitting the Player");
	}
}

 

우리의 문서에서 도메인 리로드가 비활성화 된 경우 올바르게 수행되도록 스크립트를 수정하는 방법에 대한 자세한 내용을 참조하십시오.

Asset Store

인기 있는 Asset Store packages가 비활성화된 도메인 및 Scene 리로드와 함께 작동하는지 확인하려고 합니다. 프로젝트에서 발생하는 문제를 Asset packages의 게시자에게 보고하여 도움을 받을 수 있습니다.

Join Unity 2019.3 beta!

현재 프로젝트가 플레이 모드로 전환되는 속도가 느린 경우 이 기능을 사용하면 작업 속도가 상당히 빨라질 것으로 생각됩니다. Unity 2019.3 beta에 가입하여 사용해 보십시오. 포럼에서 귀하의 의견을 들을 수 있기를 기대합니다! 이 기능은 실험적이기 때문에 사용자의 요구에 맞게 구성하는 데 도움이 될 수 있습니다. 우리는 특히 당신이 마주치는 문제에 대해 듣기를 고대하고 있습니다.

이미 이 기능을 테스트하고 귀중한 피드백을 제공함으로써 전체 커뮤니티를 도와준 포럼 사용자 @Sini, @chrisk, @Peter77 및 @Baste에게 큰 감사를 드립니다.

블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

변환 설정

베이킹 파이프라인(baking pipeline)을 적절히 설정

이 튜토리얼의 나머지 부분에서는 Preferences > Entites의 "Scene View Mode" 항목이 "Runtime Data"로 되어 있다고 가정합니다:


블로그 이미지

RIsN

,

원본: https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/DOTS_Guide/ecs_tutorial/README.md

프로젝트 설정

새 프로젝트를 만들고, 필요한 패키지를 설치하고, 설정을 조정.

  1. 3D(URP) 템플릿을 사용하여 Unity Hub에 새 프로젝트를 만듭니다. 템플릿을 사용해 본 적이 없으면 결국 다운로드해야 합니다. 올바른 에디터 버전을 사용하고 있는지 확인해주세요.
  2. 프로젝트가 로드되면 "URP Empty Template" 인스펙터에서 "Remove Readme Assets" 단추를 클릭합니다. 이렇게 하면 이 자습서에 필요하지 않은 "Assets/TutorialInfo" 폴더가 제거됩니다. 이 인스펙터는 기본적으로 선택되어 있어야 하지만 혹시 선택되지 않았다면 프로젝트 창에서 Assets/Readme.asset를 선택합니다.
  3. 이 튜토리얼에서는 패키지 하나만 명시적으로 설치해야 합니다. 다른 패키지는 종속되어 프로젝트에 포함됩니다. Window > Package Manager로 이동하여 왼쪽 상단의 + 버튼을 클릭하고 "Add package by name"을 선택합니다. "Name" 필드를 "com.unity.entities.graphics"으로 설정하고 "Version" 필드를 비워둔 다음 "Add" 버튼을 클릭하고 패키지와 종속 패키지가 설치될 때까지 기다립니다.
  4. Edit > Project Settings > Editor에서 "Enter Play Mode Options"을 사용 가능으로 설정하고, 하위 선택사항 중 "Reload ___" 관련은 선택하지 않은 상태로 둡니다. 이러한 옵션의 기능에 대한 자세한 내용은 다음 링크를 참조 해주세요.
    이러한 설정이 static 필드(블로그 게시물 등 참조)의 사용에 미치는 영향에 주의 해주세요.
  5.  
  6. 프로젝트 창에서 Assets 폴더의 다음 목록에서 누락된 폴더를 생성 해주세요.
    • Prefabs
    • Scenes (already created)
    • Scripts/Aspects
    • Scripts/Authoring
    • Scripts/Components
    • Scripts/MonoBehaviours
    • Scripts/Systems
    • Settings (already created)

블로그 이미지

RIsN

,