2023.02.18 - [유니티/DOTS] - Unity.Physics에 가속도와 힘 구현해보기 - 1
Unity.Physics에 가속도와 힘 구현해보기 - 1
2023.02.10 - [유니티/DOTS] - Physics 1.0 써보기 주의 : 개인적으로 시행착오 겪으며 구현해 보는 것이니 신뢰 ㄴㄴ , 주절거리는 글임 개요 Unity.Physics에서는 속도에 따른 Transform 계산과 각종 충돌 계산
tsyang.tistory.com
오류 수정
이전 글에서 내가 구현한 가속도로 중력을 만들었을 때 유니티 physics의 중력보다 더 빠르게 움직이는 현상이 있었다. 처음엔 시스템 업데이트 순서가 잘못됐거나 버그로 여겼는데 아니었다.
SystemAPI.Time.fixedDeltaTime이 당~연히
Physics 시스템이 돌아가는 Fixed Step Simulation System Group의 deltaTime이라고 생각했지만 .. 사실 저 값은 프로젝트에서 설정하는 값인 초기값 1/50초로 설정이 되어 있었고...
FixedStepSimulationSystemGroup의 DeltaTime은 위 프로퍼티로 얻어와야 한다 .. 아무튼 저걸 가져다 쓰거나 그냥 1/60초로 업데이트를 해주면 똑같이 동작한다.
힘의 구현
게임에서 쓰일 힘은 크게 세 가지로 정할 수 있다.
- 특정 개체에 직접 적용되는 힘
- 모든 개체에 적용되는 힘
- 일정 영역에 적용되는 힘
1,2는 AI나 별도의 시스템에서 처리해주면 되므로 비교적 간단한다.
문제는 3. 일정 영역에 적용되는 힘인데, 이를 위해서는 일정 영역에 오브젝트가 닿았는지 안 닿았는지를 판단해야 하기 때문이다. 다행히 이런 충돌 문제는 Unity Physics의 Trigger를 이용하면 된다.
3번과 같은 힘의 적용은, 별도의 Force Entity를 생성한 뒤 이것이 다른 Entity와 충돌할 때 발생하는 TriggerEvent를 이용하여 가능하다.
Force Component
힘 컴포넌트의 구현은 간단하다. Vector3값만 가지고 있으면 된다.
public struct ForceComponent : IComponentData
{
public float3 Linear;
}
public class ForceAuthoring : MonoBehaviour
{
public Vector3 Linear;
}
public class ForceBaker : Baker<ForceAuthoring>
{
public override void Bake(ForceAuthoring authoring)
{
AddComponent<ForceComponent>(new ForceComponent
{
Linear = authoring.Linear,
});
}
}
ApplyingForceSystem
힘의 적용은 힘을 부여할 Entity가 Force Component를 가진 Entity와 충돌할 때 발생되는 TriggerEvent를 이용하여 적용한다.
우선 TriggerEvent를 이용하기 위해서는 필요한 컴포넌트들에 대한 참조가 필요하다. 이런 참조를 ComponentLookup이라고 하는데 편의상 이런 ComponentLookup을 한 데 모아준 ComponentDataHandles를 정의해줄 것이다.
struct ComponentDataHandles
{
public ComponentLookup<AccelerationComponent> AccelerationComponentGroup;
public ComponentLookup<ForceComponent> ForceComponentGroup;
public ComponentLookup<PhysicsMass> PhysicsMassComponentGroup;
public ComponentDataHandles(ref SystemState state)
{
AccelerationComponentGroup = state.GetComponentLookup<AccelerationComponent>();
ForceComponentGroup = state.GetComponentLookup<ForceComponent>();
PhysicsMassComponentGroup = state.GetComponentLookup<PhysicsMass>();
}
public void Update(ref SystemState state)
{
AccelerationComponentGroup.Update(ref state);
ForceComponentGroup.Update(ref state);
PhysicsMassComponentGroup.Update(ref state);
}
}
ApplyingForceSystem에서는 Acceleration와 Force 그리고 PhysicsMass (질량과 위치를 얻어오기 위함이다.)를 사용한다.
public void OnUpdate(ref SystemState state)
{
_handles.Update(ref state);
state.Dependency = new ForceComponentTriggerJob
{
AccelerationComponentGroup = _handles.AccelerationComponentGroup,
ForceComponentGroup = _handles.ForceComponentGroup,
PhysicsMassComponentGroup = _handles.PhysicsMassComponentGroup,
}.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);
}
그리고 이렇게 SystemState를 이용하여 컴포넌트들을 업데이트하고 Job을 실행한다. 참고로 TriggerEvent를 병렬적으로 처리하는 건 안되는 것 같다.
ForceTriggerJob
[BurstCompile]
public partial struct ForceComponentTriggerJob : ITriggerEventsJob
{
public ComponentLookup<AccelerationComponent> AccelerationComponentGroup;
[ReadOnly] public ComponentLookup<ForceComponent> ForceComponentGroup;
[ReadOnly] public ComponentLookup<PhysicsMass> PhysicsMassComponentGroup;
public void Execute(TriggerEvent triggerEvent)
{
var entityA = triggerEvent.EntityA;
var entityB = triggerEvent.EntityB;
bool isForceComponentA = ForceComponentGroup.HasComponent(entityA);
bool isForceComponentB = ForceComponentGroup.HasComponent(entityB);
if(!isForceComponentA && !isForceComponentB) return;
bool isAccelComponentA = AccelerationComponentGroup.HasComponent(entityA);
bool isAccelComponentB = AccelerationComponentGroup.HasComponent(entityB);
if (!isAccelComponentA && !isAccelComponentB) return;
bool isMassComponentA = PhysicsMassComponentGroup.HasComponent(entityA);
bool isMassComponentB = PhysicsMassComponentGroup.HasComponent(entityB);
if (!isMassComponentA && !isMassComponentB) return;
if (isForceComponentA && isAccelComponentB && isMassComponentB)
ApplyForce(entityA, entityB);
if (isForceComponentB && isAccelComponentA && isMassComponentA)
ApplyForce(entityB, entityA);
}
private void ApplyForce(Entity applier, Entity receiver)
{
var forceComponent = ForceComponentGroup[applier];
var accelComponent = AccelerationComponentGroup[receiver];
var massComponent = PhysicsMassComponentGroup[receiver];
var accelVector = forceComponent.Linear * massComponent.InverseMass;
accelComponent.Linear += accelVector;
AccelerationComponentGroup[receiver] = accelComponent;
}
}
실행결과
흰 공은 질량을 가볍게하고 검은 공은 무겁게 했음.
인력
인력을 쓰려면 충돌한 두 엔티티의 위치 정보가 필요하다.
따라서 LocalTransform을 쿼리에 추가하고 job을 돌려준다.
인력 컴포넌트는 일반 힘 컴포넌트와 동일하지만 이름만 다르다.
public struct AttractiveForceComponent : IComponentData
{
public float3 Linear;
}
Linear값을 두 엔티티 위치의 방향 벡터에 곱한다. 따라서 Linear값이 (1,1,1)이면 인력이 되고 (-1,-1,-1)이 되면 척력이 된다.
Job역시 동일하지만 힘을 적용하는 부분은 다음과 같이 조금 차이가 있다.
private void ApplyForce(Entity applier, Entity receiver)
{
var forceComponent = AttractiveForce[applier];
var accelComponent = AccelerationComponentGroup[receiver];
var receiverMass = PhysicsMassComponentGroup[receiver];
var applierTransform = LocalTransformGroup[applier];
var receiverTransform = LocalTransformGroup[receiver];
var positionVector = applierTransform.Position - receiverTransform.Position;
if (!math.any(positionVector)) return;
positionVector = math.normalize(positionVector);
var accelVector = forceComponent.Linear * receiverMass.InverseMass * positionVector;
accelComponent.Linear += accelVector;
AccelerationComponentGroup[receiver] = accelComponent;
}
파랑은 척력, 빨강은 인력. 흰색은 가벼운 공, 검은색은 무거운 공이다.
'게임엔진 > DOTS' 카테고리의 다른 글
유니티/DOTS2D Sprite Animation (1) - 핵심 구현 아이디어 (6) | 2023.04.02 |
---|---|
2D Sprite Animation (0) - 레퍼런스 조사 (0) | 2023.03.19 |
ECS - RequireForUpdate / Dependency (0) | 2023.03.05 |
Entities - 컴포넌트 구조 (0) | 2023.02.25 |
Unity.Physics에 가속도와 힘 구현해보기 - 1 (1) | 2023.02.18 |