StatefulTriggerEventBufferAuthoring.cs
코드 설명
이 코드는 TriggerEvent를 StatefulTriggerEvent로 변환하여 Dynamic Buffer에 저장하는 역할을 합니다.
특정 엔티티에 StatefulTriggerEventBufferAuthoring
컴포넌트를 추가하면 Trigger 이벤트의 상태(Enter
, Stay
, Exit
)를 추적할 수 있습니다.
using Unity.Assertions; using Unity.Entities; using UnityEngine; namespace Unity.Physics.Stateful { // StatefulTriggerEventBufferAuthoring 컴포넌트 // GameObject에 추가하면 해당 오브젝트는 ECS Entity로 변환될 때 StatefulTriggerEvent 버퍼를 갖게 됨. public class StatefulTriggerEventBufferAuthoring : MonoBehaviour { // Baker 클래스: GameObject를 ECS Entity로 변환하는 역할 class Baker : Baker<StatefulTriggerEventBufferAuthoring> { public override void Bake(StatefulTriggerEventBufferAuthoring authoring) { // 현재 GameObject를 ECS Entity로 변환 (Transform 사용 방식: 동적) var entity = GetEntity(TransformUsageFlags.Dynamic); // StatefulTriggerEvent를 저장할 Dynamic Buffer 추가 AddBuffer<StatefulTriggerEvent>(entity); } } } // StatefulTriggerEvent 구조체 // Trigger 이벤트를 저장할 수 있는 데이터 구조 (IStatefulSimulationEvent 인터페이스 구현) public struct StatefulTriggerEvent : IBufferElementData, IStatefulSimulationEvent<StatefulTriggerEvent> { // 트리거 이벤트에 연관된 두 개의 엔티티 public Entity EntityA { get; set; } public Entity EntityB { get; set; } // 물리 본체(Body)의 인덱스 public int BodyIndexA { get; set; } public int BodyIndexB { get; set; } // 충돌한 Collider의 키 public ColliderKey ColliderKeyA { get; set; } public ColliderKey ColliderKeyB { get; set; } // 현재 이벤트 상태 (Enter, Stay, Exit) public StatefulEventState State { get; set; } // 생성자: 기존의 TriggerEvent 데이터를 StatefulTriggerEvent로 변환 public StatefulTriggerEvent(TriggerEvent triggerEvent) { EntityA = triggerEvent.EntityA; EntityB = triggerEvent.EntityB; BodyIndexA = triggerEvent.BodyIndexA; BodyIndexB = triggerEvent.BodyIndexB; ColliderKeyA = triggerEvent.ColliderKeyA; ColliderKeyB = triggerEvent.ColliderKeyB; State = default; // 초기 상태 설정 (Enter, Stay, Exit 없음) } // 특정 엔티티에 대해, 이벤트에 연관된 '다른 엔티티'를 반환하는 메서드 public Entity GetOtherEntity(Entity entity) { // 전달된 엔티티가 EntityA 또는 EntityB인지 검증 (디버그용) Assert.IsTrue((entity == EntityA) || (entity == EntityB)); // EntityA가 입력되면 EntityB를 반환하고, 반대로 EntityB가 입력되면 EntityA 반환 return (entity == EntityA) ? EntityB : EntityA; } // 이벤트 비교를 위한 메서드 (정렬, 검색 등에 사용 가능) public int CompareTo(StatefulTriggerEvent other) { return ISimulationEventUtilities.CompareEvents(this, other); } } // StatefulTriggerEvent를 제외하는 컴포넌트 // 이 컴포넌트가 추가된 엔티티는 StatefulTriggerEventBufferSystem에서 이벤트를 수집하지 않음 public struct StatefulTriggerEventExclude : IComponentData { } }
Unity의 GameObject를 ECS(Entity-Component-System) 엔티티로 변환하는 역할
Baker
클래스에서 StatefulTriggerEvent
를 저장할 Dynamic Buffer를 추가함
이를 통해 Trigger 이벤트를 저장하는 버퍼를 ECS 엔티티에 추가 가능
해당 엔티티는 StatefulTriggerEvent
버퍼를 가지게 됨.
TriggerEvent
정보를 저장하는 Stateful 버전StatefulEventState
를 추가하여 Trigger 이벤트의 상태(Enter, Stay, Exit)를 추적- 기존
TriggerEvent
는 한 프레임 동안만 존재 → 이를StatefulTriggerEvent
로 변환하여 지속적인 상태 추적 가능
필드명 | 설명 |
---|---|
EntityA , EntityB | 트리거 이벤트가 발생한 두 개의 엔티티 |
BodyIndexA , BodyIndexB | 각각의 물리 본체(Body)의 인덱스 |
ColliderKeyA , ColliderKeyB | 충돌한 Collider 의 키 |
State | Enter, Stay, Exit 상태를 저장 |
- 특정 엔티티가 트리거 이벤트에 포함된 다른 엔티티를 가져옴
EntityA
를 전달하면EntityB
를 반환하고, 반대로EntityB
를 전달하면EntityA
를 반환
StatefulTriggerEventBufferSystem
이 특정 엔티티의 트리거 이벤트를 저장하지 않도록 제외 처리 (쿼리에서 제외)
StatefulTriggerEventBufferSystem.cs
코드 설명
- 트리거 이벤트를 StatefulTriggerEvent로 변환하여 Dynamic Buffer에 저장
Enter
,Stay
,Exit
이벤트를 추적하여 충돌 지속 여부를 확인 가능OnCreate()
StatefulSimulationEventBuffers
를 생성하고 이벤트 저장을 위한 버퍼를 할당StatefulTriggerEvent
를 포함하고StatefulTriggerEventExclude
가 없는 엔티티를 찾는 쿼리 설정
OnUpdate()
- 기존 이벤트 버퍼 초기화
TriggerEvent
데이터를 수집하고, StatefulTriggerEvent로 변환하여 저장
OnDestroy()
- 사용했던 메모리를 해제하여 최적화
using Unity.Entities; using Unity.Jobs; using Unity.Physics.Systems; using Unity.Collections; using Unity.Burst; namespace Unity.Physics.Stateful { // 이 시스템은 TriggerEvent의 스트림을 StatefulTriggerEvent로 변환하여 Dynamic Buffer에 저장할 수 있도록 합니다. // 이를 통해 충돌 상태(Enter, Stay, Exit)를 추적할 수 있습니다. [UpdateInGroup(typeof(PhysicsSystemGroup))] // PhysicsSystemGroup 내에서 실행됨 [UpdateAfter(typeof(PhysicsSimulationGroup))] // PhysicsSimulationGroup 이후에 실행됨 public partial struct StatefulTriggerEventBufferSystem : ISystem { // 현재 및 이전 프레임의 Trigger 이벤트를 저장하는 버퍼 private StatefulSimulationEventBuffers<StatefulTriggerEvent> m_StateFulEventBuffers; // 이벤트 처리에 필요한 컴포넌트 조회 핸들 private ComponentHandles m_ComponentHandles; // 트리거 이벤트를 처리할 엔티티 쿼리 private EntityQuery m_TriggerEventQuery; // 이벤트를 저장할 컴포넌트 핸들 구조체 struct ComponentHandles { // 특정 이벤트를 제외할 엔티티 조회 public ComponentLookup<StatefulTriggerEventExclude> EventExcludes; // StatefulTriggerEvent를 저장할 Dynamic Buffer 조회 public BufferLookup<StatefulTriggerEvent> EventBuffers; // 컴포넌트 핸들 초기화 public ComponentHandles(ref SystemState systemState) { EventExcludes = systemState.GetComponentLookup<StatefulTriggerEventExclude>(true); EventBuffers = systemState.GetBufferLookup<StatefulTriggerEvent>(); } // 최신 상태로 업데이트 public void Update(ref SystemState systemState) { EventExcludes.Update(ref systemState); EventBuffers.Update(ref systemState); } } [BurstCompile] public void OnCreate(ref SystemState state) { // 트리거 이벤트를 처리할 엔티티 쿼리 생성 // (StatefulTriggerEvent가 있으며, StatefulTriggerEventExclude가 없는 엔티티) EntityQueryBuilder builder = new EntityQueryBuilder(Allocator.Temp) .WithAllRW<StatefulTriggerEvent>() // StatefulTriggerEvent를 읽고 쓸 수 있는 엔티티 선택 .WithNone<StatefulTriggerEventExclude>(); // StatefulTriggerEventExclude가 없는 엔티티만 선택 // Stateful 이벤트 버퍼를 생성하고 할당 m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulTriggerEvent>(); m_StateFulEventBuffers.AllocateBuffers(); // 쿼리 설정 및 시스템 업데이트 필요성 명시 m_TriggerEventQuery = state.GetEntityQuery(builder); state.RequireForUpdate(m_TriggerEventQuery); // 컴포넌트 핸들 초기화 m_ComponentHandles = new ComponentHandles(ref state); } [BurstCompile] public void OnDestroy(ref SystemState state) { // Stateful 이벤트 버퍼 해제 m_StateFulEventBuffers.Dispose(); } // 트리거 이벤트 버퍼를 초기화하는 Job [BurstCompile] public partial struct ClearTriggerEventDynamicBufferJob : IJobEntity { // 이벤트 버퍼 초기화 public void Execute(ref DynamicBuffer<StatefulTriggerEvent> eventBuffer) => eventBuffer.Clear(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // 최신 상태로 컴포넌트 핸들 업데이트 m_ComponentHandles.Update(ref state); // 기존 트리거 이벤트 버퍼 초기화 (병렬 실행) state.Dependency = new ClearTriggerEventDynamicBufferJob() .ScheduleParallel(m_TriggerEventQuery, state.Dependency); // 이전 프레임과 현재 프레임의 이벤트 버퍼를 교체 m_StateFulEventBuffers.SwapBuffers(); var currentEvents = m_StateFulEventBuffers.Current; // 현재 프레임의 이벤트 var previousEvents = m_StateFulEventBuffers.Previous; // 이전 프레임의 이벤트 // 새로운 Trigger 이벤트를 수집하는 Job 실행 state.Dependency = new StatefulEventCollectionJobs.CollectTriggerEvents { TriggerEvents = currentEvents }.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency); // 트리거 이벤트를 StatefulTriggerEvent로 변환하여 Dynamic Buffer에 저장하는 Job 실행 state.Dependency = new StatefulEventCollectionJobs.ConvertEventStreamToDynamicBufferJob<StatefulTriggerEvent, StatefulTriggerEventExclude> { CurrentEvents = currentEvents, // 현재 프레임의 이벤트 PreviousEvents = previousEvents, // 이전 프레임의 이벤트 EventLookup = m_ComponentHandles.EventBuffers, // 이벤트 버퍼 조회 UseExcludeComponent = true, // 제외할 컴포넌트(StatefulTriggerEventExclude) 사용 여부 EventExcludeLookup = m_ComponentHandles.EventExcludes // 제외할 이벤트 조회 }.Schedule(state.Dependency); } } }
ComponentHandles
: StatefulTriggerEventExclude 및 StatefulTriggerEvent 버퍼를 조회하는 핸들을 보유하는 구조체
EventExcludes
: 트리거 이벤트가 수집되지 않도록 제외할 엔티티를 조회합니다.EventBuffers
: StatefulTriggerEvent를 저장하는 Dynamic Buffer를 조회합니다.
트리거 이벤트를 처리할 쿼리 생성:
StatefulTriggerEvent
컴포넌트를 읽고 쓸 수 있는 엔티티를 선택하고,StatefulTriggerEventExclude
가 없는 엔티티만 선택하여 트리거 이벤트를 수집합니다.
m_StateFulEventBuffers
를 초기화하고, 버퍼를 할당합니다.
m_TriggerEventQuery
쿼리를 설정하여 이 쿼리가 시스템 업데이트에 필요함을 명시합니다.
컴포넌트 핸들 업데이트: m_ComponentHandles
를 최신 상태로 업데이트합니다.
버퍼 초기화: ClearTriggerEventDynamicBufferJob
을 병렬로 실행하여 이전 트리거 이벤트 버퍼를 초기화합니다.
이벤트 버퍼 교체: SwapBuffers
를 사용하여 현재 프레임과 이전 프레임의 버퍼를 교체합니다.
Trigger 이벤트 수집: StatefulEventCollectionJobs.CollectTriggerEvents
를 통해 새로운 트리거 이벤트를 수집합니다.
StatefulTriggerEvent 변환: 수집한 트리거 이벤트를 StatefulTriggerEvent로 변환하고 Dynamic Buffer에 저장합니다.
IStatefulSimulationEvent.cs
충돌 상태 변화에 대한 추적을 가능하게 하여, 물체 간 상호작용의 상태를 세분화하고, 이를 이벤트 시스템을 통해 관리
using Unity.Entities; namespace Unity.Physics.Stateful { // 이벤트 상태를 설명합니다. // 이벤트 상태는 다음과 같이 설정됩니다: // 0) Undefined (정의되지 않음): 상태가 불명확하거나 필요하지 않을 때 // 1) Enter (진입): 두 물체가 현재 프레임에서 상호작용하고 있지만, 이전 프레임에서는 상호작용하지 않았을 때 // 2) Stay (유지): 두 물체가 현재 프레임에서 상호작용 중이고, 이전 프레임에서도 상호작용했을 때 // 3) Exit (탈출): 두 물체가 현재 프레임에서 상호작용하지 않지만, 이전 프레임에서는 상호작용했을 때 public enum StatefulEventState : byte { Undefined, // 상태가 정의되지 않음 Enter, // 두 물체가 현재 프레임에서 상호작용 중이지만 이전 프레임에서는 상호작용하지 않았을 때 Stay, // 두 물체가 현재 프레임과 이전 프레임에서 모두 상호작용하고 있을 때 Exit // 두 물체가 현재 프레임에서 상호작용하지 않지만 이전 프레임에서는 상호작용했을 때 } // 추가적인 StatefulEventState를 포함하여 ISimulationEvent를 확장합니다. public interface IStatefulSimulationEvent<T> : IBufferElementData, ISimulationEvent<T> { public StatefulEventState State { get; set; } // 이벤트 상태 } }
StatefulJobs.cs
Unity에서 물리 시뮬레이션의 충돌 이벤트를 상태 기반으로 추적하고, 이를 Dynamic Buffer
에 저장하는 시스템을 구현하는 데 사용됩니다.
여기서는 StatefulEventCollectionJobs
라는 이름의 클래스가 주요한 역할을 합니다.
여러 개의 Job을 정의하여 충돌 이벤트를 수집하고, 이를 상태 기반 이벤트로 변환하여 저장합니다.
using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Jobs; namespace Unity.Physics.Stateful { public static class StatefulEventCollectionJobs { // Trigger 이벤트를 StatefulTriggerEvent로 변환하여 NativeList에 추가하는 Job [BurstCompile] public struct CollectTriggerEvents : ITriggerEventsJob { // 저장할 Trigger 이벤트를 담을 리스트 public NativeList<StatefulTriggerEvent> TriggerEvents; // TriggerEvent를 StatefulTriggerEvent로 변환하여 리스트에 추가 public void Execute(TriggerEvent triggerEvent) => TriggerEvents.Add(new StatefulTriggerEvent(triggerEvent)); } // Collision 이벤트를 StatefulCollisionEvent로 변환하여 NativeList에 추가하는 Job [BurstCompile] public struct CollectCollisionEvents : ICollisionEventsJob { // 저장할 Collision 이벤트를 담을 리스트 public NativeList<StatefulCollisionEvent> CollisionEvents; // CollisionEvent를 StatefulCollisionEvent로 변환하여 리스트에 추가 public void Execute(CollisionEvent collisionEvent) => CollisionEvents.Add(new StatefulCollisionEvent(collisionEvent)); } // Collision 이벤트와 함께 세부 정보를 계산하여 StatefulCollisionEvent로 변환하고 NativeList에 추가하는 Job [BurstCompile] public struct CollectCollisionEventsWithDetails : ICollisionEventsJob { // 저장할 Collision 이벤트를 담을 리스트 public NativeList<StatefulCollisionEvent> CollisionEvents; // 물리 월드와 세부 사항 계산에 사용할 컴포넌트 조회 [ReadOnly] public PhysicsWorld PhysicsWorld; [ReadOnly] public ComponentLookup<StatefulCollisionEventDetails> EventDetails; // 세부 사항 계산을 강제할지 여부 public bool ForceCalculateDetails; // CollisionEvent를 처리하고 세부 사항을 계산하여 StatefulCollisionEvent로 변환 public void Execute(CollisionEvent collisionEvent) { var statefulCollisionEvent = new StatefulCollisionEvent(collisionEvent); // 세부 사항 계산 여부 결정 bool calculateDetails = ForceCalculateDetails; if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityA)) { calculateDetails = EventDetails[collisionEvent.EntityA].CalculateDetails; } if (!calculateDetails && EventDetails.HasComponent(collisionEvent.EntityB)) { calculateDetails = EventDetails[collisionEvent.EntityB].CalculateDetails; } // 세부 사항을 계산하고 상태에 저장 if (calculateDetails) { var details = collisionEvent.CalculateDetails(ref PhysicsWorld); statefulCollisionEvent.CollisionDetails = new StatefulCollisionEvent.Details( details.EstimatedContactPointPositions.Length, details.EstimatedImpulse, details.AverageContactPointPosition); } // 계산된 CollisionEvent를 리스트에 추가 CollisionEvents.Add(statefulCollisionEvent); } } // 이벤트 스트림을 Dynamic Buffer로 변환하는 Job // T: StatefulSimulationEvent 타입 // C: 제외할 컴포넌트 타입 [BurstCompile] public struct ConvertEventStreamToDynamicBufferJob<T, C> : IJob where T : unmanaged, IBufferElementData, IStatefulSimulationEvent<T> // T는 IStatefulSimulationEvent를 구현해야 함 where C : unmanaged, IComponentData // C는 IComponentData를 구현해야 함 { // 이전과 현재의 이벤트 리스트 public NativeList<T> PreviousEvents; public NativeList<T> CurrentEvents; // 이벤트를 저장할 Dynamic Buffer 조회 public BufferLookup<T> EventLookup; // 제외할 컴포넌트를 사용할지 여부 public bool UseExcludeComponent; // 제외할 컴포넌트를 조회하는 컴포넌트 핸들 [ReadOnly] public ComponentLookup<C> EventExcludeLookup; // 이벤트를 변환하고 Dynamic Buffer에 추가하는 작업 public void Execute() { // 이벤트를 저장할 임시 리스트 var statefulEvents = new NativeList<T>(CurrentEvents.Length, Allocator.Temp); // 이전 및 현재 이벤트에서 상태를 기반으로 상태 변경된 이벤트를 가져옴 StatefulSimulationEventBuffers<T>.GetStatefulEvents(PreviousEvents, CurrentEvents, statefulEvents); // 상태가 변경된 이벤트를 처리 for (int i = 0; i < statefulEvents.Length; i++) { var statefulEvent = statefulEvents[i]; // 이벤트가 추가될 엔티티 A와 B가 동적으로 버퍼를 가지고 있는지 확인 var addToEntityA = EventLookup.HasBuffer(statefulEvent.EntityA) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA)); var addToEntityB = EventLookup.HasBuffer(statefulEvent.EntityB) && (!UseExcludeComponent || !EventExcludeLookup.HasComponent(statefulEvent.EntityA)); // 엔티티 A와 B에 이벤트를 추가 if (addToEntityA) { EventLookup[statefulEvent.EntityA].Add(statefulEvent); } if (addToEntityB) { EventLookup[statefulEvent.EntityB].Add(statefulEvent); } } } } } }
TriggerEvent
객체를 받아 StatefulTriggerEvent
로 변환한 후 TriggerEvents
리스트에 추가하는 작업을 수행합니다.
TriggerEvent
: 두 개체 간의 충돌 여부를 나타내는 기본적인 이벤트입니다.
StatefulTriggerEvent
: 이 구조체는 충돌 상태를 추적할 수 있도록 StatefulEventState
(Enter, Stay, Exit, Undefined) 상태를 추가하여 TriggerEvent
를 확장한 것입니다.
Execute
메서드는 주어진 TriggerEvent
를 StatefulTriggerEvent
로 변환하여 TriggerEvents
리스트에 추가합니다.
CollisionEvent
를 받아 StatefulCollisionEvent
로 변환한 후 CollisionEvents
리스트에 추가합니다.
CollisionEvent
: 두 개체 간의 충돌을 나타내는 이벤트입니다.
StatefulCollisionEvent
: 충돌 상태를 추적하기 위해 StatefulEventState
를 추가한 CollisionEvent
입니다.
Execute
메서드는 CollisionEvent
를 StatefulCollisionEvent
로 변환하여 CollisionEvents
리스트에 추가합니다.
충돌 이벤트와 함께 세부 정보를 계산하고 이를 StatefulCollisionEvent
로 변환하여 CollisionEvents
리스트에 추가합니다.
ForceCalculateDetails
: 세부 사항을 강제로 계산할지 여부를 나타내는 플래그입니다.
EventDetails
: 각 개체에 대한 세부 사항을 계산할지 여부를 결정하는 StatefulCollisionEventDetails
컴포넌트의 조회입니다.
Execute
메서드는CollisionEvent
를StatefulCollisionEvent
로 변환한 후, 해당 이벤트에 대해 세부 사항을 계산합니다.CalculateDetails
메서드는 충돌 세부 사항을 계산하고, 그 결과를StatefulCollisionEvent
의CollisionDetails
에 저장합니다.- 세부 사항을 계산할 필요가 없다면 기본적으로 세부 사항을 계산하지 않습니다.
세부 사항 계산
Estimated Contact Point Positions (예상 접촉 지점 위치):
- 충돌이 발생했을 때 예상되는 접촉 지점의 위치들
- 이는 충돌이 발생한 표면에서 실제로 물리적으로 접촉한 지점들
- 예를 들어, 두 물체가 충돌하면서 여러 접촉 지점이 생길 수 있다.
Estimated Impulse (예상 충격량):
- 두 객체 간의 충돌로 발생하는 충격량입니다. 충돌 후 물체가 받을 힘을 측정하는 값
- 충돌 전후의 물리적 특성(속도 변화, 질량 등)을 바탕으로 계산
- 이는 두 객체 간의 충돌로 인해 변화하는 운동량
Average Contact Point Position (평균 접촉 지점 위치):
- 여러 접촉 지점이 있다면, 그 중에서 평균적인 접촉 지점 위치를 계산한 것
- 이는 충돌이 발생한 여러 지점들 중에서, 전체적인 충격을 대표할 수 있는 중간 지점을 제공하는 역할
상태 기반 이벤트 스트림을 Dynamic Buffer
로 변환하는 작업을 수행합니다.
EventLookup
: StatefulTriggerEvent
와 StatefulCollisionEvent
와 같은 상태 기반 이벤트를 저장할 Dynamic Buffer
의 조회입니다.
UseExcludeComponent
: 특정 컴포넌트를 제외할지 여부를 결정하는 플래그입니다.
PreviousEvents
와CurrentEvents
에서 상태 기반 이벤트를 비교하여 상태 변화가 있는 이벤트를 가져옵니다.- 변환된 이벤트를
EventLookup
에 해당하는 엔티티의Dynamic Buffer
에 추가합니다. EventExcludeLookup
를 사용하여 제외할 컴포넌트가 있으면 해당 엔티티에 대한 이벤트 추가를 방지합니다.
StatefulSimulationEventBuffers.cs
StatefulSimulationEventBuffers
라는 구조체로, 상태를 추적할 수 있는 이벤트 버퍼를 관리합니다.
정의된 함수들은 물리 시뮬레이션 이벤트의 상태를 추적하고 이를 처리하는데 필요한 버퍼 관리 및 이벤트 비교 기능을 제공합니다. (주로 충돌이나 상호작용 이벤트를 처리 )
각 이벤트가 이전 프레임과 비교하여 어떤 상태(Enter, Stay, Exit)에 해당하는지 판단하고, 이를 기반으로 상태를 변경하여 결과를 반환합니다.
using Unity.Collections; namespace Unity.Physics.Stateful { // StatefulSimulationEventBuffers는 이벤트의 상태를 추적하는 두 버퍼를 관리하는 구조체입니다. // 이 구조체는 지난 프레임과 현재 프레임의 이벤트를 저장하며, 충돌 이벤트에 대한 상태 변경을 추적합니다. public struct StatefulSimulationEventBuffers<T> where T : unmanaged, IStatefulSimulationEvent<T> { // 이전 프레임의 이벤트를 저장하는 리스트 public NativeList<T> Previous; // 현재 프레임의 이벤트를 저장하는 리스트 public NativeList<T> Current; // 이벤트 버퍼 할당 public void AllocateBuffers() { // 이전 및 현재 프레임 이벤트를 저장할 버퍼를 할당 Previous = new NativeList<T>(Allocator.Persistent); Current = new NativeList<T>(Allocator.Persistent); } // 이벤트 버퍼를 해제 public void Dispose() { // 버퍼가 생성되었으면 해제 if (Previous.IsCreated) Previous.Dispose(); if (Current.IsCreated) Current.Dispose(); } // 이전 프레임과 현재 프레임의 버퍼를 교환 public void SwapBuffers() { // 이전과 현재 이벤트 버퍼를 교환 var tmp = Previous; Previous = Current; Current = tmp; // 현재 버퍼를 비움 (새로운 이벤트로 덮어쓰기 위해) Current.Clear(); } /// <summary> /// 이전 프레임과 현재 프레임의 이벤트를 결합하여 하나의 정렬된 리스트로 반환합니다. /// 반드시 SortBuffers를 먼저 호출하여 정렬된 상태로 만들어야 합니다. /// </summary> /// <param name="statefulEvents">결합된 이벤트 리스트</param> /// <param name="sortCurrent">현재 이벤트 리스트를 정렬해야 하는지 여부</param> public void GetStatefulEvents(NativeList<T> statefulEvents, bool sortCurrent = true) => GetStatefulEvents(Previous, Current, statefulEvents, sortCurrent); /// <summary> /// 두 개의 정렬된 이벤트 버퍼를 결합하여 하나의 리스트로 반환합니다. /// 각 이벤트에는 적절한 StatefulEventState가 설정됩니다. /// </summary> /// <param name="previousEvents">이전 프레임의 이벤트 버퍼</param> /// <param name="currentEvents">현재 프레임의 이벤트 버퍼</param> /// <param name="statefulEvents">결합된 이벤트 리스트</param> /// <param name="sortCurrent">현재 이벤트 리스트를 정렬해야 하는지 여부</param> public static void GetStatefulEvents(NativeList<T> previousEvents, NativeList<T> currentEvents, NativeList<T> statefulEvents, bool sortCurrent = true) { // 현재 이벤트 리스트가 정렬되지 않았다면 정렬 if (sortCurrent) currentEvents.Sort(); // 새로운 리스트를 비움 statefulEvents.Clear(); int c = 0; // 현재 이벤트 리스트의 인덱스 int p = 0; // 이전 이벤트 리스트의 인덱스 // 현재 이벤트와 이전 이벤트를 비교하며 상태 변경 while (c < currentEvents.Length && p < previousEvents.Length) { // 두 이벤트를 비교 int r = previousEvents[p].CompareTo(currentEvents[c]); if (r == 0) { // 현재 이벤트가 이전 이벤트와 동일하다면 'Stay' 상태로 추가 var currentEvent = currentEvents[c]; currentEvent.State = StatefulEventState.Stay; statefulEvents.Add(currentEvent); c++; p++; } else if (r < 0) { // 이전 이벤트가 현재 이벤트보다 먼저 왔다면 'Exit' 상태로 추가 var previousEvent = previousEvents[p]; previousEvent.State = StatefulEventState.Exit; statefulEvents.Add(previousEvent); p++; } else //(r > 0) { // 현재 이벤트가 이전 이벤트보다 먼저 왔다면 'Enter' 상태로 추가 var currentEvent = currentEvents[c]; currentEvent.State = StatefulEventState.Enter; statefulEvents.Add(currentEvent); c++; } } // 현재 이벤트가 다 처리되었고, 이전 이벤트가 남았다면 'Exit' 상태로 추가 if (c == currentEvents.Length) { while (p < previousEvents.Length) { var previousEvent = previousEvents[p]; previousEvent.State = StatefulEventState.Exit; statefulEvents.Add(previousEvent); p++; } } // 이전 이벤트가 다 처리되었고, 현재 이벤트가 남았다면 'Enter' 상태로 추가 else if (p == previousEvents.Length) { while (c < currentEvents.Length) { var currentEvent = currentEvents[c]; currentEvent.State = StatefulEventState.Enter; statefulEvents.Add(currentEvent); c++; } } } } }
AllocateBuffers()
Previous
와 Current
두 개의 이벤트 버퍼를 할당합니다.
이 함수는 NativeList<T>
타입의 Previous
와 Current
버퍼를 할당합니다.
이 버퍼들은 각 프레임의 이벤트들을 저장하는 데 사용됩니다.
Allocator.Persistent
는 이 메모리가 여러 프레임에 걸쳐 유지될 수 있도록 해줍니다.
Dispose()
할당된 Previous
와 Current
이벤트 버퍼를 해제합니다.
Dispose()
는 AllocateBuffers()
에서 할당한 Previous
와 Current
버퍼를 메모리에서 해제하는 역할을 합니다.
IsCreated
를 통해 버퍼가 생성되었는지 확인한 후, 생성된 버퍼에 대해 Dispose()
를 호출하여 메모리 관리를 수행합니다.
SwapBuffers()
Previous
와 Current
버퍼를 교환하고, Current
버퍼를 비웁니다.
이 함수는 이전 프레임의 이벤트(Previous
)와 현재 프레임의 이벤트(Current
)를 교환합니다.
그 후, Current
버퍼를 비워서 다음 프레임의 데이터를 받을 준비를 합니다.
교환하는 이유는 이벤트가 프레임 간에 이동하면서 상태를 추적하고 업데이트하는 방식 때문입니다.
Previous
와 Current
이벤트 리스트를 결합하여 상태별 이벤트 리스트를 반환합니다.
이 함수는 두 개의 이벤트 리스트인 Previous
와 Current
를 결합하는 작업을 수행하는 GetStatefulEvents()
의 래퍼(wrapper) 함수입니다.
sortCurrent
파라미터를 사용하여 Current
리스트가 정렬되어야 하는지를 결정합니다.
기본적으로 sortCurrent
는 true
로 설정되어 있어 Current
리스트가 정렬됩니다.
두 개의 정렬된 이벤트 리스트를 결합하여 하나의 상태별 이벤트 리스트로 반환합니다.
주어진 두 개의 이벤트 리스트(previousEvents
와 currentEvents
)를 비교하고, 각 이벤트에 적절한 StatefulEventState
를 설정합니다.
각 상태는 Enter
, Stay
, Exit
으로 나뉩니다.
Enter
: 현재 프레임에 새롭게 등장한 이벤트.Stay
: 이전과 현재 프레임 모두에서 존재하는 이벤트.Exit
: 이전 프레임에는 존재했지만 현재 프레임에는 없는 이벤트.
이 함수는 각 이벤트를 비교하여 상태를 설정하고, statefulEvents
에 추가합니다.
StatefulCollisionEventBufferAuthoring.cs
using System.Runtime.InteropServices; using Unity.Assertions; using Unity.Entities; using Unity.Mathematics; using UnityEngine; namespace Unity.Physics.Stateful { // StatefulCollisionEventBufferAuthoring은 Unity의 MonoBehaviour로, // 물리 충돌 이벤트가 포함된 버퍼를 정의하고, 해당 엔티티에 대해 세부 사항을 계산할지 여부를 결정합니다. public class StatefulCollisionEventBufferAuthoring : MonoBehaviour { [Tooltip("선택하면 이 엔티티의 충돌 이벤트 동적 버퍼에서 세부 정보가 계산됩니다")] public bool CalculateDetails = false; // 충돌 이벤트에 세부 사항을 계산할지 여부를 결정하는 변수 // Baker 클래스는 Authoring 컴포넌트에서 데이터를 추출하고 Entity로 변환하는 역할을 합니다. class StatefulCollisionEventBufferBaker : Baker<StatefulCollisionEventBufferAuthoring> { public override void Bake(StatefulCollisionEventBufferAuthoring authoring) { // TransformUsageFlags.Dynamic 플래그를 사용하여 해당 엔티티를 생성합니다. var entity = GetEntity(TransformUsageFlags.Dynamic); // CalculateDetails가 true일 경우, StatefulCollisionEventDetails를 엔티티에 추가합니다. if (authoring.CalculateDetails) { var dynamicBufferTag = new StatefulCollisionEventDetails { CalculateDetails = authoring.CalculateDetails }; AddComponent(entity, dynamicBufferTag); // StatefulCollisionEventDetails 컴포넌트 추가 } // 충돌 이벤트를 저장할 버퍼 추가 AddBuffer<StatefulCollisionEvent>(entity); } } } // StatefulCollisionEventDetails 컴포넌트는 엔티티가 세부 사항을 계산할지를 설정하는 플래그입니다. public struct StatefulCollisionEventDetails : IComponentData { public bool CalculateDetails; // 세부 사항을 계산할지 여부를 설정하는 변수 } // StatefulCollisionEvent는 DynamicBuffer에 저장할 수 있는 충돌 이벤트 구조체입니다. public struct StatefulCollisionEvent : IBufferElementData, IStatefulSimulationEvent<StatefulCollisionEvent> { public Entity EntityA { get; set; } // 첫 번째 엔티티 public Entity EntityB { get; set; } // 두 번째 엔티티 public int BodyIndexA { get; set; } // 첫 번째 엔티티의 바디 인덱스 public int BodyIndexB { get; set; } // 두 번째 엔티티의 바디 인덱스 public ColliderKey ColliderKeyA { get; set; } // 첫 번째 엔티티의 충돌체 키 public ColliderKey ColliderKeyB { get; set; } // 두 번째 엔티티의 충돌체 키 public StatefulEventState State { get; set; } // 이벤트 상태 (Enter, Stay, Exit 등) public float3 Normal; // 충돌 법선 벡터 // CalculateDetails가 체크된 경우, 이 필드는 유효한 값이 있으며 그렇지 않으면 기본값으로 초기화됩니다. internal Details CollisionDetails; // 생성자: CollisionEvent를 StatefulCollisionEvent로 변환합니다. public StatefulCollisionEvent(CollisionEvent collisionEvent) { EntityA = collisionEvent.EntityA; EntityB = collisionEvent.EntityB; BodyIndexA = collisionEvent.BodyIndexA; BodyIndexB = collisionEvent.BodyIndexB; ColliderKeyA = collisionEvent.ColliderKeyA; ColliderKeyB = collisionEvent.ColliderKeyB; State = default; Normal = collisionEvent.Normal; CollisionDetails = default; } // 충돌 세부 사항을 설명하는 구조체 public struct Details { internal bool IsValid; // 세부 사항이 유효한지 여부 // 1이면 정점 충돌, 2이면 모서리 충돌, 3 이상이면 면 충돌 public int NumberOfContactPoints; // 추정된 임펄스 public float EstimatedImpulse; // 평균 접촉 지점 위치 public float3 AverageContactPointPosition; // 생성자: 세부 사항을 초기화합니다. public Details(int numContactPoints, float estimatedImpulse, float3 averageContactPosition) { IsValid = (0 < numContactPoints); // 유효한 접촉점이 있을 경우에만 세부 사항이 유효함 NumberOfContactPoints = numContactPoints; EstimatedImpulse = estimatedImpulse; AverageContactPointPosition = averageContactPosition; } } // 주어진 엔티티와 다른 엔티티를 반환하는 함수 public Entity GetOtherEntity(Entity entity) { Assert.IsTrue((entity == EntityA) || (entity == EntityB)); // 엔티티 A 또는 B만 유효함 return entity == EntityA ? EntityB : EntityA; } // 주어진 엔티티에서 다른 엔티티로 향하는 법선 벡터를 반환합니다. public float3 GetNormalFrom(Entity entity) { Assert.IsTrue((entity == EntityA) || (entity == EntityB)); // 엔티티 A 또는 B만 유효함 return math.select(-Normal, Normal, entity == EntityB); // EntityB에 대해서는 반대 방향 법선을 반환 } // 충돌 세부 사항이 유효한지 확인하고, 유효한 경우 세부 사항을 반환합니다. public bool TryGetDetails(out Details details) { details = CollisionDetails; return CollisionDetails.IsValid; // 세부 사항이 유효한지 여부를 반환 } // 이벤트를 비교하는 함수 public int CompareTo(StatefulCollisionEvent other) => ISimulationEventUtilities.CompareEvents(this, other); } }
StatefulCollisionEventBufferAuthoring
은 MonoBehaviour
를 상속받는 클래스
이 클래스는 CalculateDetails
라는 변수를 통해 세부 사항 계산 여부를 설정할 수 있도록 합니다.
StatefulCollisionEventBufferBaker
는 Baker
클래스로, StatefulCollisionEventBufferAuthoring
의 값을 기반으로 Entity를 생성합니다.
이 클래스에서 CalculateDetails
값이 true
일 경우, StatefulCollisionEventDetails
컴포넌트를 해당 엔티티에 추가합니다.
StatefulCollisionEventDetails
는 IComponentData
를 구현한 구조체로,
충돌 이벤트에 대해 세부 사항을 계산할지 여부를 설정하는 CalculateDetails
변수만을 포함하고 있습니다.
StatefulCollisionEvent
는 충돌 이벤트를 나타내는 구조체로, EntityA
와 EntityB
를 비롯한 충돌 관련 정보를 포함합니다.
State
는 충돌 상태를 추적하며, CollisionDetails
는 선택적으로 세부 충돌 정보를 포함합니다.
Details
구조체는 충돌의 세부 사항을 설명하며, 접촉 지점 수, 추정 임펄스 및 평균 접촉 지점 위치를 저장합니다.
StatefulCollisionEventBufferSystem.cs
CollisionEvent
를 StatefulCollisionEvent
로 변환하여 동적 버퍼에 저장하는 역할
using Unity.Burst; using Unity.Entities; using Unity.Jobs; using Unity.Physics.Stateful; using Unity.Physics; using Unity.Physics.Systems; // 이 시스템은 CollisionEvent 스트림을 StatefulCollisionEvent로 변환하여 Dynamic Buffer에 저장할 수 있도록 합니다. // 변환을 위해서는 아래의 조건을 만족해야 합니다: // 1) PhysicsShapeAuthoring 컴포넌트의 'Collision Response' 속성에서 'Collide Raise Collision Events' 옵션을 활성화하고, // 2) StatefulCollisionEventBufferAuthoring 컴포넌트를 엔티티에 추가하여 (세부 사항 계산 여부도 선택), // 또는, 만약 Character Controller에서 이를 원한다면: // 1) CharacterControllerAuthoring 컴포넌트에서 'Raise Collision Events' 플래그를 활성화합니다. [UpdateInGroup(typeof(PhysicsSystemGroup))] // 물리 시스템 그룹에 포함 [UpdateAfter(typeof(PhysicsSimulationGroup))] // 물리 시뮬레이션 그룹 후에 실행 public partial struct StatefulCollisionEventBufferSystem : ISystem { private StatefulSimulationEventBuffers<StatefulCollisionEvent> m_StateFulEventBuffers; // StatefulCollisionEvent 버퍼 private ComponentHandles m_Handles; // 컴포넌트 핸들 // 아무런 작업을 하지 않는 컴포넌트. 일반적인 job을 사용하기 위해 만들어졌습니다. OnUpdate() 메서드에서 설명 internal struct DummyExcludeComponent : IComponentData { }; // 컴포넌트 핸들 구조체 struct ComponentHandles { public ComponentLookup<DummyExcludeComponent> EventExcludes; // 이벤트 제외를 위한 컴포넌트 조회 public ComponentLookup<StatefulCollisionEventDetails> EventDetails; // 이벤트 세부 사항을 위한 컴포넌트 조회 public BufferLookup<StatefulCollisionEvent> EventBuffers; // 이벤트 버퍼 조회 // 시스템 상태에 맞는 컴포넌트 핸들을 초기화합니다. public ComponentHandles(ref SystemState systemState) { EventExcludes = systemState.GetComponentLookup<DummyExcludeComponent>(true); EventDetails = systemState.GetComponentLookup<StatefulCollisionEventDetails>(true); EventBuffers = systemState.GetBufferLookup<StatefulCollisionEvent>(false); } // 시스템 상태에 맞는 컴포넌트 핸들을 업데이트합니다. public void Update(ref SystemState systemState) { EventExcludes.Update(ref systemState); EventBuffers.Update(ref systemState); EventDetails.Update(ref systemState); } } // 시스템 초기화 [BurstCompile] public void OnCreate(ref SystemState state) { m_StateFulEventBuffers = new StatefulSimulationEventBuffers<StatefulCollisionEvent>(); // StatefulCollisionEvent 버퍼 할당 m_StateFulEventBuffers.AllocateBuffers(); // 버퍼 할당 state.RequireForUpdate<StatefulCollisionEvent>(); // 시스템이 업데이트 되도록 설정 m_Handles = new ComponentHandles(ref state); // 컴포넌트 핸들 초기화 } // 시스템 종료 [BurstCompile] public void OnDestroy(ref SystemState state) { m_StateFulEventBuffers.Dispose(); // 버퍼 해제 } // 충돌 이벤트 동적 버퍼를 지우는 작업을 수행하는 Job [BurstCompile] public partial struct ClearCollisionEventDynamicBufferJob : IJobEntity { public void Execute(ref DynamicBuffer<StatefulCollisionEvent> eventBuffer) => eventBuffer.Clear(); // 버퍼 초기화 } // 시스템 업데이트 메서드 [BurstCompile] public void OnUpdate(ref SystemState state) { m_Handles.Update(ref state); // 컴포넌트 핸들 업데이트 // 충돌 이벤트 동적 버퍼를 지우는 작업을 병렬로 스케줄 state.Dependency = new ClearCollisionEventDynamicBufferJob() .ScheduleParallel(state.Dependency); // 이전/현재 버퍼를 교환 m_StateFulEventBuffers.SwapBuffers(); var currentEvents = m_StateFulEventBuffers.Current; // 현재 이벤트 var previousEvents = m_StateFulEventBuffers.Previous; // 이전 이벤트 // 충돌 이벤트와 세부 사항을 수집하는 작업을 스케줄 state.Dependency = new StatefulEventCollectionJobs. CollectCollisionEventsWithDetails { CollisionEvents = currentEvents, PhysicsWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().PhysicsWorld, // 물리 월드 EventDetails = m_Handles.EventDetails // 이벤트 세부 사항 }.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency); // 이벤트 스트림을 동적 버퍼로 변환하는 작업을 스케줄 state.Dependency = new StatefulEventCollectionJobs. ConvertEventStreamToDynamicBufferJob<StatefulCollisionEvent, DummyExcludeComponent> { CurrentEvents = currentEvents, PreviousEvents = previousEvents, EventLookup = m_Handles.EventBuffers, // 이벤트 버퍼 조회 UseExcludeComponent = false, // 제외 컴포넌트 사용 안함 EventExcludeLookup = m_Handles.EventExcludes // 이벤트 제외 조회 }.Schedule(state.Dependency); } }
세부 작업:
- 컴포넌트 핸들 업데이트:
m_Handles.Update(ref state)
를 호출하여 컴포넌트 핸들을 최신 상태로 업데이트합니다. - 충돌 이벤트 버퍼 초기화:
ClearCollisionEventDynamicBufferJob
을 병렬로 스케줄하여 충돌 이벤트 버퍼를 지웁니다. - 이벤트 버퍼 교환:
SwapBuffers()
를 호출하여 현재와 이전 프레임의 이벤트 버퍼를 교환합니다.
이후 새로운 이벤트를currentEvents
에 기록하고, 이전 이벤트는previousEvents
로 저장됩니다. - 충돌 이벤트와 세부 사항 수집:
StatefulEventCollectionJobs.CollectCollisionEventsWithDetails
를 스케줄하여 충돌 이벤트와 관련된 세부 사항을 수집합니다.
물리 월드 및 이벤트 세부 사항도 이 작업에서 처리됩니다. - 이벤트 스트림을 동적 버퍼로 변환:
StatefulEventCollectionJobs.ConvertEventStreamToDynamicBufferJob
을 호출하여 충돌 이벤트를 동적 버퍼로 변환하고,StatefulCollisionEvent
버퍼에 기록합니다.
충돌 위치 쿼리
1. 충돌 이벤트에서 충돌 위치(World Position) 가져오기
public partial struct CollisionPositionLoggerSystem : ISystem { public void OnUpdate(ref SystemState state) { foreach (var (collisionEvents, entity) in SystemAPI.Query<DynamicBuffer<StatefulCollisionEvent>>() .WithEntityAccess()) { foreach (var collisionEvent in collisionEvents) { if (collisionEvent.TryGetDetails(out var details)) { float3 worldPosition = details.AverageContactPointPosition; Debug.Log($"Collision at {worldPosition} between {collisionEvent.EntityA} and {collisionEvent.EntityB}"); } } } } }
Query로 StatefulCollisionEvent
버퍼를 가져옴
SystemAPI.Query<DynamicBuffer<StatefulCollisionEvent>>()
를 사용해 현재 프레임의 충돌 이벤트를 가져옴.
각 충돌 이벤트의 세부 사항 가져오기
collisionEvent.TryGetDetails(out var details)
를 호출하여 충돌 세부 정보를 가져올 수 있는지 확인.details.AverageContactPointPosition
은 충돌 지점들의 평균 좌표로, 월드 좌표계에서의 위치를 제공.
디버그 로그 출력
Debug.Log()
를 이용해 충돌 위치와 관련된 엔티티 정보를 출력.
2. 여러 개의 접촉점(Contact Points) 활용하기
만약 여러 개의 접촉점을 각각 확인하려면, CalculateDetails
를 활성화하고 details.NumberOfContactPoints
값을 사용해야 합니다.
하지만, 현재 StatefulCollisionEvent
는 AverageContactPointPosition
만을 제공하므로, 개별 접촉점의 좌표를 얻으려면 PhysicsWorld
를 직접 사용해야 합니다.
이 경우, collisionEvent.CalculateDetails(ref PhysicsWorld)
를 호출하여 접촉점 데이터를 직접 가져올 수 있습니다.
if (collisionEvent.TryGetDetails(out var details)) { Debug.Log($"Collision has {details.NumberOfContactPoints} contact points."); }