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);
}
}
위와 같이 되어있고, 결과적으로
Spawner 그룹이 생성을 담당하고 (생성시 라이프타임과 회전에 대한 컴포넌트 추가)
Rotation 그룹이 회전을 담당하고
LifeTime 그룹이 소멸을 담당하는 것을 보실 수 있습니다.
HelloCube 예제는 이것으로 끝 입니다.
Advanced와 StressTests도 있습니다만, 당장에 필요해보이진 않아서 잠시 묻어두고 좀더 현 레벨에 맞는 다른 것을 찾아보거나 만들어보려 합니다.