카테고리 없음

Unity - ECSAmples 살펴보기 #3 /HelloCube의 SpawnAndRemove

JAVART 2020. 8. 5. 13:57
반응형

2020/08/04 - [Unity/DOTS] - Unity - ECSSamples 살펴보기 #2 /HelloCube의 SubScene, SpawnFromMonoBehaviour, SpawnFromEntity

 

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-Te..

javart.tistory.com

참고 사항 : 모든 기본 정보는 1편에 기재되어 있습니다.

 

HelloCube

6. SpawnAndRemove

 - ReadMe : 

# HelloCube: SpawnAndRemove

This sample demonstrates spawning and removing Entities from the World.
-> 이 예제는 세계(월드)에 엔티티를 소환하고 제거하기 위한 설명입니다.

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

1. Two different methods of adding and removing Entities from the World
    - From data
    - From code (by manually loading the resource and creating the Entity)
-> 1. 세계에 엔티티를 추가하거나 제거하는 데엔 두 가지 방법이 있습니다.
    - Data에서
    - Code에서(자원을 수동으로 불러오고 엔티티를 생성합니다.)

2. The performance characteristics of adding and removing Entities from the World does not change due to fragmentation of chunks.
-> 2. 세계에서 엔티티를 추가하고 제거하는 성능 특성은 청크 조각화로 인해 변경되지 않습니다.

3. Chunk utilization widget histogram activity in the EntityDebugger.
-> 3. EntityDebugger의 청크 활용도 위젯 히스토그램 작업입니다.

When the example starts, Entities are added into the World via a SpawnJob in the same way as the HelloCube SpawnFromEntity sample demonstrates. In this example, each red cube is an Entity that has a LifeTime Component. After the Entity's life time expires, the Entity will be destroyed and its position queued for respawn at some later point.
-> 예제가 시작되면 HelloCube/SpawnFromEntity 샘플이 보여주는 것과 동일한 방식으로 SpawnJob을 통해 엔티티가 월드에 추가됩니다. 아 예에서 각 빨간색 큐브는 LifeTime 컴포넌트가 있는 엔티티입니다. 엔티티의 수명이 만료된 후 엔티티는 소멸되고 위치는 나중에 다시 큐에 대기합니다.

Another part of the code shows loading Prefabs from code and creating Entities. Once a new Entity is created, it will be placed at the same position in the World where the originally spawned Entity was, but it will be colored green.
-> 코드의 또 다른 부분은 코드에서 프리팹을 로드하고 엔티티를 생성하는 것을 보여줍니다. 새로운 엔티티가 생성되면 원래 생성 된 엔티티가 있던 월드의 동일한 위치에 배치되지만 녹색으로 표시됩니다.

 

- SpawnerAuthoring_SpawnAndRemove : 

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

// ReSharper disable once InconsistentNaming
[RequiresEntityConversion]
[AddComponentMenu("DOTS Samples/SpawnAndRemove/Spawner")]
[ConverterVersion("joe", 1)]
public class SpawnerAuthoring_SpawnAndRemove : 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_SpawnAndRemove
        {
            // The referenced prefab will be converted due to 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_SpawnAndRemove : 

using Unity.Entities;

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

 

- SpawnerSystem_SpawnAndRemove : 

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

// 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.

// ReSharper disable once InconsistentNaming
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class SpawnerSystem_SpawnAndRemove : SystemBase
{
    // BeginInitializationEntityCommandBufferSystem is used to create a command buffer which will then be played back
    // when that barrier system executes.
    //
    // Though the instantiation command is recorded in the SpawnJob, it's not actually processed (or "played back")
    // until the corresponding EntityCommandBufferSystem is updated. To ensure that the transform system has a chance
    // to run on the newly-spawned entities before they're rendered for the first time, the SpawnerSystem_FromEntity
    // will use the BeginSimulationEntityCommandBufferSystem to play back its commands. This introduces a one-frame lag
    // 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
        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. 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 job that will add Instantiate commands to the EntityCommandBuffer.
        // 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)
        // The actual job will be cached once it is compiled (it will only get Burst compiled once).
        Entities
            .WithName("SpawnerSystem_SpawnAndRemove")
            .WithBurst(FloatMode.Default, FloatPrecision.Standard, true)
            .ForEach((Entity entity, int entityInQueryIndex, in Spawner_SpawnAndRemove spawner, in LocalToWorld location) =>
        {
            var random = new Random(1);

            for (var x = 0; x < spawner.CountX; x++)
            {
                for (var y = 0; y < spawner.CountY; y++)
                {
                    var instance = commandBuffer.Instantiate(entityInQueryIndex, spawner.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.SetComponent(entityInQueryIndex, instance, new LifeTime { Value = random.NextFloat(10.0F, 100.0F) });
                    commandBuffer.SetComponent(entityInQueryIndex, instance, new RotationSpeed_SpawnAndRemove { RadiansPerSecond = math.radians(random.NextFloat(25.0F, 90.0F)) });
                }
            }

            commandBuffer.DestroyEntity(entityInQueryIndex, entity);
        }).ScheduleParallel();

        // SpawnJob runs in parallel with no sync point until the barrier system executes.
        // When the barrier system executes we want to complete the SpawnJob and then play back the commands
        // (Creating the entities and placing them). We need to tell the barrier system which job it needs to
        // complete before it can play back the commands.
        m_EntityCommandBufferSystem.AddJobHandleForProducer(Dependency);
    }
}

 

- RotationSpeed_SpawnAndRemove : 

using System;
using Unity.Entities;

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

 

 - RotationSpeedAuthoring_SpawnAndRemove : 

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

// ReSharper disable once InconsistentNaming
[RequiresEntityConversion]
[AddComponentMenu("DOTS Samples/SpawnAndRemove/Rotation Speed")]
[ConverterVersion("joe", 1)]
public class RotationSpeedAuthoring_SpawnAndRemove : MonoBehaviour, IConvertGameObjectToEntity
{
    public float DegreesPerSecond = 360;

    // The MonoBehaviour data is converted to ComponentData on the entity.
    // 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)
    {
        dstManager.AddComponentData(entity, new RotationSpeed_SpawnAndRemove { RadiansPerSecond = math.radians(DegreesPerSecond) });
        dstManager.AddComponentData(entity, new LifeTime { Value = 0.0F });
    }
}

 

- RotationSpeedSystem_SpawnAndRemove : 

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_SpawnAndRemove and Rotation component.

// ReSharper disable once InconsistentNaming
public class RotationSpeedSystem_SpawnAndRemove : SystemBase
{
    // OnUpdate runs on the main thread.
    protected override void OnUpdate()
    {
        var deltaTime = Time.DeltaTime;
        
        // The in keyword on the RotationSpeed_SpawnAndRemove component tells the job scheduler that this job will not write to rotSpeedSpawnAndRemove
        Entities
            .WithName("RotationSpeedSystem_SpawnAndRemove")
            .ForEach((ref Rotation rotation, in RotationSpeed_SpawnAndRemove rotSpeedSpawnAndRemove) =>
        {
            // Rotate something about its up vector at the speed given by RotationSpeed_SpawnAndRemove.
            rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), rotSpeedSpawnAndRemove.RadiansPerSecond * deltaTime));
        }).ScheduleParallel();
    }
}

- LifeTimeSystem : 

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

public struct LifeTime : IComponentData
{
    public float Value;
}

// This system updates all entities in the scene with both a RotationSpeed_SpawnAndRemove and Rotation component.
public class LifeTimeSystem : SystemBase
{
    EntityCommandBufferSystem m_Barrier;

    protected override void OnCreate()
    {
        m_Barrier = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    }
    
    // OnUpdate runs on the main thread.
    protected override void OnUpdate()
    {
        var commandBuffer = m_Barrier.CreateCommandBuffer().ToConcurrent();

        var deltaTime = Time.DeltaTime;
        Entities.ForEach((Entity entity, int nativeThreadIndex, ref LifeTime lifetime) =>
        {
            lifetime.Value -= deltaTime;

            if (lifetime.Value < 0.0f)
            {
                commandBuffer.DestroyEntity(nativeThreadIndex, entity);
            }
        }).ScheduleParallel();
            
        m_Barrier.AddJobHandleForProducer(Dependency);
    }
}

 


해당 예제의 Scene 상태

 

 

위와 같이 되어있고, 결과적으로 

 

Spawner 그룹이 생성을 담당하고 (생성시 라이프타임과 회전에 대한 컴포넌트 추가)

Rotation 그룹이 회전을 담당하고

LifeTime 그룹이 소멸을 담당하는 것을 보실 수 있습니다.

 


HelloCube 예제는 이것으로 끝 입니다. 

 

Advanced와 StressTests도 있습니다만, 당장에 필요해보이진 않아서 잠시 묻어두고 좀더 현 레벨에 맞는 다른 것을 찾아보거나 만들어보려 합니다.  

반응형