2022.03.13 - [유니티/ECS] - Unity ECS - 일단시작 1
Unity ECS - 일단시작 1
2021.09.26 - [이론/설계] - ECS (Entity Component System) ECS (Entity Component System) ECS 정의 "A different paradigm of writing code, where we model our programs in a data oriented way" 유니티 EC..
tsyang.tistory.com
이전 글에서는 일단 큐브를 만들어서 ECS시스템으로 움직이도록 했다.
또한, 이전 글에서는
[GenerateAuthoringComponent]를 이용하여 Entity에 Component를 추가하고,
Entities.Foreach를 이용하여 System 내에서 적절한 Entity를 찾고 작업을 수행했다.
이번에는 Entity에 Component를 추가하고 System에서 Entity를 업데이트하는 또 다른 방법을 다룬다.
새로운 방법을 이용해서 이전 글에서 구현한 큐브를 회전도 시켜보자.
MonoBehaviour를 Component로
MonoBehaviour가 아래와 같은 IConvertGameObjectToEntity 인터페이스를 구현하도록 하면 ECS Entity에 Component를 추가할 수 있다.
public interface IConvertGameObjectToEntity
{
void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem);
}
먼저 어떤 속도로 큐브를 회전시킬지 정하는 회전 속도에 대한 컴포넌트가 필요하므로 만들어준다.
[Serializable]
public struct RotationSpeed : IComponentData
{
public float RadiansPerSec;
}
이전 글의 Velocity 컴포넌트처럼 [GenerateAuthoringComponent] 어트리뷰트를 달아주면 GameObject의 Inspector에 해당 컴포넌트를 추가해 줄 수 있다.

그 대신!!
public class RotationSpeedAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
public float DegreesPerSecond = 360.0f;
public void Convert(
Entity entity,
EntityManager dstManager,
GameObjectConversionSystem conversionSystem)
{
var data = new RotationSpeed{RadiansPerSec = math.radians(DegreesPerSecond)};
dstManager.AddComponentData(entity, data);
}
}
위와 같은 코드를 추가하자.
당연히 MonoBehaviour이기 때문에 Inspector에서 게임 오브젝트에 스크립트를 추가할 수 있다.
구현한 Convert 메서드에서는 해당 게임 오브젝트가 컨버팅 될 때 Entity에 어떤 컴포넌트를 추가해줄지를 정해준다. 위 코드에서는 RotationSpeed 컴포넌트를 추가해주었다.
dstManager란 DestinationWorld의 매니저이다.
AddComponentData를 통해 해당 오브젝트가 컨버팅 될 Entity에 컴포넌트를 추가한다.
나는 여기에 속도를 랜덤으로 지정해주는 기능을 추가하고자 한다.
public class RotationSpeedAuthoring
: MonoBehaviour, IConvertGameObjectToEntity
{
public float DegreesPerSecond = 360.0f;
public bool IsRandom = false;
private void Awake()
{
if (IsRandom)
DegreesPerSecond = UnityEngine.Random.Range(0, 360);
}
public void Convert(
Entity entity,
EntityManager dstManager,
GameObjectConversionSystem conversionSystem)
{
var data = new RotationSpeed{RadiansPerSec = math.radians(DegreesPerSecond)};
dstManager.AddComponentData(entity, data);
}
}
Awake()가 Convert()보다 먼저 호출되므로 저렇게 랜덤 한 값을 넣어줄 수도 있다.
여기에 더해서 커스텀 에디터도 작업해주자.
([GenerateAuthoringComponent] 도 CustomEditor를 만들 수 있는지는 모르겠음. 귀찮아서 안 해봄)
Random값을 사용한다면 DegreesPerSecond를 감추도록 하겠다.
public class RotationSpeedAuthoringEditor : Editor
{
public override void OnInspectorGUI()
{
var script = target as RotationSpeedAuthoring;
script.IsRandom = EditorGUILayout.Toggle("Is Random", script.IsRandom);
if (false == script.IsRandom)
script.DegreesPerSecond = EditorGUILayout.FloatField
("Degrees Per Second", script.DegreesPerSecond);
}
}

커스텀 에디터도 잘 동작한다.
이제 게임을 실행시키면~!

RotationSpeed가 잘 들어가 있는 걸 확인할 수 있다.
이처럼 IConvertGameObjectToEntity를 통한 방법은 [GenerateAuthoringComponent]를 통한 방법보다 더 유연하다.
청크를 받아와 로직 업데이트

이전 글에서는 위와 같은 방법으로 아래 과정을 수행했다.
- 어떤 엔티티를 받아올지 정한다.
- 엔티티 단위로 작업을 뿌려준다.
이번에는 EntityQuery를 이용해 1번을 수행해보자.
public class RotationSpeedSystem : SystemBase
{
private EntityQuery _query;
protected override void OnCreate()
{
_query = GetEntityQuery(
ComponentType.ReadWrite<Rotation>(),
ComponentType.ReadOnly<RotationSpeed>());
}
}
위와 같은 코드를 작성해주면 _query는 Rotation과 RotationSpeed를 가진 엔티티들을 가져오는 쿼리가 된다.
여기에 더해 EntityQueryDesc을 통해 특정 컴포넌트를 가지고 있으면 제외한다거나, 제시한 컴포넌트 중 하나라도 가지고 있으면 된다던가 하는 좀 더 복잡한 쿼리를 지정해줄 수 있다.
또한 여러 필터들도 추가해줄 수 있다.
(ex. setChangeUnversionFilter로 변화가 없는 엔티티들이 있는 청크는 건너 뜀)
그다음에 2번을 위해서 청크 단위로 작업을 해줄 Job 클래스를 만들어준다. Job 클래스는 IJobEntityBatch의 Execute()를 구현해주면 된다.
[BurstCompile] //Job이 BurstCompile되도록 하는 어트리뷰트
struct RotationSpeedJob : IJobEntityBatch
{
public float DeltaTime;
public ComponentTypeHandle<Rotation> RotationTypeHandle;
[Unity.Collections.ReadOnly]
public ComponentTypeHandle<RotationSpeed> RotationSpeedTypeHandle;
public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
{
NativeArray<Rotation> chunkRotations =
batchInChunk.GetNativeArray(RotationTypeHandle);
NativeArray<RotationSpeed> chunkRotationSpeeds =
batchInChunk.GetNativeArray(RotationSpeedTypeHandle);
for (int i = 0; i < batchInChunk.Count; ++i)
{
var rotation = chunkRotations[i];
var rotationSpeed = chunkRotationSpeeds[i];
//실제 객체를 회전시키는 부분
chunkRotations[i] = new Rotation
{
Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSec * DeltaTime))
};
}
}
}
Rotation와 RotationSpeed에 접근하기 위해 Handle을 지정해준다. 어떤 Handle을 쓸지는 외부에서 주입해줄 수 있다.
protected override void OnUpdate()
{
var rotationTypeHandle = GetComponentTypeHandle<Rotation>(); //RW
var rotationSpeedTypeHandle = GetComponentTypeHandle<RotationSpeed>(isReadOnly: true); //R
var job = new RotationSpeedJob()
{
RotationTypeHandle = rotationTypeHandle,
RotationSpeedTypeHandle = rotationSpeedTypeHandle,
DeltaTime = Time.DeltaTime * CubeGM.SimulationSpeed
};
Dependency = job.ScheduleParallel(_query, batchesPerChunk : 1, Dependency);
}
그리고 RotationSpeedSystem의 OnUpdate를 위와 같이 구현해준다.
여기서 ScheduleParallel의 batchesPerChunk 옵션을 설명하자면, 이 값이 1일 때 청크 하나를 모두 처리하겠다는 의미가 된다. 따라서 이 batchesPerChunk가 1일 때 효율적이다.
그러나, 청크에 들어가는 엔티티의 숫자가 적고 수행해야 할 작업이 오래 걸린다면 청크를 여러 batch로 쪼개어 작업하는 게 병렬 프로세싱의 이점을 취하는 방법일 수 있다.
이제 실행시키면~

큐브의 Rotation과 Position이 잘 변화하는 것을 볼 수 있다.
Rotation의 변경은 IConvertGameObjectToEntity + IJobEntityBatch를 사용하였고,
Position의 변경은 이전 글에서 작성한 GenerateAuthoringComponent + Entities.Foreach를 사용하였다.
'짬통' 카테고리의 다른 글
| DOTS 1.0 - 2 (생성, MonoBehavour 연계) (0) | 2023.02.04 |
|---|---|
| DOTS 1.0 - 기본 (Component,System,Aspect,Job) (1) | 2023.01.29 |
| DOTS 1.0 나온 기념 세팅법 (2) | 2023.01.01 |
| 일단 시작3 - Instantiate & Destory (0) | 2022.03.23 |
| DOTS - 일단 시작 1 (1) | 2022.03.13 |