2019년 1월 24일 목요일

[Unity-ECS] 엔티티 구성 요소 ECS 및 Jobs System

Unity Entity Component System 과 C # Job System은 서로 다른 두 가지 시스템이지만 분리 할 수 없습니다. 이러한 두 시스템을 이해하기 위해 다음과 같이 Unity 시나리오에서 게임 개체를 만드는 워크 플로를 살펴 보겠습니다.

  • GameObject 객체를 만듭니다.
  • 객체에 구성 요소 추가: Renderer,Collider,Rigidbody physics
  • MonoBehaviour 스크립트를 생성하고 런타임에 해당 구성 요소의 상태 속성을 제어하고 변경하기 위해 객체에 추가합니다.

    위의 세 단계가 수행되며 Unity의 실행 흐름이라고합니다. Unity 개발자는 가장 기본적인 프로세스입니다. 그러나이 접근법에는 자체 단점과 성능 문제가 있습니다. 예를 들어, 데이터와 로직은 긴밀하게 결합되어 있습니다. 즉, 로직이 특정 데이터와 관련되어 있고 별도로 분리 할 수 없기 때문에 코드 재사용 빈도가 적습니다.

    예를 들어 GameObject 및 Components 예제에서 GameObject는 Transform, Renderer, Rigidbody 및 Collider 참조를 사용하며 이러한 스크립트에서 참조되는 객체는 힙 메모리에 분산되어 있습니다.



    게임 객체, 동작 및 구성 요소 간의 메모리 객체는 다음 그림을 참조 하십시오.



    Unity GameObject 을 사용하면 매우 짧은 시간 안에 게임을 빌드하고 실행할 수 있습니다. 이 기능은 개발자가 신속하게 시작할 수 있게 해주는 Unity 의 기능 이기도 하지만 성능에는 이상적이지 않습니다. 이 문제를 자세히 살펴 보도록합니다. 각 참조 유형에는 액세스 할 필요가 없는 많은 추가 데이터가 포함되어 있으며 이러한 사용되지 않은 멤버는 프로세서 캐시에서 중요한 공간을 차지합니다. 예를 들어, 우리가 상속받은 Mono 는 전형적인 경우입니다. 기존 구성 요소의 함수 인터페이스 함수 나 변수가 거의 필요하지 않은 경우 나머지를 "폐기물 공간" 다이어그램에 표시된 것처럼 공간 낭비라고 생각할 수 있습니다.



    위의 그림에서 굵은 글씨체는 실제로 이동 작업에 사용 된 멤버를 나타내며 나머지는 공간을 낭비합니다 .GameObject 를 이동하려면 스크립트가 위치에 액세스하고 Transform 구성 요소에서 데이터 멤버를 회전해야합니다. 하드웨어가 메모리에서 데이터를 가져 오면 캐시 라인은 많은 쓸모없는 데이터를 채 웁니다. 이동해야하는 모든 GameObject 에 대해 위치 및 회전 멤버만 있는 배열을 설정하면 매우 짧은 시간에 어떻게 실행될 수 있겠습니까? 쓸데없는 데이터를 제거 하시겠습니까? ECS는이 문제를 해결하기 위해 고안되었습니다.
  • ECS 물리적 구성 요소 시스템
    Unity 의 새로운 엔티티 구성 요소 시스템은 비효율적 인 객체 참조를 제거하는 데 도움이 되며 자체 구성 요소가 있는 GameObject에 관계 없이 필요한 데이터만 포함하는 엔티티를 고려합니다.

    다음 엔티티 구성 요소 시스템에서 Bullet 엔티티에는 Transform 또는 Rigidbody 구성 요소가 연결되어 있지 않습니다. Bullet 엔티티는 업데이트를 명시 적으로 실행하는 데 필요한 원시 데이터입니다. 이 새로운 시스템을 사용하면 각 객체 유형에서 논리를 완전히 분리 할 수 있습니다.



    이 시스템은 큰 장점이 있습니다: 캐시 효율성을 높이고 액세스 시간을 단축 할뿐만 아니라 이 데이터 정렬을 필요로 하는 최신 CPU 에서 고급 기술 (자동 벡터화 / SIMD)을 사용할 수 있습니다. 기본 성능이 필요합니다. 아래 그림과 같습니다.





    위의 그림에서 캐시 라인 저장소의 단편화와 상속 된 Mono 시스템에 의해 생성 된 공간 낭비에 유의하십시오. 데이터 비교는 다음과 같습니다.



    위의 그림은 단일 이동 작업과 관련된 메모리 공간을 동일한 목표를 달성하는 두 작업과 비교 한 결과입니다.
  • C# Jobs System
    다중 스레드 코드를 사용하는 대부분의 사람들은 스레드 안전 코드 작성이 어려우며 자원에 대한 스레딩이 발생할 수 있음을 알고 있지만 기회가 거의 없으며 프로그래머가 생각하지 않으면 심각한 오류가 발생할 수 있습니다. 그것 이외에, 컨텍스트 전환은 비용이 많이 들기 때문에 가능한 한 효율적으로 작업 부하를 균형 잡는 방법을 배우는 것이 어렵습니다. 다음 이미지와 같이 새로운 Unity C# Job System 이 이러한 모든 문제를 해결합니다.



    Bullet Manager 에서 볼 수 있듯이 대부분의 게임 프로그래머는 GameObject 의 관리자를 작성합니다. 일반적으로 이러한 관리자는 GameObject 목록을 관리하고 모든 프레임의 모든 총알 활동을 프레임마다 업데이트합니다. 위치. 이것은 C # Jobs System 을 사용하는 조건과 매우 일치합니다. 총알 모션은 별도로 처리 할 수 있기 때문에 병렬 처리에 매우 적합합니다. C# Jobs System 을 사용하면 이 기능을 손쉽게 가져와서 다른 데이터 블록을 병렬로 실행할 수 있습니다. 게임 논리 코드에 집중해야 합니다. 물리적 구성 요소 시스템과 C# Job System 의 결합을 도입하십시오.

물리적 구성 요소 시스템과 C# Job System 의 결합은 보다 강력한 기능을 제공합니다. 물리적 구성 요소 시스템이 효율적이고 컴팩트 한 방식으로 데이터를 설정하기 때문에 작업 시스템은 데이터 배열을 분할하여 효율적으로 병렬로 작동 할 수 있습니다. 그러면 이 새로운 시스템을 어떻게 사용해야 할까요?

예를 들어서 우리가 게임을 디자인하는 방법은 아래와 같습니다:

  • 플레이어가 스페이스 바를 치고 프레임에 일정 수의 배송됩니다.



  • 생성 된 각 용기는 화면 경계 내에서 임의의 X 좌표로 설정됩니다.
  • 생성 된 각 선박에는 화면 아래쪽으로 보낼 수있는 이동 기능이 있습니다.
  • 한도를 초과하면 생성 된 각 선박의 위치가 재설정됩니다.

    이것은 상대적으로 간단한 게임 로직입니다. 이전에 우린 이런 디자인을 쉽게하기 위해
  • Mono 를 사용했습니다.
    이것은 게임 개발자로서 가장 쉽게 마스터 할 수있는 가장 일반적인 디자인입니다. 각 프레임의 스페이스 바 입력을 확인하고 화면의 왼쪽과 오른쪽 사이에서 임의의 X/Z 를 찾는 AddShips() 메서드를 트리거합니다. Z 위치에서 선상의 회전 각을 아래쪽으로 향하게 설정하고 그 위치에 선 조립식을 생성합니다.


  • 
    void Update()
    {
    	if (Input.GetKeyDown("space"))
    	{
    		AddShips(incremementCount);
    	}
    }
    
    void AddShips(int amount)
    {
    	for (int i = 0; i < amount; i++)
    	{
    		float xVal = UnityEngine.Random.Range(leftBound, rightBound);
    		float zVal = UnityEngine.Random.Range(0f, 10f);
    
    		Vector3 pos = new Vector3(xVal, 0f, bottomBound + zVal);
    		Quaternion rot = Quaternion.Euler(0f, 0f, 0f);
    
    		var obj = Instantiate(targetPrefab, pos, rot) as GameObject;
    	}
    }
    
    
    선박 객체가 생성되고 각 구성 요소가 힙 메모리에 생성되며 추가 모바일 스크립트가 프레임 당 위치를 업데이트하여 화면의 아래쪽 경계와 위쪽 경계 사이에 있도록 보장합니다.
    
    using UnityEngine;
    
    public class MonoMovement : MonoBehaviour
    {
        void Update()
        {
            Vector3 pos = transform.position;
            pos += transform.forward * MonoGameManager.Instance.moveSpeed * Time.deltaTime;
    
            if (pos.z < MonoGameManager.Instance.bottomBound)
                pos.z = MonoGameManager.Instance.topBound;
    
            transform.position = pos;
        }
    }
    
    
    아래 그림은 한 번에 16,500 개의 개체를 화면에 표시하는 분석기를 보여줍니다. 나쁘지는 않지만 더 빠르게 할 수 있습니다. BehaviorUpdate() 메서드를 살펴보면 모든 동작 업데이트를 완료하는 데 8.67ms 가 걸리며, 이 작업은 모두 주 스레드에서 수행됩니다. C# Jobs System 에서는 이 작업이 사용 가능한 모든 CPU 에 배포됩니다. 위의 솔루션에 Jobs System 을 추가하여 구현을 살펴 보겠습니다.
    • Mono 를 이용한 Jobs System
      위의 방법은 스크립트를 작성하기 쉽습니다. 시작하기만 하면 개발자가 될 수 있지만, 몇 년 동안 일해 온 개발자로서 여전히 문제가 있는 경우 계속해서 코드 구현을 살펴보아야합니다.
    
    using Unity.Burst;
    using UnityEngine;
    using UnityEngine.Jobs;
    
    [BurstCompile]
    public struct JobSystemMovement : IJobParallelForTransform
    {
        public float moveSpeed;
        public float topBound;
        public float bottomBound;
        public float deltaTime;
    
        public void Execute(int index, TransformAccess transform)
        {
            Vector3 pos = transform.position;
            pos += moveSpeed * deltaTime * (transform.rotation * new Vector3(0f, 0f, 1f));
    
            if (pos.z < bottomBound)
                pos.z = topBound;
    
            transform.position = pos;
        }
    }
    
    
    우리의 새로운 MovementJob 스크립트는 IJob 인터페이스를 구현하는 구조입니다. 각 선박의 이동 및 경계 검사 계산에 대해 이동 속도, 상한, 하한 및 증분 시간 값을 계산해야합니다. 작업 시스템에는 점진적 시간 개념이 없으므로 데이터를 명시 적으로 제공해야합니다. 새 위치 자체의 계산 논리는 Mono 의 상속과 동일하지만 데이터는 Transform 변환에 다시 할당되며 참조 유형 (예: Transform) 이 여기에 있으므로 TransformAccess 매개 변수를 통해 업데이트 해야 하는데 이것은 올바르지 않습니다. 위 코드의 IJobParallelForTransform 에서 Execute 메서드를 실행하면 이 구조를 Job Scheduler 에 전달할 수 있습니다. 그러면 Job Scheduler 에서 시스템이 모든 실행과 해당 논리적 실행을 완료합니다. 이 작업의 구조에 대해 자세히 알아 보려면 사용 된 내용을 분석해 보겠습니다. 인터페이스: IJob, ParallelFor, Transform,IJob 들은 다 IJob 변형 상속을 위한 기본 인터페이스Parallel For Loop 를 기본적으로 단일 스레드 루프를 사용하는 병렬 모드이며 다른 CPU 의 작업 범위에 따라 본문을 청크로 분할합니다. 마지막으로, Transform 은 실행되는 Execute 함수가 TransformAcess 매개 변수를 포함한다는 것을 나타냅니다. TransformAcess 매개 변수는 8 코어 시스템이 있고 각 CPU가 실행할 수있는 경우 일반 for 루프에서 반복되는 800 개의 요소 배열을 고려하여 외부 Transform 참조에 모바일 데이터를 제공하는 데 사용됩니다 100 개 항목의 작업을 자동화하면 어떻게될까요? 이것이 바로 시스템이 해야 할 일입니다. 인터페이스 이름의 끝에 있는 Transform 키워드는 Execute 메서드의 TransformAccess 매개 변수를 제공합니다. 이제는 각 Execute 호출에 대해 각 선박의 개별 변환 데이터가 전달된다는 것을 알았습니다. 이제 게임 관리자를 살펴 보겠습니다. AddShips() 및 Update() 메서드는 각 프레임에 대해 이 데이터를 설정하는 방법을 알고 있습니다.
    
    using Unity.Jobs;
    using UnityEngine;
    using UnityEngine.Jobs;
    
    public class JobSystemGameManager : MonoBehaviour
    {
        [SerializeField]
        public GameObject targetPrefab;
    
        public static JobSystemGameManager Instance;
    
        private TransformAccessArray transforms;
        private JobSystemMovement moveJob;
        private JobHandle moveHandle;
    
        public float leftBound = -100;
        public float rightBound = 100;
        public float topBound = 1000;
        public float bottomBound = -1000;
        public float moveSpeed = 20f;
        public int incremementCount = 200;
    
        void Start()
        {
            Instance = new JobSystemGameManager();
            transforms = new TransformAccessArray(0, -1);
        }
    
        void Update()
        {
            moveHandle.Complete();
    
            if (Input.GetKeyDown("space"))
            {
                AddShips(incremementCount);
            }
    
            moveJob = new JobSystemMovement()
            {
                moveSpeed = moveSpeed,
                topBound = topBound,
                bottomBound = bottomBound,
                deltaTime = Time.deltaTime
            };
    
            moveHandle = moveJob.Schedule(transforms);
    
            JobHandle.ScheduleBatchedJobs();
        }
    
        void AddShips(int amount)
        {
            moveHandle.Complete();
    
            transforms.capacity = transforms.length + amount;
    
            for (int i = 0; i < amount; i++)
            {
                float xVal = UnityEngine.Random.Range(leftBound, rightBound);
                float zVal = UnityEngine.Random.Range(0f, 10f);
    
                Vector3 pos = new Vector3(xVal, 0f, bottomBound + zVal);
                Quaternion rot = Quaternion.Euler(0f, 0f, 0f);
    
                var obj = Instantiate(targetPrefab, pos, rot) as GameObject;
    
                transforms.Add(obj.transform);
            }
        }
    }
    
    
    새로운 변수를 추적해야합니다: TransformAccessArray 는 각 컨테이너 Transform (작업 준비 TransformAccess) 에 대한 변경 내용을 저장하는 데이터 컨테이너입니다. 일반 Transform 데이터 유형은 스레드로부터 안전하지 않으며 GameObject 의 모바일 관련 데이터를 설정하는 데 사용됩니다. MovementJob 은 방금 생성 한 Jobs 구조의 인스턴스입니다. 우리는 Jobs System 에서 작업을 구성하기 위해 이 식별자를 사용합니다. 작업 식별자는 작업 완료시와 같이 다양한 작업 (예: 작업 예약) 에 참조되는 작업에 사용됩니다. 작업에 대한 핸들을 받게됩니다.
    
    void Start()
    {
    	Instance = new JobSystemGameManager();
    	transforms = new TransformAccessArray(0, -1);
    }
    
    void Update()
    {
    	moveHandle.Complete();
    
    	if (Input.GetKeyDown("space"))
    	{
    		AddShips(incremementCount);
    	}
    
    	moveJob = new JobSystemMovement()
    	{
    		moveSpeed = moveSpeed,
    		topBound = topBound,
    		bottomBound = bottomBound,
    		deltaTime = Time.deltaTime
    	};
    
    	moveHandle = moveJob.Schedule(transforms);
    
    	JobHandle.ScheduleBatchedJobs();
    }
    
    void AddShips(int amount)
    {
    	moveHandle.Complete();
    
    	transforms.capacity = transforms.length + amount;
    
    	for (int i = 0; i < amount; i++)
    	{
    		float xVal = UnityEngine.Random.Range(leftBound, rightBound);
    		float zVal = UnityEngine.Random.Range(0f, 10f);
    
    		Vector3 pos = new Vector3(xVal, 0f, bottomBound + zVal);
    		Quaternion rot = Quaternion.Euler(0f, 0f, 0f);
    
    		var obj = Instantiate(targetPrefab, pos, rot) as GameObject;
    
    		transforms.Add(obj.transform);
    	}
    }
    
    
    우리는 그것이 각 프레임을 완료하고 재 배열하는지 확인하려고합니다. 위의 moveHandle.Complete() 행은 예약 된 작업이 완료 될 때까지 주 스레드가 계속 실행되지 않도록 합니다. 이 작업 핸들을 사용하면 작업을 다시 준비하고 전달할 수 있으며 moveHandle.Complete() 를 반환하면 현재 프레임의 새 데이터로 업데이트 할 수 있습니다. MovementJob 은 작업을 다시 실행하도록 예약합니다. 이것은 차단 작업이지만 이전 작업을 계속 실행하면서 작업 예약을 방지합니다. 또한 선박 수집이 반복되는 동안 새로운 선박을 추가하는 것을 막을 수 있습니다. 많은 작업이 있는 시스템에서는 이러한 이유로 Complete() 메소드를 사용하지 않을 수 있습니다. MovementJob 이 Update() 가 끝날 때 호출되면 선박에서 업데이트해야 할 TransformAccessArray 를 통해 액세스 할 수 있는 모든 Transform 목록을 전달하고 모든 작업이 설정되고 예약되면 모든 Jobs 는 JobHandle.ScheduleBatchedJobs() 메서드를 사용하여 전달할 수 있습니다. AddShips() 메서드는 이전 Execute와 비슷하지만 약간의 차이점이 있습니다. 다른 곳에서 메서드를 호출하면 Job 이 완료되었는지 다시 확인하게됩니다. 이 일은 발생하지 않지만 큰 실수는 하지 않도록 주의하십시오. 또한 TransformAccessArray 의 멤버도 저장합니다. 생성 된 Transform에 대한 참조입니다. Job 성능이 어떻게되는지 봅시다. C# Job System 을 사용하여 동일한 프레임 시간 (약 33ms) 내에 Mono 에서 상속 된 화면 개체 수를 거의 두 배로 늘릴 수 있습니다. 이제 Movement 및 UpdateBoundingVolumes 작업이 프레임 당 약 4ms 가 걸리는 것을 볼 수 있습니다. 이는 큰 발전입니다! 또한 화면의 보트 수가 Mono 의 거의 두 배입니다. 그러나 우리는 여전히 더 잘할 수 있으며 현재 방법에는 여전히 몇 가지 제한 사항이 있습니다.
    • GameObject 인스턴스 생성은 시스템 호출 메모리 할당과 관련된 오랜 프로세스입니다.
    • Transforms 은 여전히 힙의 임의의 위치에 할당됩니다.
    • Transforms 은 여전히 사용되지 않는 데이터가 포함되어 있으며 메모리 액세스 효율성을 줄입니다.
    • C# Jobs System
      이 문제는 다소 복잡하지만 일단 이것을 이해하면 항상 이 지식을 습득하게됩니다. 먼저 새로운 적선 선 조립이 어떻게 이 문제를 해결하는지 살펴 보겠습니다.



      먼저, Transform 구성 요소 (사용되지 않음) 이외의 다른 기본 제공 Unity 구성 요소가 없습니다.이 프리 패브는 이제 구성 요소가있는 GameObject가 아니라 엔티티 생성에 사용할 템플릿을 나타냅니다. 프리팹의 개념은 관습 적으로 새로운 시스템에 완전히 적용 할 수 없으므로 엔티티 데이터를 저장하기 위한 편리한 컨테이너로 모든 스크립트에서 생각할 수 있으며 이제는 GameObjectEntity.cs 가 프리팹에 첨부됩니다. 이 필수 구성 요소는 이 게임 객체가 엔티티로 처리되고 새로운 엔티티 구성 요소 시스템을 사용함을 나타냅니다. 보시다시피 객체에는 이제 RotationComponent, PositionComponent 및 MoveSpeedComponent 도 포함됩니다. 표준 구성 요소 (위치 및 회전 등)는 내장되어 있으며 명시 적으로 생성 할 필요는 없지만 MoveSpeed 는 이 외에도 GPU 인스턴스화를 지원하는 public 멤버에 대한 재료 참조를 제공하는 MeshInstanceRendererComponent 가 필요합니다. 물리적 구성 요소 시스템의 경우 이러한 구성 요소가 새로운 시스템과 결합되는 방식을 살펴 보겠습니다.
    
    using System;
    using Unity.Entities;
    
    [Serializable]
    public struct ECSMoveSpeed : IComponentData
    {
        public float Value;
    }
    
    public class ECSMoveSpeedComponent : ComponentDataWrapper { }
    
    
    데이터 스크립트 중 하나를 열면 각 구조가 IComponentData 에서 상속 받음을 볼 수 있습니다. IComponentData 는 데이터를 사용 및 추적 할 엔터티 구성 요소 시스템의 유형으로 표시하고 백그라운드에서 데이터를 지능적으로 할당하고 패키징 할 수있게 합니다. 게임 코드에 집중하십시오. ComponentDataWrapper 클래스를 사용하면 이 데이터를 연결된 조립식 뷰포트의 뷰포트에 표시 할 수 있습니다. 이 사전 프레임과 관련된 데이터는 기본 이동 (위치 및 회전) 및 이동 속도에 필요한 Transform 구성 요소의 일부만 나타냅니다. 이 새 워크 플로에서 변환 구성 요소를 사용하십시오. GameplayManager 스크립트의 새 버전을 살펴 보겠습니다.
    
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Mathematics;
    using Unity.Transforms;
    using UnityEngine;
    
    public class ECSGameManager : MonoBehaviour
    {
        [SerializeField]
        public GameObject targetPrefab;
    
        public static ECSGameManager Instance;
    
        public float leftBound = -100;
        public float rightBound = 100;
        public float topBound = 1000;
        public float bottomBound = -1000;
        public float moveSpeed = 20f;
        public int incremementCount = 200;
    
        EntityManager manager;
    
        void Start()
        {
            Instance = new ECSGameManager();
            manager = World.Active.GetOrCreateManager();
        }
    
        void Update()
        {
            if (Input.GetKeyDown("space"))
            {
                AddShips(incremementCount);
            }
        }
    
        void AddShips(int amount)
        {
            NativeArray entities = new NativeArray(amount, Allocator.Temp);
            manager.Instantiate(targetPrefab, entities);
    
            for (int i = 0; i < amount; i++)
            {
                float xVal = UnityEngine.Random.Range(leftBound, rightBound);
                float zVal = UnityEngine.Random.Range(0f, 10f);
                manager.SetComponentData(entities[i], new Position { Value = new float3(xVal, 0f, bottomBound + zVal) });
                manager.SetComponentData(entities[i], new Rotation { Value = new quaternion(0, 0, 0, 1) });
                manager.SetComponentData(entities[i], new ECSMoveSpeed { Value = moveSpeed });
            }
            entities.Dispose();
        }
    }
    
    
    엔티티 구성 요소 시스템을 스크립트에서 사용할 수 있도록 일부 변경했습니다 엔티티를 생성, 업데이트 또는 파기하는 기관이라고 생각할 수 있는 EntityManager 변수가 있습니다. 또한 NativeArray 유형은 배송 수단으로 작성됩니다. 생성 된 관리자의 인스턴스화 메서드는 GameObject 매개 변수와 인스턴스화 된 엔터티의 수를 지정하는 NativeArray 설정을 사용하며, GameObject 에 전달 된 경우 앞서 언급 한 GameObjectEntity 스크립트와 필요한 구성 요소 데이터가 있어야합니다. EntityManager 는 실제로 GameObject 를 생성하거나 사용하지 않고, 엔티티를 생성하고, 모든 엔티티를 훑으면서, 새로운 인스턴스마다 시작 데이터를 설정하지 않고, 프리팹의 데이터 컴포넌트를 기반으로 엔티티를 생성합니다. 이동 후에는 메모리 누출을 방지하기 위해 안전하고 강력한 새 데이터 컨테이너를 릴리스 해야합니다.
    
    using Unity.Collections;
    using Unity.Entities;
    using Unity.Burst;
    using Unity.Jobs;
    using Unity.Mathematics;
    using Unity.Transforms;
    using UnityEngine;
    
    public class ECSMovementSystem : JobComponentSystem
    {
        [BurstCompile]
        struct ECSMovementJob : IJobProcessComponentData
        {
            public float topBound;
            public float bottomBound;
            public float deltaTime;
    
            public void Execute(ref Position position, [ReadOnly] ref Rotation rotation, [ReadOnly] ref ECSMoveSpeed speed)
            {
                float3 value = position.Value;
    
                value += deltaTime * speed.Value * math.forward(rotation.Value);
    
                if (value.z < bottomBound)
                    value.z = topBound;
    
                position.Value = value;
            }
        }
    
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            ECSMovementJob moveJob = new ECSMovementJob
            {
                topBound = ECSGameManager.Instance.topBound,
                bottomBound = ECSGameManager.Instance.bottomBound,
                deltaTime = Time.deltaTime
            };
    
            return moveJob.Schedule(this, inputDeps);
        }
    }
    
    
    엔티티가 설정되면 관련된 모든 모바일 작업을 새로운 MovementSystem 으로 분리 할 수 있으며 샘플 코드의 맨 위에서 맨 아래까지 새로운 개념을 각각 소개합니다. MovementSystem 클래스는 OnUpdate() 와 같은 구현에 필요한 콜백 함수를 제공하는 JobComponentSystem 에서 상속되어 시스템과 관련된 모든 코드가 독립적으로 유지되고 이 소형 패키지에서 시스템 별 업데이트를 수행 할 수 있도록합니다. uber-GameplayManager.cs 를 소유하는 대신 JobComponentSystem 의 철학은 모든 데이터와 수명주기 관리를 한 곳에 저장하는 것입니다. MovementJob 구조는 Execute 함수의 매개 변수를 통해 입력 된 각 인스턴스 데이터와 OnUpdate() 를 통해 업데이트 된 멤버 변수의 각 작업 데이터를 포함하여 작업에 필요한 모든 정보를 캡슐화합니다. position 매개 변수를 제외한 모든 인스턴스 데이터는 [ReadOnly] 특성으로 표시됩니다. 이 예제에서는 각 프레임의 위치만 업데이트하기 때문입니다. 각 선박 엔티티의 회전 및 이동 속도는 수명 주기 동안 고정되어 있으며 실제 Execute 기능에는 필요한 모든 데이터에 대해 작동하는 코드가 포함되어 있습니다. 백그라운드에서 자동으로 수행되는 Execute 함수 호출에 모든 위치, 회전 및 이동 속도 데이터를 입력하는 방법이 궁금 할 수 있습니다. 솔리드 구성 요소 시스템은 매우 스마트하며 IComponentData 유형 (IJobProcessComponentData 의 템플릿 매개 변수로 지정됨) 을 포함하는 모든 개체에 사용할 수 있습니다. 엔티티는 자동으로 데이터를 필터링하고 주입합니다.
    
    public class ECSMovementSystem : JobComponentSystem
    {
        // ...
        // Movement Job
        // ...
    
        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            ECSMovementJob moveJob = new ECSMovementJob
            {
                topBound = ECSGameManager.Instance.topBound,
                bottomBound = ECSGameManager.Instance.bottomBound,
                deltaTime = Time.deltaTime
            };
    
            return moveJob.Schedule(this, inputDeps);
        }
    }
    
    
    다음 OnUpdate() 메서드 인 MovementJob 은 JobComponentSystem 에서 제공하는 새로운 기능인 새로운 메서드이기 때문에 모든 프레임 설정을 구성하고 동일한 스크립트로 디스패치하는 것이 더 쉽습니다. 여기서 하는 모든 작업은 다음과 같습니다.
    • 새로 삽입 된 ComponentDataArrays (각 엔티티 인스턴스 데이터)를 사용하여 MovementJob 데이터를 설정합니다.







  • 각 데이터 프레임 설정 (시간 및 경계)
  • 작업 예약

  • 우리의 작업은 이미 완전하게 독립적으로 설정되어 있으며 이 특정 데이터 구성 요소 집합을 포함하는 엔티티가 처음 인스턴스화 될 때까지 OnUpdate() 함수가 호출되지 않습니다. 동일한 모바일 행동을 하는 일부를 추가하기로 결정한 경우 그런 다음 GameObject 가 이 세 개의 동일한 구성 요소 스크립트 (인스턴스화 한 대표 GameObject 의 데이터 유형 포함)를 추가하기만 하면 됩니다. 여기서 중요한 점은 MovementSystem 이 엔티티가 실행중인 엔티티가 무엇인지 신경 쓰지 않는다는 것입니다. 엔티티에 관심있는 데이터 유형이 포함되어 있고 수명주기를 제어하는 데 도움이되는 메커니즘이 있는 경우에만 주의해야 합니다. 약 33ms 의 동일한 프레임 시간에서 실행되면 물리적 구성 요소 시스템을 사용하여 한 번에 91,000 개의 개체를 화면에 표시 할 수 있습니다. Mono 에 의존하지 않으므로 엔티티 구성 요소 시스템은 사용 가능한 CPU 시간을 사용하여 더 많은 개체를 추적하고 업데이트 할 수 있습니다. 위의 파서 창에서 보았 듯이 이전의 TransformArrayAccess 파이프 라인을 완전히 무시하고 MovementJob 에서 위치 및 회전 정보를 직접 업데이트 한 다음 명시 적으로 자체 렌더링 행렬을 작성했기 때문에 기존의 Transform 구성 요소에 다시 쓸 필요가 없습니다.
    • 결론
    이 새로운 개념을 모두 연구하기 위해 며칠을 보냅니다. 새 프로젝트는 Unity2018.2 를 사용할 준비가 되어 있습니다. 초기 테스트에서 일부 테스트를 수행하여 실행 성능을 확인합니다. 즉, 새로운 시스템 테스트 결과가 훌륭합니다. 마지막으로 독자에게 일련의 데이터가 표시되며, 아래 그림과 같이 화면 최적화 지원을 통해 화면에서 지원되는 개체 수 및 업데이트 비용이 크게 향상됩니다.






  • 참조:
  • Unity Entity 구성 요소 시스템 설명서
  • Unity 엔티티 구성 요소 시스템 예
  • Unity Entity Component System 포럼
  • Unity 문서