1. HelloCube_MainThreadSubscene
RotationSystem.cs
using Unity.Burst; using Unity.Entities; using Unity.Transforms; namespace HelloCube.MainThread { public partial struct RotationSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<ExecuteMainThread>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { float deltaTime = SystemAPI.Time.DeltaTime; // 모든 엔티티에 대해 LocalTransform 컴포넌트와 RotationSpeed 컴포넌트가 있는지 반복합니다. // 각 반복에서 transform은 LocalTransform에 대한 읽기-쓰기 참조로 할당되고, // speed는 RotationSpeed 컴포넌트에 대한 읽기 전용 참조로 할당됩니다. // SystemAPI.Query와 같은 작업은 메인 스레드에서 수행 foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>()) { // ValueRW와 ValueRO는 모두 실제 컴포넌트 값에 대한 참조를 반환합니다. // 차이점 // ValueRW는 읽기-쓰기 접근에 대한 안전성 검사를 수행 // ValueRO는 읽기 전용 접근에 대한 안전성 검사를 수행 transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime); } } } }
OnCreate()는 시스템이 생성될 때 호출됩니다.
RequireForUpdate를 사용하여 시스템 업데이트를 위해 ExecuteMainThread 컴포넌트가 필요함을 선언합니다.
OnUpdate()는 시스템이 업데이트될 때 호출됩니다.
SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>
를 사용하여 LocalTransform과 RotationSpeed 컴포넌트를 가진 모든 엔티티를 반복합니다.
transform은 LocalTransform 컴포넌트의 읽기-쓰기 참조를 나타내고,
speed는 RotationSpeed 컴포넌트의 읽기 전용 참조를 나타냅니다.
transform.ValueRW를 업데이트하여 회전 속도(RadiansPerSecond)와 deltaTime을 기반으로 엔티티를 회전시킵니다.
RotationSpeedAuthoring.cs
using UnityEngine; using Unity.Entities; using Unity.Mathematics; namespace HelloCube { // Authoring 컴포넌트는 일반적인 MonoBehavior입니다. public class RotationSpeedAuthoring : MonoBehaviour { public float DegreesPerSecond = 360.0f; // 베이킹 중에 이 Baker는 엔티티 서브씬에 있는 모든 RotationSpeedAuthoring 인스턴스에 대해 한 번 실행됩니다. // (Authoring 컴포넌트의 Baker 클래스를 중첩하는 것은 단지 스타일상의 선택일 뿐입니다.) class Baker : Baker<RotationSpeedAuthoring> { public override void Bake(RotationSpeedAuthoring authoring) { // 엔티티가 이동됩니다. var entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new RotationSpeed { RadiansPerSecond = math.radians(authoring.DegreesPerSecond) }); } } } // RotationSpeed 구조체는 IComponentData를 구현합니다. public struct RotationSpeed : IComponentData { public float RadiansPerSecond; } }
Baker 클래스는 Baker를 상속받는 중첩 클래스입니다.
Bake 메서드는 엔티티를 가져와 해당 엔티티에 RotationSpeed 컴포넌트를 추가합니다.
TransformUsageFlags.Dynamic 플래그를 사용하여 엔티티를 동적으로 사용할 수 있도록 설정합니다.
Baker 클래스는 엔티티에 컴포넌트와 속성을 적용하는 역할을 합니다.
이 클래스는 Baker을 상속받아 엔티티 서브씬에서 RotationSpeedAuthoring 인스턴스를 기반으로 작업을 수행
IComponentData 인터페이스를 구현
RadiansPerSecond라는 float 변수를 통해 회전 속도를 저장
2. HelloCube_IJobEntity
HelloCube_MainThreadSubscene과 RotationSpeedAuthoring코드는 같음
RotationSystem.cs
using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace HelloCube.JobEntity { public partial struct RotationSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<ExecuteIJobEntity>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var job = new RotateAndScaleJob { deltaTime = SystemAPI.Time.DeltaTime, elapsedTime = (float)SystemAPI.Time.ElapsedTime }; job.Schedule(); } } [BurstCompile] partial struct RotateAndScaleJob : IJobEntity { public float deltaTime; public float elapsedTime; // 소스 생성 시, Execute()의 매개변수에서 쿼리가 생성됩니다. // 여기에서 쿼리는 LocalTransform, PostTransformMatrix 및 RotationSpeed 컴포넌트를 가진 모든 엔티티와 일치합니다. // (씬에서 루트 큐브는 비균일 스케일을 가지므로, 베이킹 시 PostTransformMatrix 컴포넌트를 부여받습니다.) void Execute(ref LocalTransform transform, ref PostTransformMatrix postTransform, in RotationSpeed speed) { transform = transform.RotateY(speed.RadiansPerSecond * deltaTime); postTransform.Value = float4x4.Scale(1, math.sin(elapsedTime), 1); } } }
IJobEntity
를 사용하여 작업을 병렬로 실행할 수 있어 성능을 최적화 할 수 있습니다.
일반적인 LocalTransform
컴포넌트는 엔티티의 위치, 회전, 스케일을 나타내지만, 비균일 스케일을 적용하는 데 적합하지 않을 수 있습니다.
따라서, 비균일 스케일을 지원하기 위해 PostTransformMatrix
컴포넌트를 사용합니다.
이 컴포넌트는 객체의 후처리 변환 행렬을 저장하고 적용하는 데 사용됩니다.
PostTransformMatrix
를 사용하여 시간에 따라 Y축 스케일을 동적으로 변경하는 애니메이션을 적용합니다
3. HelloCube_Aspects
HelloCube_MainThreadSubscene, HelloCube_IJobEntity과 RotationSpeedAuthoring 코드는 같음
RotationSystem.cs
using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace HelloCube.Aspects { public partial struct RotationSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<ExecuteAspects>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { float deltaTime = SystemAPI.Time.DeltaTime; double elapsedTime = SystemAPI.Time.ElapsedTime; // Aspect를 사용하지 않고 큐브를 직접 회전시킵니다. // 이 쿼리는 LocalTransform 및 RotationSpeed 컴포넌트를 가진 모든 엔티티와 일치합니다. foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>()) { transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime); } // Aspect를 사용하여 큐브를 회전시킵니다. // 이 쿼리는 VerticalMovementAspect의 모든 컴포넌트를 포함합니다. // 컴포넌트와 달리, SystemAPI.Query의 Aspect 타입 매개변수는 RefRW 또는 RefRO로 래핑되지 않습니다. foreach (var movement in SystemAPI.Query<VerticalMovementAspect>()) { movement.Move(elapsedTime); } } } // 이 Aspect의 인스턴스는 하나의 엔티티의 LocalTransform 및 RotationSpeed 컴포넌트를 래핑합니다. // (이 간단한 예시는 Aspects의 유용한 사용 사례가 아닐 수 있지만, 더 큰 예제는 그들의 유용성을 더 잘 보여줍니다.) readonly partial struct VerticalMovementAspect : IAspect { readonly RefRW<LocalTransform> m_Transform; readonly RefRO<RotationSpeed> m_Speed; public void Move(double elapsedTime) { m_Transform.ValueRW.Position.y = (float)math.sin(elapsedTime * m_Speed.ValueRO.RadiansPerSecond); } } }
차이점 : IAspect
VerticalMovementAspect
는 LocalTransform
과 RotationSpeed
컴포넌트를 묶어 수직 이동을 담당합니다.
IAspect
를 사용하는 이유는 코드를 더 구조화하고, 유지보수를 쉽게 하며, 성능을 향상시키기 위해서 사용
1. 코드 구조화 및 가독성 향상
- 컴포넌트의 그룹화:
여러 컴포넌트를 하나의 애스펙트로 묶어 관리할 수 있어, 코드의 가독성이 향상됩니다.
예를 들어,VerticalMovementAspect
는LocalTransform
과RotationSpeed
컴포넌트를 함께 래핑합니다. - 명확한 역할 분담:
각 애스펙트는 특정 기능이나 역할을 담당하게 되어, 시스템의 각 부분이 무엇을 하는지 명확하게 이해할 수 있습니다.
2. 유지보수 용이성
- 코드 재사용성 증가:
애스펙트를 사용하면 공통 기능을 쉽게 재사용할 수 있습니다.
예를 들어, 여러 시스템에서 동일한 애스펙트를 사용하여 일관된 동작을 구현할 수 있습니다. - 변경 용이성:
컴포넌트나 기능을 변경해야 할 때, 애스펙트 내부에서만 수정하면 되므로 유지보수가 용이합니다.
3. 성능 최적화
- 효율적인 데이터 접근:
애스펙트는 관련된 컴포넌트를 함께 묶어서 처리하기 때문에, 데이터 접근이 더 효율적입니다.
이는 특히 큰 규모의 ECS 시스템에서 성능 향상에 기여할 수 있습니다. - 캐시 친화성:
애스펙트를 사용하면 메모리 레이아웃이 더 최적화되어, 캐시 친화적인 접근 방식을 제공할 수 있습니다.
4. HelloCube_Prefabs
SpawnerAuthoring.cs
using Unity.Entities; using UnityEngine; namespace HelloCube.Prefabs { // Authoring 컴포넌트는 Baker<T> 클래스를 가지는 일반적인 MonoBehavior입니다. public class SpawnerAuthoring : MonoBehaviour { public GameObject Prefab; // ECS에서 데이터를 변환하는 과정인 베이킹(Baking) // 베이킹 중에 이 Baker는 서브씬의 모든 SpawnerAuthoring 인스턴스에 대해 한 번 실행됩니다. // (Authoring 컴포넌트의 Baker 클래스를 Authoring MonoBehaviour 클래스 안에 중첩하는 것은 단지 스타일상의 선택일 뿐입니다.) class Baker : Baker<SpawnerAuthoring> { public override void Bake(SpawnerAuthoring authoring) { // SpawnerAuthoring 컴포넌트를 ECS의 엔티티(Entity)로 변환 var entity = GetEntity(TransformUsageFlags.None); // 변환된 엔티티에 ECS 구성 요소(Component)를 추가 AddComponent(entity, new Spawner { // Editor에서 설정된 Prefab(GameObject)을 동적으로 변환 Prefab = GetEntity(authoring.Prefab, TransformUsageFlags.Dynamic) }); } } } struct Spawner : IComponentData { public Entity Prefab; } }
SpawnSystem.cs
using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace HelloCube.Prefabs { public partial struct SpawnSystem : ISystem { uint updateCounter; [BurstCompile] public void OnCreate(ref SystemState state) { // Spawner 컴포넌트를 가진 엔티티가 월드에 적어도 하나 존재하지 않으면 시스템이 업데이트되지 않도록 합니다. state.RequireForUpdate<Spawner>(); // ExecutePrefabs 컴포넌트를 가진 엔티티가 월드에 적어도 하나 존재하지 않으면 시스템이 업데이트되지 않도록 합니다. state.RequireForUpdate<ExecutePrefabs>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // RotationSpeed 컴포넌트를 가진 모든 엔티티에 매칭되는 쿼리를 생성합니다. // (이 쿼리는 소스 생성 과정에서 캐시되므로, 매번 업데이트 시 재생성되는 비용이 발생하지 않습니다.) var spinningCubesQuery = SystemAPI.QueryBuilder().WithAll<RotationSpeed>().Build(); // 현재 회전하는 큐브가 하나도 없을 때만 큐브를 스폰합니다. if (spinningCubesQuery.IsEmpty) { var prefab = SystemAPI.GetSingleton<Spawner>().Prefab; // 엔티티를 인스턴스화하면 동일한 컴포넌트 타입과 값을 가진 복사 엔티티들이 생성됩니다. var instances = state.EntityManager.Instantiate(prefab, 500, Allocator.Temp); // new Random()과는 다르게, CreateFromIndex()는 랜덤 시드를 해싱하여 // 유사한 시드가 유사한 결과를 생성하지 않도록 합니다. var random = Random.CreateFromIndex(updateCounter++); foreach (var entity in instances) { // 엔티티의 LocalTransform 컴포넌트를 새로운 위치로 업데이트합니다. var transform = SystemAPI.GetComponentRW<LocalTransform>(entity); transform.ValueRW.Position = (random.NextFloat3() - new float3(0.5f, 0, 0.5f)) * 20; } } } } }
FallAndDestroySystem.cs
using Unity.Burst; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace HelloCube.Prefabs { public partial struct FallAndDestroySystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { // ExecutePrefabs 컴포넌트를 가진 엔티티가 존재해야만 시스템이 업데이트되도록 설정합니다. state.RequireForUpdate<ExecutePrefabs>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // 회전 처리 float deltaTime = SystemAPI.Time.DeltaTime; foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>()) { // ValueRW와 ValueRO는 모두 실제 컴포넌트 값에 대한 참조를 반환합니다. // 차이점은 ValueRW는 읽기-쓰기 접근에 대해 안전성 검사를 수행하고, // ValueRO는 읽기 전용 접근에 대해 안전성 검사를 수행한다는 점입니다. transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime); } // EntityCommandBufferSystem.Singleton에서 생성된 EntityCommandBuffer는 // EntityCommandBufferSystem이 다음에 업데이트될 때 실행되고 소멸됩니다. var ecbSingleton = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>(); var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged); // 아래 방향 벡터 var movement = new float3(0, -SystemAPI.Time.DeltaTime * 5f, 0); // WithAll()은 쿼리에 RotationSpeed를 포함하지만, // RotationSpeed 컴포넌트 값에 접근하지는 않습니다. // WithEntityAccess()는 엔티티 ID를 튜플의 마지막 요소로 포함시킵니다. foreach (var (transform, entity) in SystemAPI.Query<RefRW<LocalTransform>>() .WithAll<RotationSpeed>() .WithEntityAccess()) { transform.ValueRW.Position += movement; if (transform.ValueRO.Position.y < 0) { // 구조적 변경을 하면 현재 반복 중인 쿼리가 무효화될 수 있으므로, // 대신 해당 엔티티를 나중에 삭제하도록 명령을 기록합니다. ecb.DestroyEntity(entity); } } } } }
EntityCommandBuffer (ECB)
는 모든 OnUpdate
가 끝난 이후 실행
왜 EntityCommandBuffer가 필요한가
- 즉시 구조적 변경은 위험:
- ECS에서는 구조적 변경(엔티티 생성, 삭제, 컴포넌트 추가/삭제)을 즉시 수행하면 현재 실행 중인 쿼리나 시스템에서 사용하는 데이터가 무효화될 수 있습니다.
이는 성능 저하나 예기치 못한 동작을 유발할 수 있습니다.
- ECS에서는 구조적 변경(엔티티 생성, 삭제, 컴포넌트 추가/삭제)을 즉시 수행하면 현재 실행 중인 쿼리나 시스템에서 사용하는 데이터가 무효화될 수 있습니다.
- ECB는 안전한 방식:
EntityCommandBuffer
를 사용하면 시스템에서 즉시 구조적 변경을 수행하지 않고, 변경 명령을 큐에 저장합니다.
저장된 명령은 해당 프레임의 ECS 업데이트 단계가 모두 완료된 후 실행됩니다.
5. HelloCube_IJobChunk
RotationSystem.cs
using Unity.Assertions; using Unity.Burst; using Unity.Burst.Intrinsics; using Unity.Collections; using Unity.Entities; using Unity.Transforms; namespace HelloCube.JobChunk { public partial struct RotationSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { // 시스템이 업데이트되기 위해서는 ExecuteIJobChunk 컴포넌트가 적어도 하나 이상 있어야 함을 지정 state.RequireForUpdate<ExecuteIJobChunk>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // RotationSpeed와 LocalTransform 컴포넌트를 가진 모든 엔티티를 쿼리하는 쿼리를 생성합니다. var spinningCubesQuery = SystemAPI.QueryBuilder().WithAll<RotationSpeed, LocalTransform>().Build(); var job = new RotationJob { TransformTypeHandle = SystemAPI.GetComponentTypeHandle<LocalTransform>(), RotationSpeedTypeHandle = SystemAPI.GetComponentTypeHandle<RotationSpeed>(true), // 읽기 전용으로 설정 DeltaTime = SystemAPI.Time.DeltaTime }; // IJobEntity와 달리, IJobChunk는 쿼리를 수동으로 전달해야 합니다. // 또한, IJobChunk는 state.Dependency JobHandle을 암시적으로 전달하거나 할당하지 않습니다. // (state.Dependency를 전달하고 할당하는 이 패턴은 서로 다른 시스템에서 예약된 엔티티 작업이 필요한 대로 의존하도록 보장합니다.) state.Dependency = job.Schedule(spinningCubesQuery, state.Dependency); } } [BurstCompile] struct RotationJob : IJobChunk { public ComponentTypeHandle<LocalTransform> TransformTypeHandle; [ReadOnly] public ComponentTypeHandle<RotationSpeed> RotationSpeedTypeHandle; public float DeltaTime; public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { // useEnabledMask 매개변수는 쿼리 내 구성 요소 중 하나 이상이 비활성화된 엔티티가 있을 때 true가 됩니다. // 쿼리 구성 요소 유형에 IEnableableComponent를 구현한 것이 없으면, useEnabledMask는 항상 false라고 가정할 수 있습니다. // 그러나 누군가 나중에 쿼리나 구성 요소 유형을 변경할 가능성을 고려하여 이 검사를 추가하는 것이 좋은 습관입니다. Assert.IsFalse(useEnabledMask); // 청크에서 LocalTransform 컴포넌트를 가져옵니다. var transforms = chunk.GetNativeArray(ref TransformTypeHandle); // 청크에서 RotationSpeed 컴포넌트를 읽기 전용으로 가져옵니다. var rotationSpeeds = chunk.GetNativeArray(ref RotationSpeedTypeHandle); // 청크 내 모든 엔티티에 대해 반복하면서 회전을 계산합니다. for (int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; i++) { // 엔티티의 LocalTransform을 업데이트하여 Y축으로 회전시킵니다. transforms[i] = transforms[i].RotateY(rotationSpeeds[i].RadiansPerSecond * DeltaTime); } } } }
Aspects, IJobChunk, IJobEntity
Aspects
, IJobChunk
, IJobEntity
는 모두 Unity의 ECS(Entity Component System)에서 엔티티 기반의 데이터 처리를 위한 접근 방식이지만 각각 다른 방식으로 동작하고 사용
이들은 크게 Aspects 기반 코드, IJobChunk 기반 코드, IJobEntity 기반 코드로 구분할 수 있으며, 각기 다른 목적과 사용 상황에 맞게 선택됩니다.
1. Aspects 기반 코드
- Aspects는 ECS에서 데이터를 객체 지향적으로 다룰 수 있는 방법을 제공합니다.
Aspects는 여러 컴포넌트를 하나의 구조로 묶어 엔티티에 대한 로직을 캡슐화하며, 주로 복잡한 데이터 구조를 처리하는 데 유용합니다.
이를 통해 특정 데이터를 가진 엔티티들을 쉽게 다룰 수 있으며, 코드가 간결하고 유지보수가 용이합니다. - 주요 특징:
- 코드 간결성
- 높은 가독성
- 데이터와 로직을 객체 지향적으로 묶어 처리
- 유지보수가 쉬운 구조 제공
using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace HelloCube.Aspects { // Aspect 정의 readonly partial struct RotationAspect : IAspect { readonly RefRW<LocalTransform> transform; readonly RefRO<RotationSpeed> speed; public void Rotate(float deltaTime) { transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime); } } public partial struct RotationSystem : ISystem { public void OnCreate(ref SystemState state) {} public void OnUpdate(ref SystemState state) { float deltaTime = SystemAPI.Time.DeltaTime; // Aspect를 사용하여 회전 처리 foreach (var rotationAspect in SystemAPI.Query<RotationAspect>()) { rotationAspect.Rotate(deltaTime); } } } }
2. IJobChunk 기반 코드
IJobChunk
는 큰 데이터 집합을 효율적으로 처리하기 위해 청크 단위로 엔티티를 다루는 방법입니다.
데이터를 더 최적화된 방식으로 처리할 수 있으며, 성능을 크게 향상시킬 수 있습니다.IJobChunk
는 기본적으로 모든 엔티티를 청크 단위로 처리하고, 이를 멀티스레드 환경에서 병렬로 실행할 수 있습니다.- 주요 특징:
- 성능 최적화 (특히 대규모 엔티티 처리)
- 청크 단위로 데이터 처리
- 고성능 작업이 필요한 경우 적합
- 복잡한 코드 (비교적 많은 설정이 필요)
using Unity.Burst; using Unity.Entities; using Unity.Transforms; using Unity.Mathematics; namespace HelloCube.JobChunk { [BurstCompile] struct RotationJob : IJobChunk { public ComponentTypeHandle<LocalTransform> TransformTypeHandle; [ReadOnly] public ComponentTypeHandle<RotationSpeed> RotationSpeedTypeHandle; public float DeltaTime; public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { var transforms = chunk.GetNativeArray(ref TransformTypeHandle); var speeds = chunk.GetNativeArray(ref RotationSpeedTypeHandle); for (int i = 0; i < chunk.Count; i++) { transforms[i] = transforms[i].RotateY(speeds[i].RadiansPerSecond * DeltaTime); } } } public partial struct RotationSystem : ISystem { public void OnCreate(ref SystemState state) {} public void OnUpdate(ref SystemState state) { var job = new RotationJob { TransformTypeHandle = SystemAPI.GetComponentTypeHandle<LocalTransform>(), RotationSpeedTypeHandle = SystemAPI.GetComponentTypeHandle<RotationSpeed>(true), DeltaTime = SystemAPI.Time.DeltaTime }; var query = SystemAPI.QueryBuilder().WithAll<RotationSpeed, LocalTransform>().Build(); state.Dependency = job.Schedule(query, state.Dependency); } } }
3. IJobEntity 기반 코드
IJobEntity
는 각 엔티티에 대해 작업을 수행하는 방식입니다.IJobEntity
는 간단하고 직관적인 코드 작성이 가능하지만, 대규모 데이터를 처리할 때 성능이 떨어질 수 있습니다.
그러나 작은 규모의 엔티티를 다룰 때는 유용하고, 코드 작성이 쉽습니다.- 주요 특징:
- 코드 간결성
- 소규모 엔티티 처리에 적합
- 복잡한 데이터 구조가 필요하지 않음
- 성능 최적화 측면에서는
IJobChunk
보다는 낮음
using Unity.Burst; using Unity.Entities; using Unity.Transforms; namespace HelloCube.JobEntity { [BurstCompile] public partial struct RotationSystem : ISystem { public void OnCreate(ref SystemState state) {} public void OnUpdate(ref SystemState state) { // 각 엔티티를 쿼리하여 회전 처리 foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>()) { float deltaTime = SystemAPI.Time.DeltaTime; transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime); } } } }
기능 | Aspects 기반 코드 | IJobChunk 기반 코드 | IJobEntity 기반 코드 |
---|---|---|---|
코드 간결성 | 높은 수준의 가독성 제공 | 상대적으로 코드가 더 복잡 | 비교적 간결하고 직관적인 코드 |
성능 요구 사항 | 소규모/중간 규모의 엔티티 처리에 적합 | 대규모 엔티티 처리와 성능 최적화에 적합 | 소규모에서 중간 규모 엔티티 처리에 적합 |
유지 보수성 | Aspect로 동작 분리 가능 | 쿼리 및 데이터 처리가 분리되어 유지보수가 복잡할 수 있음 | 코드가 간결하여 유지보수가 용이 |
캐시 효율성 | 기본 캐시 사용 | 캐시 최적화에 더 적합 | 기본 캐시 사용 및 IJobChunk보다 덜 최적화됨 |
6. HelloCube_Reparenting
ReparentingSystem.cs
public partial struct ReparentingSystem : ISystem { bool attached; float timer; const float interval = 0.7f; [BurstCompile] public void OnCreate(ref SystemState state) { timer = interval; attached = true; state.RequireForUpdate<ExecuteReparenting>(); state.RequireForUpdate<RotationSpeed>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { timer -= SystemAPI.Time.DeltaTime; // 타이머를 감소시켜 일정 시간 간격을 유지합니다. if (timer > 0) { return; // 타이머가 0보다 크면 업데이트를 중단하고 계속 대기 } timer = interval; // 타이머를 다시 초기화 (간격 유지) var rotatorEntity = SystemAPI.GetSingletonEntity<RotationSpeed>(); // 회전 속도를 가진 엔티티 가져오기 var ecb = new EntityCommandBuffer(Allocator.Temp); // EntityCommandBuffer 생성, 임시 할당으로 사용 if (attached) { // 부모에서 분리: 'RotationSpeed' 컴포넌트를 가진 엔티티를 찾고, 그 자식들을 분리합니다. DynamicBuffer<Child> children = SystemAPI.GetBuffer<Child>(rotatorEntity); for (int i = 0; i < children.Length; i++) { ecb.RemoveComponent<Parent>(children[i].Value); // 자식들의 'Parent' 컴포넌트 제거 } } else { // 부모에 붙이기: 'RotationSpeed' 컴포넌트가 없는 모든 엔티티를 부모에 추가합니다. foreach (var (transform, entity) in SystemAPI.Query<RefRO<LocalTransform>>() .WithNone<RotationSpeed>() // RotationSpeed가 없는 엔티티만 .WithEntityAccess()) // Entity를 참조로 포함 { ecb.AddComponent(entity, new Parent { Value = rotatorEntity }); // 'Parent' 컴포넌트 추가 } } ecb.Playback(state.EntityManager); // 명령 버퍼의 명령들을 실행 attached = !attached; // 부모-자식 관계를 번갈아가며 변경 } }
timer를 기반으로 부모-자식 관계를 주기적으로 바꿉니다.
attached 변수를 사용해 부모에서 분리하거나 부모에 다시 붙입니다.
EntityCommandBuffer를 사용하여 엔티티의 부모-자식 관계를 효율적으로 수정합니다.
RotationSystem.cs
using HelloCube; using Unity.Burst; using Unity.Entities; using Unity.Transforms; public partial struct RotationSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<ExecuteReparenting>(); // ReparentingSystem이 먼저 실행될 것임을 명시 } [BurstCompile] public void OnUpdate(ref SystemState state) { float deltaTime = SystemAPI.Time.DeltaTime; // 델타 시간(프레임 간 시간 차이) // 'RotationSpeed'가 있는 모든 엔티티에 대해 회전 적용 foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>()) { transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime); // Y축을 중심으로 회전 } } }
7. HelloCube_EnableableComponents
EnableableComponents를 사용하여 회전 속도를 제어하는 시스템을 구현
RotationSpeedAuthoring.cs
using UnityEngine; using Unity.Entities; using Unity.Mathematics; namespace HelloCube.EnableableComponents { public class RotationSpeedAuthoring : MonoBehaviour { public bool StartEnabled; public float DegreesPerSecond = 360.0f; public class Baker : Baker<RotationSpeedAuthoring> { public override void Bake(RotationSpeedAuthoring authoring) { var entity = GetEntity(TransformUsageFlags.Dynamic); // RotationSpeed 컴포넌트를 엔티티에 추가하고, DegreesPerSecond를 라디안으로 변환하여 설정 AddComponent(entity, new RotationSpeed { RadiansPerSecond = math.radians(authoring.DegreesPerSecond) }); // RotationSpeed 컴포넌트를 활성화하거나 비활성화 SetComponentEnabled<RotationSpeed>(entity, authoring.StartEnabled); } } } struct RotationSpeed : IComponentData, IEnableableComponent { public float RadiansPerSecond; } }
Bake
메서드는 AddComponent
를 통해 RotationSpeed
컴포넌트를 엔티티에 추가하며, SetComponentEnabled
를 사용해 해당 컴포넌트를 활성화하거나 비활성화합니다.
RotationSystem.cs
using Unity.Burst; using Unity.Entities; using Unity.Transforms; namespace HelloCube.EnableableComponents { public partial struct RotationSystem : ISystem { float timer; const float interval = 1.3f; [BurstCompile] public void OnCreate(ref SystemState state) { timer = interval; state.RequireForUpdate<ExecuteEnableableComponents>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { float deltaTime = SystemAPI.Time.DeltaTime; // 델타 타임을 가져옵니다. timer -= deltaTime; // RotationSpeed가 활성화된 모든 엔티티에 대해 회전 상태를 주기적으로 토글합니다. if (timer < 0) { foreach (var rotationSpeedEnabled in SystemAPI.Query<EnabledRefRW<RotationSpeed>>() .WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)) { rotationSpeedEnabled.ValueRW = !rotationSpeedEnabled.ValueRO; // 활성화 상태를 반전시킵니다. } timer = interval; // 타이머를 초기화합니다. } // RotationSpeed가 활성화된 엔티티에 대해서만 회전을 적용합니다. foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>()) { transform.ValueRW = transform.ValueRO.RotateY( speed.ValueRO.RadiansPerSecond * deltaTime); } } } }
EnabledRefRW<RotationSpeed>
는 RotationSpeed
컴포넌트가 활성화된 엔티티를 찾습니다.
활성화 상태를 반전시키는 방식으로 주기적인 상태 변경을 구현합니다.