본문 바로가기

Unity/DOTS

Unity - ECSSamples 살펴보기 #1 - /HelloCube의 ForEach와 IJobChunk

반응형

참고자료 : https://github.com/Unity-Technologies/EntityComponentSystemSamples

 

Unity-Technologies/EntityComponentSystemSamples

Contribute to Unity-Technologies/EntityComponentSystemSamples development by creating an account on GitHub.

github.com

DOTS 중 ECS 학습을 위한 샘플 살펴보기입니다.

이 글에선 위 자료 중 ECSSample만 별도로 예정입니다. 

 

 

주의사항

 번역에 대해 100% 신뢰를 하시면 안됩니다. 필자의 영어실력은 매우 낮은편에 속하며, 번역기를 참고해 조금이라도 더 읽기 편하게 하려고 했을 뿐 입니다.

 

작업환경

 1. OS : Windows 10
 2. Editor : 2019.4.6f1

 3. RP : URP

 

참고사항

 필자는 2020.1 에서 적용을 시도해보다가 바뀐 환경 탓에 Entities를 설치하지 못하고 2019.4로 돌아왔습니다.

 

에디터 설정

1. Window/Package Manager 열기

2. Entities 설치 (이 것을 통해 DOTS 설치가 한번에 됩니다.)

3. RP가 Standard가 아니면 Edit/Render Pipeline/해당 RP/프로젝트 내 머티리얼들을 RP맞게 업그레이드 누르기

(이렇게 안하면 Standard 기준으로 되어있는 샘플 내용의 오브젝트가 깨져 정상작동하지 않을 수 있습니다.)

 

디렉토리 설정

 1. {DownloadPath}\EntityComponentSystemSamples-master\EntityComponentSystemSamples-master\ECSSamples 에 있는 Assets를 ECSSample로 변경

 2. 작업할 프로젝트 Assets에 복사

 

ECSSample/HelloCube

1. ForEach

 첫번째 예제인 ForEach입니다. 해당 예제에서는 MonoBehaviour의 GameObject를 어떻게 Entity로 변환하고 동작하게 하는지에 대해 제일 쉬운 방법을 제공합니다.

 

 - ReadMe.md : 

# HelloCube: ForEach

This sample demonstrates a simple ECS System that rotates a pair of cubes.
-> 이 예제는 한 쌍의 큐브를 회전하는 간단한 ECS 시스템을 보여줍니다.

## What does it show?
## 무엇을 보여주나요?

This sample demonstrates the separation of data and functionality in ECS. Data is stored in components, while functionality is written into systems.
-> 이 예제는 ECS에서 데이터와 기능의 분리를 보여줍니다. 데이터는 Component에 저장되고 기능은 System에 기록됩니다.

The **RotationSpeedSystem_ForEach** *updates* the object's rotation using the *data* stored in the **RotationSpeed_ForEach** Component.
-> **RotationSpeedSystem_ForEach**는 **RotationSpeed_ForEach* Component에 저장된 *data*를 사용하여 객체의 회전을 *updates*시킵니다.

## Systems and Entities.ForEach
## 시스템과 Entities.ForEach

RotationSpeedSystem_ForEach is a system that derives from SystemBase and uses an Entities.ForEach lambda to iterate through the Entities. This example only creates a single Entity, but if you added more Entities to the scene, the RotationSpeedSystem_ForEach updates them all — as long as they have a RotationSpeed_ForEach Component (and the Rotation Component added when converting the GameObject's Transform to ECS Components).
->RotationSpeedSystem_ForEach는 SystemBase을 상속하고 Entities.ForEach 람다를 이용하여 Entities를 반복하는 시스템입니다. 이 예제는 하나의 엔티티만 생성하지만 장면에 더 많은 엔티티를 추가 한 경우 RotationSpeedSystem_ForEach는 RotationSpeed_ForEach Component(및 게임 오브젝트의 변환을 ECS 구성 요소로 변환 할 때 추가 된 회전 컴포넌트)가 있는 모두를 업데이트합니다.

Note that the system using Entities.ForEach uses ScheduleParallel to schedule the lambda to run on multiple worker threads if there are multiple chunks. This automatically takes advantage of multiple cores if they are available (and if your data spans multiple chunks).
-> Entities.ForEach를 사용하는 시스템은 ScheduleParllel을 사용하여 청크가 여러 개인 경우 Lambda가 여러 작업자 스레드에서 실행되도록 예약합니다. 사용 가능한 경우(및 데이터가 여러 청크에 걸쳐있는 경우) 여러 코어를 자동으로 활용합니다.

## Converting from GameObject to Entity
## 게임오브젝트에서 엔티티로 컨버팅

This sample uses an automatically generated authoring component.  This MonoBehaviour is created via the **GenerateAuthoringComponent** attribute on the IComponentData and exposes all the public fields of that type for authoring.  See the **IJobChunk** sample for an example of how to write your own authoring component.
->이 예제는 자동으로 제작 구성 요소를 사용합니다. 이 MonoBehaviour는 IComponentData의 **GenerateAuthoringComponent** 속성을 통해 작성되며 제작을 위해 해당 유형의 모든 공용 필드를 노출합니다. 컴포넌트를 직접 작성하는 방법에 대한 예제는 **IJobChunk** 예제를 참조하세요.

 

RotationCube의 인스펙터 상태

 

- RotationSpeed_ForEach : 

using System;
using Unity.Entities;

//MonoBehaviour를 상속받지 않아도 인스펙터에 노출되어 데이터를 조절할 수 있음을 볼 수 있습니다.
[GenerateAuthoringComponent]
public struct RotationSpeed_ForEach : IComponentData
{
    public float RadiansPerSecond;
}

 

-RotationSpeedSystem_ForEach : 

using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

// This system updates all entities in the scene with both a RotationSpeed_ForEach and Rotation component.
// 이 시스템은 RotationSpeed_ForEach 및 Rotation 컴포넌트로 모든 엔티티를 업데이트합니다.

// ReSharper disable once InconsistentNaming
public class RotationSpeedSystem_ForEach : SystemBase
{
    // OnUpdate runs on the main thread.
    // OnUpdate는 메인 쓰레드에서 동작합니다.
    protected override void OnUpdate()
    {
    	//해당 프레임의 DeltatTime을 저장합니다.
        float deltaTime = Time.DeltaTime; 
        
        // Schedule job to rotate around up vector
        // 벡터를 중심으로 회전하는 작업을 예약합니다.
        //ForEach 내부는 메인쓰레드가 아님을 인지해야합니다.
        //Time.DeltaTime은 메인쓰레드에서 호출 가능하므로 미리 호출하여 저장한 것을 알 수 있습니다.
        //WithName은 해당 이름이 포함된 것을 가져오겠단 것이 아니라 이 작업의 이름을 지정하는 것 입니다.
        Entities
            .WithName("RotationSpeedSystem_ForEach")
            .ForEach((ref Rotation rotation, in RotationSpeed_ForEach rotationSpeed) =>
            {
                rotation.Value = math.mul(
                    math.normalize(rotation.Value), 
                    quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
            })
            .ScheduleParallel();
    }
}

Entities.WithName에 대한 설명은 해당 문서중에 있습니다.

 

WithName(string) -— assigns the specified string as the name of the generated job class. Assigning a name is optional, but can help identify the function when debugging and profiling.

WithName(string) -— 이는 선택사항이며 해당 문자열을 작업의 이름으로 표기합니다. 디버깅 및 프로파일링에서 해당 작업을 식별하는데 도움이 됩니다.

 

2. IJobChunk

 - ReadMe.md : 

# HelloCube: IJobChunk

This sample demonstrates a Job-based ECS System that rotates a pair of cubes. Instead of iterating by Entity, this example iterates by chunk. (A Chunk is a block of memory containing Entities that all have the same Archetype — that is, they all have the same set of Components.)
-> 이 예제는 한 쌍의 큐브를 회전시키는 작업 기반의 ECS 시스템을 보여줍니다. 이 예제에서는 Entity 별로 반복하는 대신 청크별로 반복합니다. 청크는 모두 동일한 아키타입을 가진 엔티티, 즉 같은 컴포넌트 세트를 갖고 있는 모든 엔티티를 포함하는 메모리 블록입니다.

## What does it show?
## 무엇을 보여주나요?

This sample shows another way to iterate through entities by accessing chunks directly.  This can offer more flexibility than Entities.ForEach in certain situations.
-> 이 예제에서는 청크에 직접 액세스하여 엔티티를 반복하는 다른 방법을 보여줍니다. 이 방법은 특정 상황에 있어서 Entities.ForEach보다 더 많은 유연성을 제공 할 수 있습니다.

As in the previous examples, the **RotationSpeedSystem_IJobChunk** *updates* the object's rotation using the *data* stored in the **RotationSpeed_IJobChunk** Component.
-> 이전 예제와 같이 **RotationSpeedSystem_IJobChunk**는 **RotationSpeed_IJobChunk** 컴포넌트에 저장된 *data*를 사용하여 객체의 회전을 *updates*합니다.

This sample also demonstrates writing a custom authoring component.  This can provide more flexibility that generating one with the **GenerateAuthoringComponent** attribute.
-> 이 예제에서는 사용자 지정 컴포넌트를 작성하는 법을 보여줍니다. 이는 **GenerateAuthoringComponent**속성을 이용하여 생성하는 유연성을 제공할 수 있습니다.

## Systems and IJobChunk
## 시스템과 IJobChunk

In this example, the Job in RotationSpeedSystem_IJobChunk is now implemented using IJobChunk.
-> 이 예제에서 RotationSpeedSystem_IJobChunk의 작업은 이제 IJobChunk를 사용하여 구현됩니다.

In a Job implemented with IJobChunk, the ECS framework passes an ArchetypeChunk instance to your Execute() function for each chunk of memory containing the required Components. You can then iterate through the arrays of Components stored in that chunk.
-> IJobkChunk로 구현 된 작업에서 ECS 프레임워크는 필요한 컴포넌트를 포함하는 각 메모리 청크에 대해 ArchetypeChunk 인스턴스를 Execute() 함수에 전달합니다. 그런 다음 해당 청크에 저장된 컴포넌트 배열을 반복 할 수 있습니다.

Notice that you have to do a little more manual setup for an IJobChunkJob. This includes constructing an EntityQuery that identifies which Component types the System operates upon. You must also pass ArchetypeChunkComponentType objects to the Job, which you then use inside the Job to get the NativeArray instances required to access the Component arrays themselves.
-> IJobChunkJob에 대해 조금 더 수동으로 설정해야합니다. 여기에는 시스템이 작동하는 컴포넌트 유형을 식별하는 EntityQuery 구성이 포합되며, ArchetypeChunkComponentType 객체를 Job에 전달해야합니다. 그러면 Job 내부에서 Component 배열 자체에 엑세스하는 데 필요한 NativeArray 인스턴스를 가져옵니다.

Systems using IJobChunk can handle more complex situations than those supported by Entities.ForEach, while maintaining maximum efficiency.
-> IJobChunk를 사용하는 시스템은 Entities.ForEach가 지원하는 것보다 더 복잡한 상황을 처리 할 수 있으며 최대 효율성을 유지합니다.

## Converting from GameObject to Entity
## 게임 오브젝트에서 엔티티로 변환

The **ConvertToEntity** MonoBehaviour converts a GameObject and its children into Entities and ECS Components upon Awake. Currently the set of built-in Unity MonoBehaviours that ConvertToEntity can convert includes the Transform and MeshRenderer. You can use the **Entity Debugger** (menu: **Window** > **Analysis** > **Entity Debugger**) to inspect the ECS Entities and Components created by the conversion.
-> **ConvertToEntity**는 MonoBehaviour의 Awake()시점에 해당 게임오브젝트와 자식 오브젝트들을 엔티티와 ECS 컴포넌트로 변환합니다. 현재 ConverToEntity가 변환 할 수 있는 내장 Unity MonoBehaviour 세트에는 Transform 및 MeshRenderer가 포함됩니다. **Entity Debugger**를 사용하여 변환으로 생성 된 sCES 엔티티 및 컴포넌트를 검사할 수 있습니다.

You can implement the IConvertGameObjectEntity interface on your own MonoBehaviours to supply a conversion function that ConvertToEntity will use to convert the data  stored in the MonoBehaviour to an ECS Component.
-> 자신의 MonoBehaviour에 IConvertGameObjectEntity 인터페이스를 구현하여 ConvertToEntity가 MonoBehaviour에 저장된 데이터를 ECS 구성 요소로 변환하는데 사용할 변환 함수를 제공 할 수 있습니다.

In this example, the **RotationSpeedAuthoring_IJobChunk** MonoBehaviour uses IConvertGameObjectEntity to add the RotationSpeed_IJobChunk Component to the Entity on conversion.
-> 이 예제에서 **RotationSpeedAuthoring_IJobChunk**는 MonoBehaviour의 IConvertGameObjectEntity를 사용하여 RotationSpeed_IJobChunk 컴포넌트를 변환시 엔티티에 추가합니다.

 

- RotationSpeed_IJobChunk : 

using System;
using Unity.Entities;

// ReSharper disable once InconsistentNaming
[Serializable]
public struct RotationSpeed_IJobChunk : IComponentData
{
    public float RadiansPerSecond;
}

//실제로 적용될 컴포넌트 데이터입니다.
//RotationSpeedAuthoring_IJobChunk에 의해 값이 조절되며 등록될 것 입니다.

- RotationSpeedAuthoring_IJobChunk : 

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

// ReSharper disable once InconsistentNaming
//이 스크립트가 인스펙터에 등록(게임 오브젝트에 등록)되기 위해선 Entity로 변환하는 컴포넌트가 필수 라는 것 같습니다.
//작성 시점 메뉴얼에 자세한 내용이 적혀있지 안았습니다.
//대표적인 변환 컴포넌트로 ConverToEntity가 있겠습니다.
[RequiresEntityConversion] 
[AddComponentMenu("DOTS Samples/IJobChunk/Rotation Speed")]
//버전과 번호를 명시할 수 있습니다. 다음은 메뉴얼에서의 설명입니다.
//버전 번호를 선언하면 ComponentSystem은 자산 파이프 라인에 의해 캐시 된 데이터가 활성 코드를 사용하여
//준비되도록 할 수 있습니다. 변환 시스템 또는 최적화 시스템의 버전 번호가 변경되거나 새 변환 시스템이 추가되면 장면이 다시 변하게 됩니다.
[ConverterVersion("joe", 1)]
//상속받은 내용을 보면 알 수 있듯이, MonoBehaviour에서 동작하며, 변환 작업에 명시적으로 영향을 끼칠 수 있습니다.
public class RotationSpeedAuthoring_IJobChunk : MonoBehaviour, IConvertGameObjectToEntity
{
    public float DegreesPerSecond = 360.0F;

    // The MonoBehaviour data is converted to ComponentData on the entity.
    // MonoBehaviour 데이터는 Entitiy에서의 ComponentData로 변환됩니다.
    // We are specifically transforming from a good editor representation of the data (Represented in degrees)
    // 우리는 데이터의 기존의 익숙한 편집기 표현에서 구체적으로 변환하고 있습니다. (도 단위로 표시)
    // To a good runtime representation (Represented in radians)
    // 실제 런타임에서 동작하는 라디안타임으로 변환했습니다.
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var data = new RotationSpeed_IJobChunk { RadiansPerSecond = math.radians(DegreesPerSecond) };
        dstManager.AddComponentData(entity, data);
    }
}

 

- RotationSpeedSystem_IJobChunk : 

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

// This system updates all entities in the scene with both a RotationSpeed_IJobChunk and Rotation component.
// 이 시스템은 장면의 모든 엔티티를 RotationSpeed_IJobChunk 및 Rotation 컴포넌트로 모두 업데이트합니다.
// ReSharper disable once InconsistentNaming
public class RotationSpeedSystem_IJobChunk : SystemBase
{
    EntityQuery m_Group;

	//생성시점에 호출됩니다.
    protected override void OnCreate()
    {
        // Cached access to a set of ComponentData based on a specific query
        // 특정 쿼리를 기반으로 한 컴포넌트데이터 세이트에 대한 캐시 된 접근
        //여기선 Rotation, 읽기 전용 RotationSpeed_IJobChunk 두가지를 쿼리할 것임을 알 수 있습니다.
        m_Group = GetEntityQuery(typeof(Rotation), ComponentType.ReadOnly<RotationSpeed_IJobChunk>());
    }

    // Use the [BurstCompile] attribute to compile a job with Burst. You may see significant speed ups, so try it!
    // [BurstCompie] 속성을 사용하면 작업이 버스트를 이용하여 컴파일 됩니다. 사용한 것만으로 상당한 속도 향상을 경험할수 있습니다.
    [BurstCompile]
    //IJobChunk는 작업 중 청크를 단위로 하는 작업을 명시할 수 있도록 합니다.
    struct RotationSpeedJob : IJobChunk
    {
        public float DeltaTime;
        //아래의 두 타입 명시 또한, 쿼리된 청크내에서 컴포넌트데이터 배열을 불러오기 위한 타입 명시입니다.
        //이를 어디서 초기화 하였는지는 이 구조체 아래의 OnUpdate()를 참조하세요.
        public ArchetypeChunkComponentType<Rotation> RotationType;
        [ReadOnly] public ArchetypeChunkComponentType<RotationSpeed_IJobChunk> RotationSpeedType;

		//청크와 청크의길이, 첫번째 엔티티의 인덱스를 제공하며 실행됩니다.
        public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
        	//청크된 Rotation 타입의 컴포넌트데이터를 배열로 불러옵니다
            var chunkRotations = chunk.GetNativeArray(RotationType);
            //청크된 RotationSpeed_IJobChunk 타입의 컴포넌트데이터를 배열로 불러옵니다.
            var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedType);
            
            for (var i = 0; i < chunk.Count; i++)
            {
                var rotation = chunkRotations[i];
                var rotationSpeed = chunkRotationSpeeds[i];

                // Rotate something about its up vector at the speed given by RotationSpeed_IJobChunk.
                // RotationSpeed-IJobChunk가 제공 한 속도로 위쪽 벡터에 대해 회전시킵니다.
                chunkRotations[i] = new Rotation
                {
                    Value = math.mul(math.normalize(rotation.Value),
                        quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
                };
            }
        }
    }//구조체 끝

    // OnUpdate runs on the main thread.
    // OnUpdate는 메인 쓰레드에서 구동됩니다.
    protected override void OnUpdate()
    {
        // Explicitly declare:
        // 아래 사항을 명시적으로 선언했습니다.
        // - Read-Write access to Rotation
        // - 읽기-쓰기 접근의 Rotation
        // - Read-Only access to RotationSpeed_IJobChunk
        // - 읽기전용 접근의 RotationSpeed_IJobChunk
        var rotationType = GetArchetypeChunkComponentType<Rotation>();
        var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed_IJobChunk>(true);

		//작업을 선언합니다.
        //C# Job System에 대해 이해가 불가하면 해당 블로그에 저장한 글이 있습니다.
        var job = new RotationSpeedJob()
        {
            RotationType = rotationType,
            RotationSpeedType = rotationSpeedType,
            DeltaTime = Time.DeltaTime
        };

		//작업 스케줄
        Dependency = job.Schedule(m_Group, Dependency);
    }
}
반응형