본문 바로가기

Unity/DOTS

Unity - ECSSamples 살펴보기 #2 /HelloCube의 SubScene, SpawnFromMonoBehaviour, SpawnFromEntity

반응형

2020/08/03 - [Unity/DOTS] - Unity - ECSSamples 살펴보기 #1 - /HelloCube의 ForEach와 IJobChunk

 

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

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

javart.tistory.com

참고 사항 : 기본적인 정보는 1편에 적혀있습니다.

 

HelloCube

3. SubScene

- ReadMe : 

# HelloCube: SubScene

This sample demonstrates the Sub Scene workflow.
-> 이 예제는 Sub Scene 워크플로에 대해 설명합니다.

Sub Scenes provide an efficient way to edit and load large game scenes in Unity.
-> Sub Scene은 Unity에서 대규모 게임 장면을 편집하고 로드하는 효율적인 방법을 제공합니다.

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

This sample uses the Components and Systems from HelloCube ForEach. The scene contains a pair of rotating cubes that are loaded automatically on play from a Sub Scene.
-> 이 예제에서는 HelloCube/ForEach의 컴포넌트 및 시스템을 사용합니다. 해당 Scene에는 Sub Scene에서 재생시 자동으로 로드되는 한 쌍의 회전 큐브가 포함됩니다.

## Sub Scenes
## 하위 장면들

When you save a scene, Unity converts any Sub Scenes to a native, binary format.
-> Scene을 저장하면, Unity는 모든 Sub Scene들을 기본 이진 형식으로 변환합니다.
This format is memory-ready and can be loaded or streamed with only minimal alteration of the data in RAM. The format is ideally suited for streaming large amounts of Entities.
-> 이 형식은 메모리 준비가 되어 있으며 RAM에서 데이터를 최소한으로 변경하여 로드하거나 스트리밍할 수 있습니다. 이 형식은 대량의 Entity를 스트리밍하는 데 이상적입니다.

You can load a Sub Scene automatically on play. You can also defer loading until you stream the Sub Scene in from code (Using the RequestSceneLoaded component)
-> 재생시 Sub Scene을 자동으로 로드 할 수 있습니다. 코드에서 Sub SCeneㅇ르 스트리밍 할 때까지 로드를 연기 할 수도 있습니다. (RequestSceneLoaded 컴포넌트를 사용해야합니다.)

By default, Sub Scenes are loaded from the Entity binary files, even in the editor.
-> 기본적으로, Sub Scene은 편집기에서도 Entity 이진 파일에서 로드 됩니다.

You can select a Sub Scene and click the **Edit** button in the Unity Inspector window to edit it.
-> Sub Scene을 선택하고 Unity 인스펙터 창에서 **편집** 버튼을 클릭하여 편집할 수 있습니다.
While editing, you see the GameObject representations of the Entities in the Sub Scene in the Scene view and you can edit them as you would any GameObject.
-> 편집하는 동안 Scene 뷰에 Sub Scene에 있는 Entity의 게임 오브젝트 표현이 표시되며, 다른 게임 오브젝트처럼 편집 할 수 있습니다.
A **live link** conversion pipeline applies any changes you make to the Game view scene.
-> **Live Link** 변환 파이프 라인은 Game 뷰 Scene의 모든 변경 사항을 적용합니다.

The ability to edit only part of the scene and still have all the other Sub Scenes loaded as Entities for context, creates a very scalable workflow for editing massive scenes.
-> Scene의 일부만 편집하고 컨텍스트에 대한 Entity로 다른 모든 Sub Scene들을 로드 할 수있는 기능은 대규모 Scene을 편집하기 위한 확장 가능한 워크 플로우를 만듭니다.

### To Create a Sub Scene
### Sub Scene 만들기

1. In the Unity Hierarchy window, right-click on empty space, or on a GameObject that you want to create the Sub Scene next to.
-> 1. Unity의 Hierarchy 창에서 빈 공간이나 옆에 Sub Scene을 생성하려는 GameObject를 마우스 오른쪽 버튼으로 클릭하십시오.

2. Select **New Sub Scene > Empty Scene...** in the context menu. Unity then creates an empty Sub Scene and creates a corresponding Scene Asset file in your project.
-> 2. 상황에 맞는 메뉴에서 **New Sub Scne > Empty Scene...**을 선택합니다. 그런 다음 Unity는 빈 Sub Scene을 생성하고 프로젝트에 해당 Scene Asset 파일을 생성합니다.

Click on the Sub Scene in the Hierarchy window to view its properties in the Inspector.
-> Hierarchy 창에서 Sub Scene을 클릭하여 Inspector에서 속성을 봅니다.

# 스크립트가 필요한 것이 아니므로, 해당 예제에서 설명한 것도 여기서 끝입니다.

 

4. SpawnFromMonoBehaviour

- ReamMe : 

# HelloCube: SpawnFromMonoBehaviour

This sample demonstrates how to spawn Entities and Components using a Prefab GameObject. The scene spawns a "field" of the pairs of spinning cubes.
-> 이 예제는 프리팹 게임 오브젝트를 사용하여 어떻게 엔티티와 컴포넌트를 소환하는지에 대해 설명합니다. 이 Scene에서는 회전하는 큐브 쌍의 "field"를 생성합니다.

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

This sample uses the Components and Systems from HelloCube ForEach.
-> 이 예제는 HelloCube/ForEach의 컴포넌트와 시스템을 사용합니다.

Unity.Entities provides a GameObjectConversionUtility to convert a GameObject hierarchy to its Entity representation. With this utility, you can convert a Prefab into an Entity representation and use that representation to spawn new instances whenever needed.
-> Unity.Entities는 GameObjectConversionUtility를 제공하여 게임 오브젝트 계층을 Entity로 표현할 수 있도록 합니다. 이 유틸리티를 사용하면 Prefabv을 Entity로 변환하고 해당 표현을 사용하여 필요할 때마다 새 인스턴스를 생성 할 수 있습니다.

When you instantiate the Entity prefab, the whole prefab representation is cloned as a group, in the same way that instantiating a Prefab based on GameObjects does.
-> 엔티티 프리팹을 인스턴스화하면 GameObjects를 기반으로 Prefab을 인스턴스화하는 것과 동일한 방식으로 전체 Prefab 표현이 그룹으로 복제됩니다.

The Spawner_FromMonoBehaviour class converts the Prefab to its Entity representation in the Start() method and then instantiates a field of spinning objects.
-> Spawner_FromMonoBehaviour 클래스는 Pregab을 Start() 메서드에서 Entity 표현으로 변환 한 다음 회전 객체 필드를 인스턴스화합니다.

 

- Spawner_FromMonoBehaviour : 

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

// ReSharper disable once InconsistentNaming
[AddComponentMenu("DOTS Samples/SpawnFromMonoBehaviour/Spawner")]
public class Spawner_FromMonoBehaviour : MonoBehaviour
{
    public GameObject Prefab;
    public int CountX = 100;
    public int CountY = 100;

    void Start()
    {
        // Create entity prefab from the game object hierarchy once
        // 게임오브젝트 계층을 이용해 엔티티 프리팹을 생성합니다.
        // 생성 전 설정을 만듭니다. 여기서는 어느 월드에 생성할 것인지에 대한 설정같습니다.
        var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
        // 게임오브젝트 프리팹과 설정을 이용해 엔티티 프리팹을 생성합니다.
        var prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, settings);
        //해당 월드의 엔티티 매니저를 불러옵니다.
        var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

//CountX * CountY의 오브젝트를 생성합니다.
        for (var x = 0; x < CountX; x++)
        {
            for (var y = 0; y < CountY; y++)
            {
                // Efficiently instantiate a bunch of entities from the already converted entity prefab
                // 이미 변환 된 엔티티 프리팹에서 여러 엔티티를 효율저긍로 인스턴스화합니다.
                var instance = entityManager.Instantiate(prefab);

                // Place the instantiated entity in a grid with some noise
                // 인스턴스화된 엔티티를 약간의 노이즈를 넣은 그리드에 배치합니다.
                var position = transform.TransformPoint(new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
                entityManager.SetComponentData(instance, new Translation {Value = position});
            }
        }
    }
}

 

#만약 회전 속도를 조절하고 싶다면 37번째 줄 아래에 다음과 같이 적용해보세요.

//37번째 줄 코드입니다. 이 코드 아래에 적용하세요.
entityManager.SetComponentData(instance, new Translation {Value = position});
//이 코드입니다. 이렇게하면 생성시 위치에 따른 회전속도를 조절 할 수 있습니다.
entityManager.SetComponentData(instance, new RotationSpeed_ForEach { RadiansPerSecond = x * y + 10 });

 

 

5. SpawnFromEntity

 - ReadMe : 

# HelloCube: SpawnFromEntity

This sample demonstrates a different way to spawn Entities and Components using a Prefab GameObject. Like the previous example, the scene spawns a "field" of the pairs of spinning cubes.
-> 이 예제는 게임오브젝트 프리팹을 사용하여 엔티티와 컴포넌트를 생성하는 다른 방법에 대해 설명합니다. 이전 예제와 마찬가지로 Scene은 회전 큐브 쌍의 "field"를 생성합니다.

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

There are two key differences to the way Entities were spawned in the previous example:
-> 이전 예제에서 엔티티가 생성되는 방식에는 두 가지 주요 차이점이 있습니다.

1. In this example, the Prefab GameObject is converted to an Entity representation by the SpawnerAuthoring_FromEntity MonoBehaviour, in its IConvertGameObjectToEntity.Convert method.
-> 1. 이 예제에서 게임오브젝트 프리팹은 IConvertGameObjectToEntity.Convert 메소드에서 SpawnerAuthoring_FromEntity에 의해 MonoBehaviour에서 Entity 표현으로 변환됩니다.

2. The example uses a JobSystem to spawn the Entities rather than a MonoBehaviour Start() method.
-> 2. 이 예제는 MonoBehaviour의 Start() 메소드가 아닌 JobSystem을 사용하여 엔티티를 생성합니다.

## Spawning entities
## 엔티티 소환하기

Component data can have references to other entities. In this case we have a reference to a entity prefab. Similar to GameObjects, when instantiating and destroying a prefab, the whole prefab is cloned or deleted as a group.
-> 구성요소 데이터는 다른 엔티티에 대한 참조를 가질 수 있습니다. 이 경우 엔티티 프리팹에 대한 참조가 있습니다. 게임 오브젝트와 유사하게 프리팹을 인스턴스화하고 파괴 할 때 전체 프리팹이 복제되거나 그룹으로 삭제됩니다.

This way you can write all of your runtime code using a System and ComponentData.
-> 이렇게하면 System 및 컴포넌트데이터를 사용하여 모든 런타임 코드를 작성할 수 있습니다.

The SpawnerSystem_FromEntity looks for any Spawner_FromEntity Component.
-> SpawnerSystem_FromEntity는 Spawner_FromEntity 구성 요소를 찾습니다.
When it finds one, the System instantiates the prefabs in a grid and then destroys the spawner Entity (so that it only spawns a given set of Entities once).
-> 하나를 찾으면 시스템은 그리드에서 프리팹을 인스턴스화 한 다음 스포너 엔티티를 파괴합니다. (따라서 주어진 엔티티 세트를 한 번만 스폰합니다.)

The SpawnerSystem_FromEntity uses an Entities.ForEach, which is very similar to the those demonstrated in earlier examples. The difference is that this Entities.ForEach provides the Entity object (and the entity index in query) to your lambda. The Entity object is required so that the System can destroy the Spawner Entity once it has been processed.
-> SpawnerSystem_FromEntity는 Entites.ForEach를 사용하는데, 이는 이전 예제에서 보여준 것과 매우 유사합니다. 차이점은 이 Entities.ForEach는 Entity 객체(및 쿼리의 엔티티 인덱스)를 람다에 제공한다는 것 입니다. Entity 오브젝트는 시스템이 처리 된 후 Spawner Entity를 파기할 수 있도록 하기 위해 필요합니다.

Another important concept that SpawnerSystem_FromEntity illustrates is the use of an EntityCommandBuffer and a EntityCommandBufferSystem. To prevent race conditions, you cannot make _structural changes_ inside a Job. Structural changes include anything that changes the structure of your data, such as creating/destroying Entities or adding/removing Components. To overcome this limitation, ECS provides the EntityCommandBuffer.
-> SpawnerSystem_FromEntity가 보여주는 또 다른 중요한 개념은 EntityCommandBuffer와 EntityCommandBufferSystem을 사용한다는 것 입니다. 경쟁 조건을 방지하기 위해 작업 앤에서 _구조적 변경_을 수행 할 수 없습니다. 구조 변경에는 엔티티 생성 / 파기 또는 구성요소 추가 / 제거와 같은 데이터 구조를 변경하는 모든 것이 포함됩니다. 이 제한을 극복하기 위해 ECS는 EntityCommandBuffer를 제공합니다.

Instead of performing structural changes directly, a Job can add a command to an EntityCommandBuffer to perform such changes on the main thread after the Job has finished. Command buffers allow you to perform any, potentially costly, calculations on a worker thread, while queuing up the actual insertions and deletions for later.
-> 이 작업은 구조적 변경을 직접 수행하는 대신 EntityCommandBuffer에 명령을 추가하여 작업이 완료된 후 기본 스레드에서 이러한 변경을 수행 할 수 있습니다. 명령 버퍼를 사용하면 작업자 스레드에서 잠재적으로 비용이 많이 드는 계산을 수행하는 동시에 나중에 실제 삽입 및 삭제를 대기시킬 수 있습니다.

The EndSimulationBarrier is a standard ECS System that provides an EntityCommandBuffer for any System to use. EndSimulationBarrier automatically executes any commands in this buffer when the System runs at the end of a frame.
-> EndSimulationBarrier는 모든 시스템에서 사용할 수 있는 EntityCommandBuffer를 제공하는 표준 ECS 시스템입니다. EndSimulationBarrier는 시스템이 프레임 끝에서 실행될 때 이 버퍼의 명령을 자동으로 실행합니다.

 

- SpawnerAuthoring_FromEntity : 

using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

//Entity로 변환 된 후 큐브 field를 생성할 수 있도록 컴포넌트 데이터를 추가합니다.

// ReSharper disable once InconsistentNaming
[RequiresEntityConversion]
[AddComponentMenu("DOTS Samples/SpawnFromEntity/Spawner")]
[ConverterVersion("joe", 1)]
//IDeclareReferencedPrefabs - 참조된 프리팹 선언으로 변환 작업 전에 미리 선언할 수 있도록 합니다.
public class SpawnerAuthoring_FromEntity : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
    public GameObject Prefab;
    public int CountX;
    public int CountY;

    // Referenced prefabs have to be declared so that the conversion system knows about them ahead of time
    // 변환 시스템이 사전에 미리 알 수 있도록 참조된 프리팹을 선언해야합니다.
    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    {
        referencedPrefabs.Add(Prefab);
    }

    // Lets you convert the editor data representation to the entity optimal runtime representation
    // 편집기 데이터 표혀녀을 엔티티 최적 런타임 표현으로 변환 할 수 있습니다.
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var spawnerData = new Spawner_FromEntity
        {
            // The referenced prefab will be converted due to DeclareReferencedPrefabs.
            // DeclareReferencedPrefabs로 인해 참조 된 프리팹이 변환됩니다.
            // So here we simply map the game object to an entity reference to that prefab.
            // 여기서는 게임 오브젝트를 해당 프리팹에 대한 엔티티 참조에 매핑합니다.
            Prefab = conversionSystem.GetPrimaryEntity(Prefab),
            CountX = CountX,
            CountY = CountY
        };
        dstManager.AddComponentData(entity, spawnerData);
    }
}

 

- Spawner_FromEntity : 

using Unity.Entities;

// ReSharper disable once InconsistentNaming
public struct Spawner_FromEntity : IComponentData
{
    public int CountX;
    public int CountY;
    public Entity Prefab;
}

 

- SpawnerSystem_FromEntity : 

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

// Systems can schedule work to run on worker threads.
// 시스템은 작업자 스레드에서 실행되도록 작업을 예약 할 수 있습니다.
// However, creating and removing Entities can only be done on the main thread to prevent race conditions.
// 그러나, 엔티티 생성 및 제거는 경쟁 조건을 방지 하기 위해 메인 스레드에서만 수행 할 수 있습니다.
// The system uses an EntityCommandBuffer to defer tasks that can't be done inside the Job.
// 시스템은 EntitycommandBuffer를 사용하여 작업 내에서 수행 할 수 없는 작업을 연기합니다.

// ReSharper disable once InconsistentNaming
// 시스템의 업데이트 주기를 결정합니다. "SimulationSystemGroup"은 Update 루프 단계의 끝에서 실행되도록 하는 것 입니다.
// 자세한 정보는 아래에 링크 "System Update Order"를 확인해주세요.
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class SpawnerSystem_FromEntity : SystemBase
{
    // BeginInitializationEntityCommandBufferSystem is used to create a command buffer which will then be played back
    // BeginInitializationEntityCommandBufferSystem은 명령 버퍼를 작성하는 데 사용됩니다.
    // when that barrier system executes.
    // barrier system이 실행될 때 BeginInitializationEntityCommandBufferSystem도 실행됩니다.
    // Though the instantiation command is recorded in the SpawnJob, it's not actually processed (or "played back")
    // 인스턴스화 명령은 SpawnJob에 기록되지만 실제로 처리되지는 않습니다.
    // until the corresponding EntityCommandBufferSystem is updated. To ensure that the transform system has a chance
    // 해당 EntityCommandBufferSystem이 업데이트 될 때까지 변환 시스템이
    // to run on the newly-spawned entities before they're rendered for the first time, the SpawnerSystem_FromEntity
    // 새로 생성된 엔티티가 처음으로 렌더링 되기 전에 생성된 엔티티에서 실행될 수 있도록 하기 위해 SpawnerSystem_FromEntity는
    // will use the BeginSimulationEntityCommandBufferSystem to play back its commands. This introduces a one-frame lag
    // BeginSimulationEntityCommandBufferSystem을 이용하여 해당 명령을 재생합니다. 이것은 한 프레임 지연을 발생케합니다.
    // between recording the commands and instantiating the entities, but in practice this is usually not noticeable.
    // 이 행위는 명령을 기록하고 엔티티를 인스턴스화하는 것 사이에 프레임 지연을 발생케 하지만 실제로 눈에 띄지 않습니다.
    BeginInitializationEntityCommandBufferSystem m_EntityCommandBufferSystem;

    protected override void OnCreate()
    {
        // Cache the BeginInitializationEntityCommandBufferSystem in a field, so we don't have to create it every frame
        // 필드에서 BeginInitializationEntityCommandBufferSystem을 캐시하므로 매 프레임마다 시스템을 작성할 필요가 없습니다.
        m_EntityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate()
    {
        //Instead of performing structural changes directly, a Job can add a command to an EntityCommandBuffer to perform such changes on the main thread after the Job has finished.
        // 작업은 구조적 변경을 직접 수행하는 대신 EntityCommandBuffer에 명령을 추가하여 작업이 완료된 후 기본 스레드에서 이러한 변경을 수행할 수 있습니다.
        //Command buffers allow you to perform any, potentially costly, calculations on a worker thread, while queuing up the actual insertions and deletions for later.
        //명령 버퍼를 사용하면 작업자 스레드에서 잠재적으로 비용이 많이 드는 계산을 수행하는 동시에 나중에 실제 삽입 및 삭제를 대기시킬 수 있습니다.
        var commandBuffer = m_EntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();

        // Schedule the Entities.ForEach lambda job that will add Instantiate commands to the EntityCommandBuffer.
        // EntityCommandBuffer에 Instantiate명령을 추가 할 Entities.ForEach 람다 작업을 예약합니다.
        // Since this job only runs on the first frame, we want to ensure Burst compiles it before running to get the best performance (3rd parameter of WithBurst)
        // 이 작업은 첫 번째 프레임에서만 실행되므로 최상의 성능을 얻기 위해 실행하기 전에 버스트가 컴파일하도록 해야합니다.(WithBurst의 세번째 매개변수)
        // The actual job will be cached once it is compiled (it will only get Burst compiled once).
        // 실제 작업은 일단 컴파일 되면 캐시됩니다.(버스트는 한 번만 컴파일 됩니다.)
        
        Entities
            .WithName("SpawnerSystem_FromEntity")
            .WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
            .ForEach((Entity entity, int entityInQueryIndex, in Spawner_FromEntity spawnerFromEntity, in LocalToWorld location) =>
        {
            for (var x = 0; x < spawnerFromEntity.CountX; x++)
            {
                for (var y = 0; y < spawnerFromEntity.CountY; y++)
                {
                // 커맨드 버퍼를 이용해 생성을 예약합니다.
                // 이렇게 함으로서 생성에 필요로 하는 시간은 별도로 계산하고, 생성에 필요한 위치 정보 값을 빠르게 계산하고 넘어가는 것 입니다.
                    var instance = commandBuffer.Instantiate(entityInQueryIndex, spawnerFromEntity.Prefab);

                    // Place the instantiated in a grid with some noise
                    var position = math.transform(location.Value,
                        new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
                    commandBuffer.SetComponent(entityInQueryIndex, instance, new Translation {Value = position});
                }
            }

			// 마찬가지로 큐브를 생성케한 엔티티를 삭제하도록 예약만 하고 넘어가는 것 입니다.
            commandBuffer.DestroyEntity(entityInQueryIndex, entity);
        }).ScheduleParallel();

        // SpawnJob runs in parallel with no sync point until the barrier system executes.
        // SpawnJob은 barrier system이 실행될 때 까지 동기화 지점없이 병렬로 실행됩니다.
        // When the barrier system executes we want to complete the SpawnJob and then play back the commands (Creating the entities and placing them).
        // barrier system이 실행되면 SpawnJob을 완료 한 다음 명령을 재생합니다. (엔티티 생성 및 배치)
        // We need to tell the barrier system which job it needs to complete before it can play back the commands.
        // 명령을 재생하기 전에 어떤 작업을 완료해야하는지 barrier system에 알려야합니다.
        // Dependency는 상속받은 SystemBase에 속한 JobHandle을 통해 받는 것 입니다.
        m_EntityCommandBufferSystem.AddJobHandleForProducer(Dependency);
    }
}

 

#참고 문서

System Update Order

 

System Update Order | Entities | 0.13.0-preview.24

System Update Order Use Component System Groups to specify the update order of your systems. You can place a systems in a group using the [UpdateInGroup] attribute on the system’s class declaration. You can then use [UpdateBefore] and [UpdateAfter] attri

docs.unity3d.com

 

반응형