13. Custom2DTransforms


Transform2DAuthoring.cs
Transform2DAuthoring 클래스의 주요 목표는 기존 Unity의 Transform 시스템을 사용하지 않고,
ECS(Entity Component System)에서 동작하는 최적화된 커스텀 2D 트랜스폼 시스템을 구현하는 것
using System.Globalization;
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace HelloCube.CustomTransforms
{
public class Transform2DAuthoring : MonoBehaviour
{
class Baker : Baker<Transform2DAuthoring>
{
public override void Bake(Transform2DAuthoring authoring)
{
// 표준 Transform 컴포넌트가 추가되지 않도록 보장합니다.
// TransformUsageFlags.ManualOverride를 사용하면 ECS가 해당 엔티티에 대해
// 기본 Transform 컴포넌트(Translation, Rotation, Scale 등)를 자동으로 추가하지 않도록 제어
// Unity의 표준 트랜스폼 처리 로직을 비활성화
// 커스텀 트랜스폼 시스템을 사용하면 2D 전용으로 최적화하여 불필요한 계산을 줄이고 성능을 향상
var entity = GetEntity(TransformUsageFlags.ManualOverride);
AddComponent(entity, new LocalTransform2D
{
Scale = 1
});
AddComponent(entity, new LocalToWorld
{
Value = float4x4.Scale(1)
});
var parentGO = authoring.transform.parent;
if (parentGO != null)
{
AddComponent(entity, new Parent
{
Value = GetEntity(parentGO, TransformUsageFlags.None)
});
}
}
}
}
// LocalToWorld 쓰기 그룹에 LocalTransform2D를 포함시킴으로써,
// LocalTransform2D를 가진 엔티티는 표준 Transform 시스템에 의해 처리되지 않습니다.
[WriteGroup(typeof(LocalToWorld))]
public struct LocalTransform2D : IComponentData
{
public float2 Position; // 2D 공간에서의 위치를 나타내는 float2
public float Scale; // 엔티티의 크기를 나타내는 float
public float Rotation; // 2D 공간에서의 회전을 나타내며, 단위는 degree(도)
public override string ToString()
{
return $"Position={Position.ToString()} Rotation={Rotation.ToString()} Scale={Scale.ToString(CultureInfo.InvariantCulture)}";
}
// 트랜스폼 데이터를 기반으로 4x4 변환 매트릭스를 생성
// math.radians(Rotation)으로 도(degree)를 라디안으로 변환
// float4x4.TRS를 사용하여 위치(Translation), 회전(Rotation), 스케일(Scale)을 포함하는 매트릭스를 생성
public float4x4 ToMatrix()
{
quaternion rotation = quaternion.RotateZ(math.radians(Rotation));
return float4x4.TRS(new float3(Position.xy, 0f), rotation, Scale);
}
}
}
MovementSystem.cs
커스텀 2D 트랜스폼 데이터(LocalTransform2D)를 사용하여 객체를 움직이고 크기 및 회전을 조정하는 시스템을 구현
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace HelloCube.CustomTransforms
{
public partial struct MovementSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<LocalTransform2D>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float rotation = SystemAPI.Time.DeltaTime * 180f; // Half a rotation every second (in degrees)
float elapsedTime = (float) SystemAPI.Time.ElapsedTime;
float xPosition = math.sin(elapsedTime) * 2f - 1f;
float scale = math.sin(elapsedTime * 2f) + 1f; // math.sin(elapsedTime * 2f)를 사용하여 스케일이 0에서 2까지 진동
scale = scale <= 0.001f ? 0f : scale;
foreach (var localTransform2D in SystemAPI
.Query<RefRW<LocalTransform2D>>()
.WithNone<Parent>())
{
localTransform2D.ValueRW.Position.x = xPosition;
localTransform2D.ValueRW.Rotation = localTransform2D.ValueRO.Rotation + rotation;
localTransform2D.ValueRW.Scale = scale;
}
}
}
}
LocalToWorld2DSystem.cs
LocalTransform2D 데이터를 기반으로 각 엔티티의 월드 공간(LocalToWorld 변환 행렬)을 계산합니다.
추가적으로 부모-자식 관계를 고려하여 계층 구조를 통해 변환을 계산합니다.
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Assertions;
using Unity.Burst.Intrinsics;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.Transforms;
namespace HelloCube.CustomTransforms
{
// 이 시스템은 LocalTransform2D를 가진 각 엔티티에 대해 변환 행렬(Transform Matrix)을 계산합니다.
// 부모가 없는 루트 레벨/월드 공간 엔티티의 경우,
// LocalToWorld는 엔티티의 LocalTransform2D에서 직접 계산될 수 있습니다.
// 자식 엔티티의 경우, 각 고유 계층 구조를 재귀적으로 탐색하며,
// 부모의 변환과 자식의 변환을 합성하여 각 자식의 LocalToWorld를 계산합니다.
[WorldSystemFilter(WorldSystemFilterFlags.Default | WorldSystemFilterFlags.Editor)] // 이 시스템은 기본 월드와 에디터 월드에서 작동하도록 설정
[UpdateInGroup(typeof(TransformSystemGroup))] // TransformSystemGroup: 변환 계산과 관련된 시스템 그룹에서 실행
[UpdateAfter(typeof(ParentSystem))] // ParentSystem 이후에 업데이트: 부모-자식 관계가 올바르게 계산된 후 실행
[BurstCompile]
public partial struct LocalToWorld2DSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<LocalTransform2D>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// LocalTransform2D와 LocalToWorld를 가진 엔티티, 부모가 없는 루트 레벨 엔티티.
var rootsQuery = SystemAPI.QueryBuilder()
.WithAll<LocalTransform2D>()
.WithAllRW<LocalToWorld>()
.WithNone<Parent>().Build();
// LocalTransform2D와 자식(Child) 정보를 가진 엔티티.부모가 없는 상위 레벨 엔티티.
var parentsQuery = SystemAPI.QueryBuilder()
.WithAll<LocalTransform2D, Child>()
.WithAllRW<LocalToWorld>()
.WithNone<Parent>().Build();
var localToWorldWriteGroupMask = SystemAPI.QueryBuilder()
.WithAll<LocalTransform2D, Parent>()
.WithAllRW<LocalToWorld>().Build().GetEntityQueryMask();
// ComputeRootLocalToWorldJob을 실행하여 루트 엔티티의 LocalToWorld를 계산
var rootJob = new ComputeRootLocalToWorldJob
{
LocalTransform2DTypeHandleRO = SystemAPI.GetComponentTypeHandle<LocalTransform2D>(true),
PostTransformMatrixTypeHandleRO = SystemAPI.GetComponentTypeHandle<PostTransformMatrix>(true),
LocalToWorldTypeHandleRW = SystemAPI.GetComponentTypeHandle<LocalToWorld>(),
LastSystemVersion = state.LastSystemVersion,
};
state.Dependency = rootJob.ScheduleParallelByRef(rootsQuery, state.Dependency);
// ComputeChildLocalToWorldJob을 실행하여 자식 엔티티 계층을 따라가며 LocalToWorld를 계산
var childJob = new ComputeChildLocalToWorldJob
{
LocalToWorldWriteGroupMask = localToWorldWriteGroupMask,
ChildTypeHandle = SystemAPI.GetBufferTypeHandle<Child>(true),
ChildLookup = SystemAPI.GetBufferLookup<Child>(true),
LocalToWorldTypeHandleRW = SystemAPI.GetComponentTypeHandle<LocalToWorld>(),
LocalTransform2DLookup = SystemAPI.GetComponentLookup<LocalTransform2D>(true),
PostTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true),
LocalToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(),
LastSystemVersion = state.LastSystemVersion,
};
state.Dependency = childJob.ScheduleParallelByRef(parentsQuery, state.Dependency);
}
[BurstCompile]
unsafe struct ComputeRootLocalToWorldJob : IJobChunk
{
[ReadOnly] public ComponentTypeHandle<LocalTransform2D> LocalTransform2DTypeHandleRO;
[ReadOnly] public ComponentTypeHandle<PostTransformMatrix> PostTransformMatrixTypeHandleRO;
public ComponentTypeHandle<LocalToWorld> LocalToWorldTypeHandleRW;
public uint LastSystemVersion;
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
in v128 chunkEnabledMask)
{
Assert.IsFalse(useEnabledMask);
LocalTransform2D* chunk2DLocalTransforms =
(LocalTransform2D*)chunk.GetRequiredComponentDataPtrRO(ref LocalTransform2DTypeHandleRO);
// 변경 감지(chunk.DidChange)를 통해 변환이 변경된 엔티티만 업데이트
if (chunk.DidChange(ref LocalTransform2DTypeHandleRO, LastSystemVersion) ||
chunk.DidChange(ref PostTransformMatrixTypeHandleRO, LastSystemVersion))
{
LocalToWorld* chunkLocalToWorlds =
(LocalToWorld*)chunk.GetRequiredComponentDataPtrRW(ref LocalToWorldTypeHandleRW);
PostTransformMatrix* chunkPostTransformMatrices =
(PostTransformMatrix*)chunk.GetComponentDataPtrRO(ref PostTransformMatrixTypeHandleRO);
if (chunkPostTransformMatrices != null)
{
for (int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; ++i)
{
chunkLocalToWorlds[i].Value = math.mul(chunk2DLocalTransforms[i].ToMatrix(),
chunkPostTransformMatrices[i].Value);
}
}
else
{
for (int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; ++i)
{
chunkLocalToWorlds[i].Value = chunk2DLocalTransforms[i].ToMatrix();
}
}
}
}
}
[BurstCompile]
unsafe struct ComputeChildLocalToWorldJob : IJobChunk
{
[NativeDisableContainerSafetyRestriction]
public ComponentLookup<LocalToWorld> LocalToWorldLookup;
[ReadOnly] public EntityQueryMask LocalToWorldWriteGroupMask;
[ReadOnly] public BufferTypeHandle<Child> ChildTypeHandle;
[ReadOnly] public BufferLookup<Child> ChildLookup;
public ComponentTypeHandle<LocalToWorld> LocalToWorldTypeHandleRW;
[ReadOnly] public ComponentLookup<LocalTransform2D> LocalTransform2DLookup;
[ReadOnly] public ComponentLookup<PostTransformMatrix> PostTransformMatrixLookup;
public uint LastSystemVersion;
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
in v128 chunkEnabledMask)
{
Assert.IsFalse(useEnabledMask);
bool updateChildrenTransform = chunk.DidChange(ref ChildTypeHandle, LastSystemVersion);
BufferAccessor<Child> chunkChildBuffers = chunk.GetBufferAccessor(ref ChildTypeHandle);
updateChildrenTransform = updateChildrenTransform ||
chunk.DidChange(ref LocalToWorldTypeHandleRW, LastSystemVersion);
LocalToWorld* chunkLocalToWorlds =
(LocalToWorld*)chunk.GetRequiredComponentDataPtrRO(ref LocalToWorldTypeHandleRW);
for (int i = 0, chunkEntityCount = chunk.Count; i < chunkEntityCount; i++)
{
var localToWorld = chunkLocalToWorlds[i].Value;
var children = chunkChildBuffers[i];
for (int j = 0, childCount = children.Length; j < childCount; j++)
{
ChildLocalToWorldFromTransformMatrix(localToWorld, children[j].Value, updateChildrenTransform);
}
}
}
void ChildLocalToWorldFromTransformMatrix(in float4x4 parentLocalToWorld, Entity childEntity,
bool updateChildrenTransform)
{
updateChildrenTransform = updateChildrenTransform
|| PostTransformMatrixLookup.DidChange(childEntity, LastSystemVersion)
|| LocalTransform2DLookup.DidChange(childEntity, LastSystemVersion);
float4x4 localToWorld;
if (updateChildrenTransform && LocalToWorldWriteGroupMask.MatchesIgnoreFilter(childEntity))
{
var localTransform2D = LocalTransform2DLookup[childEntity];
localToWorld = math.mul(parentLocalToWorld, localTransform2D.ToMatrix());
if (PostTransformMatrixLookup.HasComponent(childEntity))
{
localToWorld = math.mul(localToWorld, PostTransformMatrixLookup[childEntity].Value);
}
LocalToWorldLookup[childEntity] = new LocalToWorld { Value = localToWorld };
}
else
{
localToWorld = LocalToWorldLookup[childEntity].Value;
updateChildrenTransform = LocalToWorldLookup.DidChange(childEntity, LastSystemVersion);
}
if (ChildLookup.TryGetBuffer(childEntity, out DynamicBuffer<Child> children))
{
for (int i = 0, childCount = children.Length; i < childCount; i++)
{
ChildLocalToWorldFromTransformMatrix(localToWorld, children[i].Value, updateChildrenTransform);
}
}
}
}
}
}
14. StateChange


ConfigAuthoring.cs
Unity에서 해당 클래스가 포함된 게임 오브젝트가 속한 엔티티로 컴포넌트를 변환하는 역할을 합니다.
ConfigAuthoring은 주로 Baker 클래스를 통해 엔티티와 연결됩니다.
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace HelloCube.StateChange
{
public class ConfigAuthoring : MonoBehaviour
{
public GameObject Prefab;
public uint Size;
public float Radius;
public Mode Mode;
class Baker : Baker<ConfigAuthoring>
{
public override void Bake(ConfigAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new Config
{
Prefab = GetEntity(authoring.Prefab, TransformUsageFlags.Dynamic),
Size = authoring.Size,
Radius = authoring.Radius,
Mode = authoring.Mode,
});
AddComponent<Hit>(entity);
#if UNITY_EDITOR
AddComponent<StateChangeProfilerModule.FrameData>(entity);
#endif
}
}
}
// Config는 IComponentData를 구현한 구조체로, ECS의 컴포넌트 데이터로 사용
public struct Config : IComponentData
{
public Entity Prefab;
public uint Size;
public float Radius;
public Mode Mode;
}
// Hit는 엔티티의 충돌 상태를 추적하는 IComponentData입니다.
// Value는 충돌 위치를 저장하는 float3 타입이고,
// HitChanged는 충돌 상태가 변경되었는지 여부를 나타내는 bool 타입
public struct Hit : IComponentData
{
public float3 Value;
public bool HitChanged;
}
// Spin은 엔티티가 회전하는 상태를 추적하는 컴포넌트
// IsSpinning은 회전 여부를 나타내는 bool 타입이며,
// IEnableableComponent 인터페이스를 구현하여 이 컴포넌트를 활성화하거나 비활성화
public struct Spin : IComponentData, IEnableableComponent
{
public bool IsSpinning;
}
// Mode는 엔티티의 동작 모드를 정의하는 열거형
public enum Mode
{
VALUE = 1, // VALUE: 일반적인 값 모드.
STRUCTURAL_CHANGE = 2, // STRUCTURAL_CHANGE: 구조적 변경 모드.
ENABLEABLE_COMPONENT = 3 // ENABLEABLE_COMPONENT: 활성화 가능한 컴포넌트 모드.
}
}
InputSystem.cs
Unity의 ECS(엔티티 컴포넌트 시스템)를 사용하여 입력 시스템을 구현
마우스 클릭으로 발생한 충돌을 감지하고, 그 충돌 지점을 Hit 컴포넌트에 저장하는 기능을 수행합니다.
충돌 상태가 변경되었음을 HitChanged 플래그로 표시
using Unity.Burst;
using Unity.Entities;
using UnityEngine;
namespace HelloCube.StateChange
{
public partial struct InputSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Hit>();
state.RequireForUpdate<Config>();
state.RequireForUpdate<ExecuteStateChange>();
}
public void OnUpdate(ref SystemState state)
{
var hit = SystemAPI.GetSingletonRW<Hit>();
hit.ValueRW.HitChanged = false;
// 마우스 입력이 없거나, 메인 카메라가 존재하지 않는 경우 업데이트를 하지 않음
if (Camera.main == null || !Input.GetMouseButton(0))
{
return;
}
// 마우스 포인터가 화면에서 가리키는 위치를 기준으로 카메라에서 시작되는 레이를 생성
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Plane을 사용하여 레이와 평면의 교차 여부를 확인
// 레이가 평면과 교차하는 지점을 찾아 dist 변수에 그 거리 값을 저장
if (new Plane(Vector3.up, 0f).Raycast(ray, out var dist))
{
hit.ValueRW.HitChanged = true;
hit.ValueRW.Value = ray.GetPoint(dist);
}
}
}
}
SetStateSystem.cs
사용자가 클릭한 위치(Hit)와 설정된 반경(Radius)을 기준으로 엔티티들의 상태를 변경하는 로직
상태 변경 방식은 세 가지 모드 : VALUE, STRUCTURAL_CHANGE, ENABLEABLE_COMPONENT
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using Unity.Profiling.LowLevel.Unsafe;
namespace HelloCube.StateChange
{
public partial struct SetStateSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Hit>();
state.RequireForUpdate<Config>();
state.RequireForUpdate<ExecuteStateChange>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var config = SystemAPI.GetSingleton<Config>();
var hit = SystemAPI.GetSingleton<Hit>();
// HitChanged 플래그가 false일 경우, 즉 이전에 충돌이 없거나 변경되지 않았다면 상태 변경을 하지 않습니다.
if (!hit.HitChanged)
{
#if UNITY_EDITOR
// StateChangeProfilerModule.FrameData: 성능 데이터를 저장하는 구조체로,
// SetStatePerf와 같은 성능 정보를 기록하는 데 사용
SystemAPI.GetSingletonRW<StateChangeProfilerModule.FrameData>().ValueRW.SetStatePerf = 0;
#endif
return;
}
var radiusSq = config.Radius * config.Radius;
var ecbSystem = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
state.Dependency.Complete();
var before = ProfilerUnsafeUtility.Timestamp;
if (config.Mode == Mode.VALUE)
{
// SetValueJob은 각 엔티티의 LocalTransform과 Hit 위치를 비교하여,
// 엔티티가 Hit 위치와 반지름 내에 있을 경우 색상과 회전 여부를 적용
new SetValueJob
{
RadiusSq = radiusSq,
Hit = hit.Value
}.ScheduleParallel();
}
else if (config.Mode == Mode.STRUCTURAL_CHANGE)
{
// Hit 위치와 반지름 내에 있는 엔티티에 Spin 컴포넌트를 추가 및 제거합니다.
// 그리고 그 엔티티의 색상을 빨간색으로 바꿉니다.
new AddSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
ECB = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
}.ScheduleParallel();
new RemoveSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
ECB = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
}.ScheduleParallel();
}
else if (config.Mode == Mode.ENABLEABLE_COMPONENT)
{
// Hit 위치와 반지름 내에 있는 엔티티에서 Spin 컴포넌트를 활성화/비활성화합니다.
new EnableSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
ECB = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
}.ScheduleParallel();
new DisableSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
}.ScheduleParallel();
}
state.Dependency.Complete();
var after = ProfilerUnsafeUtility.Timestamp;
#if UNITY_EDITOR
// profiling
var conversionRatio = ProfilerUnsafeUtility.TimestampToNanosecondsConversionRatio;
var elapsed = (after - before) * conversionRatio.Numerator / conversionRatio.Denominator;
SystemAPI.GetSingletonRW<StateChangeProfilerModule.FrameData>().ValueRW.SetStatePerf = elapsed;
#endif
}
}
[BurstCompile]
partial struct SetValueJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
void Execute(ref URPMaterialPropertyBaseColor color, ref Spin spin, in LocalTransform transform)
{
if (math.distancesq(transform.Position, Hit) <= RadiusSq)
{
color.Value = (Vector4)Color.red;
spin.IsSpinning = true;
}
else
{
color.Value = (Vector4)Color.white;
spin.IsSpinning = false;
}
}
}
[WithNone(typeof(Spin))]
[BurstCompile]
partial struct AddSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform,
[ChunkIndexInQuery] int chunkIndex)
{
// If cube is inside the hit radius.
if (math.distancesq(transform.Position, Hit) <= RadiusSq)
{
color.Value = (Vector4)Color.red;
ECB.AddComponent<Spin>(chunkIndex, entity);
}
}
}
[WithAll(typeof(Spin))]
[BurstCompile]
partial struct RemoveSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform, [ChunkIndexInQuery] int chunkIndex)
{
// If cube is NOT inside the hit radius.
if (math.distancesq(transform.Position, Hit) > RadiusSq)
{
color.Value = (Vector4)Color.white;
ECB.RemoveComponent<Spin>(chunkIndex, entity);
}
}
}
[WithNone(typeof(Spin))]
[BurstCompile]
public partial struct EnableSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform,
[ChunkIndexInQuery] int chunkIndex)
{
// If cube is inside the hit radius.
if (math.distancesq(transform.Position, Hit) <= RadiusSq)
{
color.Value = (Vector4)Color.red;
ECB.SetComponentEnabled<Spin>(chunkIndex, entity, true);
}
}
}
[BurstCompile]
public partial struct DisableSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform, EnabledRefRW<Spin> spinnerEnabled)
{
// If cube is NOT inside the hit radius.
if (math.distancesq(transform.Position, Hit) > RadiusSq)
{
color.Value = (Vector4)Color.white;
spinnerEnabled.ValueRW = false;
}
}
}
}
CubeSpawnSystem.cs
Config 컴포넌트의 설정에 따라 큐브를 생성하고, 해당 큐브들에 대한 상태를 설정하는 작업
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Rendering;
using Unity.Transforms;
namespace HelloCube.StateChange
{
public partial struct CubeSpawnSystem : ISystem
{
Config priorConfig;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Config>();
state.RequireForUpdate<ExecuteStateChange>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var config = SystemAPI.GetSingleton<Config>();
// Config를 가져와서 이전의 Config와 비교합니다.
// ConfigEquals 함수는 이전 설정과 새로운 설정을 비교하여,설정에 차이가 없으면 큐브를 재생성하지 않도록 합니다.
if (ConfigEquals(priorConfig, config))
{
return;
}
priorConfig = config;
// URPMaterialPropertyBaseColor 컴포넌트를 가진 모든 엔티티들을 파괴하는 작업을 수행
var query = SystemAPI.QueryBuilder().WithAll<URPMaterialPropertyBaseColor>().Build();
state.EntityManager.DestroyEntity(query);
// Prefab을 기반으로 큐브를 생성
var entities = state.EntityManager.Instantiate(config.Prefab, (int)(config.Size * config.Size), Allocator.Temp);
var center = (config.Size - 1) / 2f;
int i = 0;
foreach (var transform in SystemAPI.Query<RefRW<LocalTransform>>())
{
transform.ValueRW.Scale = 1;
transform.ValueRW.Position.x = (i % config.Size - center) * 1.5f;
transform.ValueRW.Position.z = (i / config.Size - center) * 1.5f;
i++;
}
var spinQuery = SystemAPI.QueryBuilder().WithAll<Spin>().Build();
if (config.Mode == Mode.VALUE)
{
state.EntityManager.AddComponent<Spin>(query);
}
else if (config.Mode == Mode.ENABLEABLE_COMPONENT)
{
state.EntityManager.AddComponent<Spin>(query);
state.EntityManager.SetComponentEnabled<Spin>(spinQuery, false);
}
}
bool ConfigEquals(Config c1, Config c2)
{
return c1.Size == c2.Size && c1.Radius == c2.Radius && c1.Mode == c2.Mode;
}
}
}



