Unity

ECS(Entity Component System) 튜토리얼: Step 8 - 세이프 존(Safe zone)

RIsN 2022. 12. 7. 21:34

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

Step 8 - 세이프 존(Safe zone)

싱글턴 구성, 실시간 변환.

  1. "Scripts/Components" 폴더에 "Shooting.cs"이라는 이름의 새 C# 스크립트 파일을 만들고 다음 내용을 저장합니다:
  2. using Unity.Entities;
    
    // 이 태그 컴퍼넌트는 "사용 가능한 컴퍼넌트"이기도 합니다.
    // 이러한 컴퍼넌트들은 엔티티에 존재하는 동안 온/오프 될 수 있습니다.
    // 이렇게 하는 것이 컴퍼넌트를 추가하거나 제거하는 것보다 훨씬 효율적입니다.
    struct Shooting : IComponentData, IEnableableComponent
    {
    }
  3. "Scripts/Authoring" 폴더에 있는 "TurretAuthoring.cs" 파일의 내용을 다음과 같이 수정합니다:
  4.  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(new Turret
             {
                 CannonBallPrefab = GetEntity(authoring.CannonBallPrefab),
                 CannonBallSpawn = GetEntity(authoring.CannonBallSpawn)
             });
     
    +        // 활성화된 컴퍼넌트들은 항상 초기에 활성화됩니다.
    +        AddComponent<Shooting>();
         }
     }
  5. "Scripts/Systems" 폴더에 "SafeZoneSystem.cs"이라는 이름의 새 C# 스크립트 파일을 만들고 다음 내용을 저장합니다.
  6. using Unity.Burst;
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    
    // Turret type을 처리없이 사용해야 합니다(Execute 메서드에 포함되지 않음).
    [WithAll(typeof(Turret))]
    [BurstCompile]
    partial struct SafeZoneJob : IJobEntity
    {
        // 이 작업을 병렬로 실행할 경우, 서로 다른 스레드에서 동일한 엔티티에 액세스하면 문제가 발생하여 
        // 세이프티 시스템에서 TurretActiveFromEntity와의 잠재적인 경합 상태에 대해 항의합니다.
        // 그러나 이 작업의 코드는 현재 처리 중인 엔티티만 TurretActiveFromEntity에서 조회되도록 작성되어 
        // 이 프로세스를 안전하게 만듭니다.
        // 그래서 우리는 병렬 세이프티 체크를 비활성화할 수 있습니다.
        [NativeDisableParallelForRestriction] public ComponentLookup<Shooting> TurretActiveFromEntity;
    
        public float SquaredRadius;
    
        void Execute(Entity entity, TransformAspect transform)
        {
            // 슈팅 태그 컴퍼넌트는 탱크가 지정된 범위를 벗어나는 경우에만 활성화됩니다.
            TurretActiveFromEntity.SetComponentEnabled(entity, math.lengthsq(transform.Position) > SquaredRadius);
        }
    }
    
    [BurstCompile]
    partial struct SafeZoneSystem : ISystem
    {
        // ComponentLookup 랜덤 접근자는 즉석에서 생성해서는 안 됩니다.
        // EntityQuery처럼, 한 번 만들고 필드에 저장해야 합니다.
        ComponentLookup<Shooting> m_TurretActiveFromEntity;
    
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
    
            m_TurretActiveFromEntity = state.GetComponentLookup<Shooting>();
        }
    
        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {
        }
    
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            float radius = SystemAPI.GetSingleton<Config>().SafeZoneRadius;
            const float debugRenderStepInDegrees = 20;
    
            // Debug rendering (the white circle).
            for (float angle = 0; angle < 360; angle += debugRenderStepInDegrees)
            {
                var a = float3.zero;
                var b = float3.zero;
                math.sincos(math.radians(angle), out a.x, out a.z);
                math.sincos(math.radians(angle + debugRenderStepInDegrees), out b.x, out b.z);
                UnityEngine.Debug.DrawLine(a * radius, b * radius);
            }
    
            m_TurretActiveFromEntity.Update(ref state);
            var safeZoneJob = new SafeZoneJob
            {
                TurretActiveFromEntity = m_TurretActiveFromEntity,
                SquaredRadius = radius * radius
            };
            safeZoneJob.ScheduleParallel();
        }
    }
  7. 다음과 같이 "Scripts/Systems" 폴더에 있는 "TurretShootingSystem.cs" 파일의 내용을 수정합니다:
  8. +// 슈팅 태그 컴퍼넌트의 요구로 인해 안전 영역에 있는 탱크에 대해 이 작업이 실행되는 것을 사실상 방지합니다.
    +[WithAll(typeof(Shooting))]
     [BurstCompile]
     partial struct TurretShoot : IJobEntity
     {
         [ReadOnly] public ComponentLookup<LocalToWorldTransform> LocalToWorldTransformFromEntity;
         public EntityCommandBuffer ECB;
     
         void Execute(in TurretAspect turret)
         {
             var instance = ECB.Instantiate(turret.CannonBallPrefab);
             var spawnLocalToWorld = LocalToWorldTransformFromEntity[turret.CannonBallSpawn];
             var cannonBallTransform = UniformScaleTransform.FromPosition(spawnLocalToWorld.Value.Position);
     
             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
             });
     
             // 아래 라인은 포탑에서 대포알까지 색을 전파합니다.
             ECB.SetComponent(instance, new URPMaterialPropertyBaseColor { Value = turret.Color });
         }
     }
  9. 플레이 모드로 들어가면 탱크가 세이프 존 밖으로 나온 후에만 사격합니다. 보기 옵션에서 gizmos가 활성화 되었는지 확인하세요, 그렇지 않으면 흰색 원이 표시되지 않습니다.
  10.  
  11. 여전히 플레이 모드에서, 제작된 "Config" GameObject를 선택하고 "Safe Zone Radius"를 수정합니다. "Live Baking" 덕분에 변경 사항이 실시간으로 반영됩니다.
  12. 플레이 모드에서 나오세요.