Mobile에서 너무 느리게 실행되는 문제 발생

  • 이게 답인지 모르겠지만 일단 이 부분 제거 후 느린 것이 사라짐
    • Post Processing을 사용하는 것으로 되어 있는 상황
    • 현재 게임에서는 Post Processing을 사용하지 않기에 해당 제거
      • 어떤 상황에서 저것이 자동으로 만들어지는 지 확인 필요
  • Project Settings ⇒ Player  Other Settings

블로그 이미지

RIsN

,

전제 조건

  • 첫번째 Scene에서 생성되어 게임이 실행되는 동안 계속 소유하고 있는 무언가가 있어야 함
    • 현재 쓰고 있는 구조에서는 App이라는 스크립트가 게임 시작부터 끝까지 존재
  • 각 Scene마다 소유하고 있는 스크립트가 존재해야 함
    • 현재 쓰고 있는 구조에서는 _Ruler(상속)라는 스크립트가 존재
      • 예) Ruler_Logo, Ruler_Lobby 등

방법

  1. (예외 처리 1) 첫번째 Scene으로 갈 지 안 갈 지를 판단
    • isGoToApp:Bool 라는 Inspector 상에서 보이는 변수를 _Ruler가 소유 중
    • 해당 isGoToApp이 True면 밑의 내용을 실행, False면 return
      • (예외 처리 2)
        • False일 때 App을 아예 생성해서 모든 작업들(데이터 로드 등)을 우선 진행한 후 이 Scene에 남아 있게 할 수도 있음
          • 이 Scene에서는 로드가 전부 될 때까지 지연 처리
        • 다만 그 경우 여러모로 각 씬 + 협업하는 사람이 합(데이터 로드는 무조건 한 곳)을 맞춰야 함
  2. _Ruler가 Awake될 때 App이라는 스크립트가 현재 화면 내에 존재하는 지 확인
  3. App이 없다면 바로 0번째 Scene을 로드
  4. 완료

생각해볼 것

  • 저런 처리(스크립트 존재) 없이 유니티 상에서 시작 시에 현재 Scene을 판단해서 어떤 항목의 ON/OFF에 따라 첫번째(0) Scene으로 갈 수 있지 않을까?
블로그 이미지

RIsN

,

[Unity] 가로 Swipe 판단

Unity 2023. 1. 22. 17:44

목표: 가로 Swipe를 토대로 캐릭터 카메라 회전

  • Event에 익숙해지기 위해서 Event 사용
    • Scene이 바뀌어도 딱히 문제 없이 사용하기 위함
private Coroutine iCoroutine_Input = null;
public void StartInput()
{
    this.iCoroutine_Input = this.StartCoroutine(this.IENInput());
}
public event System.Action<float> eSwipeHorizontal = null;
private IEnumerator IENInput()
{
    while (true)
    {
        // :: Swipe Horizontal
        // :: 처음 터치
        if (Input.GetMouseButtonDown(0))
        {
            // :: 터치 시작 위치
            Vector2 startPos = Input.mousePosition;
            // :: 터치 중
            while (Input.GetMouseButton(0))
            {
                // :: 터치 끝 위치
                Vector2 endPos = Input.mousePosition;
                // :: 터치 방향
                Vector2 swipe = endPos - startPos;
                // :: 터치 방향 이벤트
                this.eSwipeHorizontal?.Invoke(swipe.x);
                yield return null;
            }
        }
        yield return null;
    }
}
public void StopInput()
{
    if (this.iCoroutine_Input != null)
    {
        this.StopCoroutine(this.iCoroutine_Input);
        this.iCoroutine_Input = null;
    }
}

 

블로그 이미지

RIsN

,

목표: 게임의 레시피 UI가 카메라를 계속 바라볼 필요가 있음

  • 주의
    • 빈 게임 오브젝트를 만들고 거기에 스크립트를 부착할 필요가 있음.
    • 내부의 이미지나 글은 조금 각도 등이 수정이 가능한 형태로 만들어야 나중에 편해짐

예) 방식

using System.Collections;
using UnityEngine;

public class GearTool_LookAtCamera : _Gear
{
    public override void Init() { }

    // :: 따라갈 카메라 혹은 오브젝트
    [SerializeField] private Transform iCamera = null;

    // :: 쳐다보기
    void Update()
    {
        if (this.iCamera == null) this.iCamera = Camera.main.transform;

        this.transform.LookAt(
            this.transform.position + this.iCamera.rotation * Vector3.forward,
            this.iCamera.rotation * Vector3.up);
    }
}
  • _Gear는 현재 쓰고 있는 형식, MonoBehaviour로 사용하거나, 상황에 맞춰서 사용할 것
블로그 이미지

RIsN

,

목표: 3D 카메라로 바라보는 3D 오브젝트의 위치에 UI 카메라 상의 캔버스에 이미지를 표시
>> 사용 이유: 내가 선호하는 구조가 3D 카메라와 UI카메라의 복합적인 구조

using System;
using UnityEngine;

public static class ToolCamera
{
    public static Vector3 ConvertPosition3DTo2DLocal(this Camera _camera3D,
    Camera _camera2D, Canvas _canvas, Vector3 _position3D)
    {
        // :: 3D 카메라에서의 3D 위치를 3D 카메라에서의 2D 위치로 변경
        Vector3 position2Din3D = _camera3D.WorldToScreenPoint(_position3D);

        // :: 3D 카메라에서의 2D 위치를 UI(2D) 카메라에서의 2D 로컬 위치로 변경
        Vector2 result;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            _canvas.transform as RectTransform, position2Din3D, _camera2D, out result);

        // :: Z축은 0으로 고정
        return new Vector3(result.x, result.y, 0);
    }
}

 

블로그 이미지

RIsN

,

  • 해당 이미지의 Sprite Mode가 설정이 안되어 있는 경우가 있으며, 그럴 경우 해당 항목을 설정해줘야 합니다.
블로그 이미지

RIsN

,

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

Step 9 - 카메라 추적(Camera follow)

런타임에서 ECS와 GameObjects의 간단한 상호작용.

  1. "Scripts/MonoBehaviours" 폴더에 "CameraSingleton.cs"이라는 이름의 새 C# 스크립트 파일을 만들고 다음 내용을 저장합니다:
  2. // 메인 카메라에 접근하는 방법은 여러 가지가 있지만, 
    // 싱글톤(여기서 사용하는 방식)을 사용하는 접근 방식은 모든 종류의 MonoBehaviour에서 작동됩니다.
    class CameraSingleton : UnityEngine.MonoBehaviour
    {
        public static UnityEngine.Camera Instance;
    
        void Awake()
        {
            Instance = GetComponent<UnityEngine.Camera>();
        }
    }
  3. "SampleScene"의 "Main Camera" 게임 오브젝트에 "CameraSingleton" MonoBehavior를 추가합니다.
  4. "Scripts/Systems" 폴더에 "CameraSystem.cs"이라는 이름의 새 C# 스크립트 파일을 만들고 다음 내용을 저장합니다:
  5. using Unity.Collections;
    using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    
    // 이 시스템은 변환 시스템이 업데이트된 후 실행되어야 합니다. 
    // 그렇지 않으면 카메라가 탱크보다 한 프레임 뒤로 처져 흔들림이 발생합니다.
    [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    partial class CameraSystem : SystemBase
    {
        Entity Target;
        Random Random;
        EntityQuery TanksQuery;
    
        protected override void OnCreate()
        {
            Random = Random.CreateFromIndex(1234);
            TanksQuery = GetEntityQuery(typeof(Tank));
            RequireForUpdate(TanksQuery);
        }
    
        protected override void OnUpdate()
        {
            if (Target == Entity.Null || UnityEngine.Input.GetKeyDown(UnityEngine.KeyCode.Space))
            {
                var tanks = TanksQuery.ToEntityArray(Allocator.Temp);
                Target = tanks[Random.NextInt(tanks.Length)];
            }
    
            var cameraTransform = CameraSingleton.Instance.transform;
            var tankTransform = GetComponent<LocalToWorld>(Target);
            cameraTransform.position = tankTransform.Position - 10.0f * tankTransform.Forward + new float3(0.0f, 5.0f, 0.0f);
            cameraTransform.LookAt(tankTransform.Position, new float3(0.0f, 1.0f, 0.0f));
        }
    }
  6. 플레이 모드로 들어가서 게임 뷰(이전과 같이 씬 뷰가 아님)를 보면 카메라가 탱크 중 하나를 따라가고 있음을 확인할 수 있습니다.  게임 뷰에 입력 포커스가 되어 있는지 확인하고(클릭해서) 스페이스바를 반복적으로 누릅니다. 카메라는 그 때마다 매번 다른 임의의 탱크로 전환되어야 합니다.
  7. 플레이 모드에서 나오세요.

축하합니다!

이 튜토리얼을 끝냈습니다! 가서 케이크 좀 사 와서 축하하세요!

블로그 이미지

RIsN

,

원본: 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. 플레이 모드에서 나오세요.
블로그 이미지

RIsN

,

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

Step 7 - 색이 입혀진 탱크들과 대포알들(Colored tanks and cannon balls)

고급 베이킹, 베이킹 시스템들에 대해 소개

ECS 컴퍼넌트들은 렌더링에 사용되는 쉐이더들에 대한 입력들을 제어할 수 있습니다. 자체 쉐이더를 만들고(쉐이더 그래프를 통해) 사용자 지정 ECS 컴퍼넌트를 입력에 맵핑하는 것은 이 튜토리얼의 범위를 벗어나지만, URPMaterialPropertyBaseColor라는 기존 컴퍼넌트를 사용할 예정입니니다. 이름에서 알 수 있듯이 표준 URP 머티리얼의 기본 색상을 제어할 수 있습니다.

우리의 탱크는 3개의 원시 요소(탱크, 터렛, 대포)로 구성되어 있으며, 각 엔티티에는 URPMaterialPropertyBaseColorcomponent가 추가되어야 합니다.

그렇게 하려면 Tank 프리팹을 열고 Hierarchy(탱크, 포탑, 대포)에서 SpawnPoint 트랜스폼을 제외하고 세 가지 기본 요소를 모두 선택하합니다. 렌더러가 없기 때문에 색상은 필요하지 않습니다.

세 가지 기본 요소를 선택한 상태에서 인스펙터의 "컴퍼넌트 추가" 단추를 사용하여 URPMaterialPropertyBaseColorAuthoringcomponent를 추가합니다.

  1. 플레이 모드로 전환하면 탱크가 완전히 검은색이 됩니다(방금 추가한 컴퍼넌트 요소의 기본값이 0,0,0,0입니다.).
  2.  
  3. 플레이 모드에서 나가세요.
  4. 📝 NOTE
    아래 시스템에서 사용하는 EntityCommandBuffer에는 SetComponentForLinkedEntityGroup에서 대상으로 지정할 엔티티를 지정하는 쿼리가 필요합니다.
    엔티티 쿼리의 핵심은 컴퍼넌트 타입 집합들로 구성되며, 쿼리는 해당 집합과 일치하는 엔티티에 대한 필터링된 보기만 제공합니다.
    엔티티 쿼리에 대한 자세한 내용은 package documentation를 참조하세요.
    다음과 같이 "Scripts/Systems" 폴더에 있는 "TankSpawningSystem.cs" 파일의 내용을 수정합니다.
  5.  using Unity.Burst;
     using Unity.Collections;
     using Unity.Entities;
     using Unity.Mathematics;
     using Unity.Rendering;
     
     [BurstCompile]
     partial struct TankSpawningSystem : ISystem
     {
    +    // 쿼리는 OnUpdate의 즉석에서 만들어져서는 안 되므로 필드에 캐시됩니다.
    +    EntityQuery m_BaseColorQuery;
     
         [BurstCompile]
         public void OnCreate(ref SystemState state)
         {
    +        // Config(구성) 싱글톤이 로드되기 전에는 이 시스템을 실행하면 안 됩니다.
    +        state.RequireForUpdate<Config>();
     
    +        m_BaseColorQuery = state.GetEntityQuery(ComponentType.ReadOnly<URPMaterialPropertyBaseColor>());
         }
     
         [BurstCompile]
         public void OnDestroy(ref SystemState state)
         {
         }
     
         [BurstCompile]
         public void OnUpdate(ref SystemState state)
         {
             var config = SystemAPI.GetSingleton<Config>();
     
    +        // 이 시스템은 한 번만 실행되므로 랜덤 시드를 하드 코딩할 수 있습니다.
    +        // 임의 상수 시드를 사용하면 동작이 결정론적으로 진행됩니다.
    +        var random = Random.CreateFromIndex(1234);
    +        var hue = random.NextFloat();
     
    +        // 가능한 한 서로 다른 색상을 만듭니다.
    +        // 이 접근법의 논리는 다음 주소에서 자세히 설명합니다:
    +        // https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
    +        URPMaterialPropertyBaseColor RandomColor()
    +        {
    +            // Note: 이 개념에 익숙하지 않은 경우, 이것은 "로컬 함수"입니다.
    +            // 더 많은 정보를 얻기 위해 인터넷에서 그 용어를 검색할 수 있습니다.
     
    +            // 0.618034005f == 2 / (math.sqrt(5) + 1) == 황금비율의 반대
    +            hue = (hue + 0.618034005f) % 1;
    +            var color = UnityEngine.Color.HSVToRGB(hue, 1.0f, 1.0f);
    +            return new URPMaterialPropertyBaseColor { Value = (UnityEngine.Vector4)color };
    +        }
     
             var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
             var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
     
             var vehicles = CollectionHelper.CreateNativeArray<Entity>(config.TankCount, Allocator.Temp);
             ecb.Instantiate(config.TankPrefab, vehicles);
     
    +        // EntityQueryMask는 EntityQuery에 의해 특정 엔티티가 선택 되었는지 여부를 효율적인 테스트를 제공합니다.
    +        var queryMask = m_BaseColorQuery.GetEntityQueryMask();
     
    +        foreach (var vehicle in vehicles)
    +        {
    +            // 모든 프리팹의 근본에는 해당 엔티티의 모든 목록인 LinkedEntityGroup이 포함됩니다.
    +            ecb.SetComponentForLinkedEntityGroup(vehicle, queryMask, RandomColor());
    +        }
     
             state.Enabled = false;
         }
     }
  6. 플레이 모드로 들어가면 탱크들이 무작위 색상으로 지정됩니다.
  7. 플레이 모드에서 나가세요.
  8. "Scripts/Authoring" 폴더에 있는 "CannonBallAuthoring.cs" 파일의 내용을 다음과 같이 수정합니다:
  9.  using Unity.Entities;
     using Unity.Rendering;
     
     class CannonBallAuthoring : UnityEngine.MonoBehaviour
     {
     }
     
     class CannonBallBaker : Baker<CannonBallAuthoring>
     {
         public override void Bake(CannonBallAuthoring authoring)
         {
             AddComponent<CannonBall>();
    +        AddComponent<URPMaterialPropertyBaseColor>();
         }
     }
  10. 다음과 같이 "Scripts/Aspects" 폴더에 있는 "TurretAspect.cs" 파일의 내용을 수정합니다:
  11.  using Unity.Entities;
     using Unity.Mathematics;
     using Unity.Rendering;
     
     readonly partial struct TurretAspect : IAspect
     {
         readonly RefRO<Turret> m_Turret;
    +    readonly RefRO<URPMaterialPropertyBaseColor> m_BaseColor;
     
         public Entity CannonBallSpawn => m_Turret.ValueRO.CannonBallSpawn;
         public Entity CannonBallPrefab => m_Turret.ValueRO.CannonBallPrefab;
    +    public float4 Color => m_BaseColor.ValueRO.Value;
     }
  12. 다음과 같이 "Scripts/Systems" 폴더에 있는 "TurretShootingSystem.cs" 파일의 내용을 수정합니다:
  13.  [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 });
         }
     }
  14. 플레이 모드로 들어가면 대포알이 생성된 탱크와 같은 색을 갖게 됩니다.
  15. 플레이 모드에서 나가세요.

Reference

  • 맵핑에 관해서
    • https://www.ibm.com/docs/ko/developer-for-zos/14.2?topic=pli-introduction-mapping-concepts
블로그 이미지

RIsN

,

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

Step 6 - 많은 탱크들을 소환(Spawning many tanks)

한 번만 실행되어야 하는 초기화 시스템을 처리.

  1. "Tank" Game Object를 "EntityScene"에서 "Assets/Prefabs" 폴더로 드래그 앤 드롭합니다.
  2. "EntityScene"에서 "Tank" GameObject(현재는 프리팹 인스턴스)를 삭제합니다.
  3. "Scripts/Components" 폴더에 "Config.cs"이라는 이름의 새 C# 스크립트 파일을 만들고 다음 내용을 저장합니다:
  4. using Unity.Entities;
    
    struct Config : IComponentData
    {
        public Entity TankPrefab;
        public int TankCount;
        public float SafeZoneRadius;
    }
  5. "Scripts/Authoring" 폴더에 "ConfigAuthoring.cs"이라는 이름의 새 C# 소스 파일을 만들고 다음 내용을 저장합니다:
  6. using Unity.Entities;
    
    class ConfigAuthoring : UnityEngine.MonoBehaviour
    {
        public UnityEngine.GameObject TankPrefab;
        public int TankCount;
        public float SafeZoneRadius;
    }
    
    class ConfigBaker : Baker<ConfigAuthoring>
    {
        public override void Bake(ConfigAuthoring authoring)
        {
            AddComponent(new Config
            {
                TankPrefab = GetEntity(authoring.TankPrefab),
                TankCount = authoring.TankCount,
                SafeZoneRadius = authoring.SafeZoneRadius
            });
        }
    }
  7. Hierarchy 창에서 "EntityScene"을 마우스 오른쪽 단추로 클릭하고 Create Empty를 선택한 다음 새 GameObject의 이름을 "Config"로 지정합니다.
  8. "ConfigAuthoring" 컴퍼넌트를 "Config" GameObject에 추가합니다.
  9. "Config" GameObject를 선택하고, 프로젝트 폴더에서 새 필드 "TankPrefab"을 "Tank" 프리팹(드래그 & 드롭)으로 설정하고, "TankCount"를 20으로 설정하고, "SafeZoneRadius"를 15로 설정합니다.
    📝 NOTE
    "SafeZone Radius"는 아직 사용되지 않지만 이후 진행될 튜토리얼과 관련이 있습니다.
  10. "Scripts/Systems" 폴더에 "TankSpawningSystem.cs"이라는 이름의 새 C# 스크립트 파일을 만들고 다음 내용을 저장합니다.
    ⚠ WARNING
    이 시점에서 플레이 모드로 들어가면 탱크가 하나밖에 없다는 사실에 놀랄 수 있습니다. 실제로 20개의 탱크가 있지만, 그들은 모두 같은 위치에서 스폰되고 정확히 같은 방식으로 움직입니다.
  11. using Unity.Burst;
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Rendering;
    
    [BurstCompile]
    partial struct TankSpawningSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
        }
    
        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {
        }
    
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var config = SystemAPI.GetSingleton<Config>();
    
            var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
    
            var vehicles = CollectionHelper.CreateNativeArray<Entity>(config.TankCount, Allocator.Temp);
            ecb.Instantiate(config.TankPrefab, vehicles);
    
            // 이 시스템은 시작할 때 한 번만 실행해야 합니다. 따라서 한 번 업데이트하면 자동으로 비활성화됩니다.
            state.Enabled = false;
        }
    }
  12. 다음과 같이 "Scripts/Systems" 폴더에 있는 "TankMovementSystem.cs" 파일의 내용을 수정합니다:
  13.  using Unity.Entities;
     using Unity.Mathematics;
     using Unity.Transforms;
     
     partial class TankMovementSystem : SystemBase
     {
         protected override void OnUpdate()
         {
             var dt = SystemAPI.Time.DeltaTime;
     
             Entities
                 .WithAll<Tank>()
    -            .ForEach((TransformAspect transform) =>
    +            .ForEach((Entity entity, TransformAspect transform) =>
                 {
                     var pos = transform.Position;
     
    +                // 이는 탱크의 실제 위치를 수정하는 것이 아니라 3D 노이즈 기능을 샘플링하는 지점만 수정합니다. 
    +                // 이 방식으로, 모든 탱크는 다른 파츠를 사용하고 있으며 고유한 랜덤 플로우 필드를 따라 이동합니다.
    +                pos.y = 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);
                     transform.Position += dir * dt * 5.0f;
                     transform.Rotation = quaternion.RotateY(angle);
     
                 }).ScheduleParallel();
         }
     }
  14. 플레이 모드로 들어가면, 20개의 탱크가 각각의 흐름 필드를 따라 이동하기 시작합니다.
  15. 플레이 모드를 종료하세요.
블로그 이미지

RIsN

,