게임엔진/DOTS

Unity.Physics에 가속도와 힘 구현해보기 - 1

tsyang 2023. 2. 18. 21:11

2023.02.10 - [유니티/DOTS] - Physics 1.0 써보기

 

주의 : 개인적으로 시행착오 겪으며 구현해 보는 것이니 신뢰 ㄴㄴ , 주절거리는 글임

 

 

개요


Unity.Physics에서는 속도에 따른 Transform 계산과 각종 충돌 계산등을 해준다. 여기에 가속도나 힘의 개념은 없기 때문에 내가 구현해줘야 한다.

 

 

 

 

가속도


컴포넌트 

가속도는 무슨 일을 하는가? 오직 물체의 속도만을 변경시키는 데 사용된다.

 

그런데 여기서 시간의 개념을 고려해야 한다. 

 

엔티티의 가속도 지속시간은 어떻게 관리해줄 것인가?

 

  1. 엔티티 마다 (가속도 벡터, 지속시간) 형태의 리스트를 들고 있고, 이를 이용해 최종 가속도를 계산 후 1프레임 업데이트. 지속시간은 1프레임만큼 차감. 

  2.  엔티티는 가신이 받는 가속도의 지속시간을 모른다. 1프레임동안 적용될 가속도 값만 저장한다. 

 

이거 좀 고민했는데 이렇게 써보고 나니 딱 봐도 2번이다. 애초에 1번은 데이터 지향성이 떨어진다. 그리고 엔티티가 가속도의 지속시간을 아는 게 아니라 가속도를 주는 매개체들이 지속시간을 알고 있는 게 더 OOP스럽다.

 

그럼 가속도 컴포넌트는 간단하다. 

/// <summary>
/// 1프레임 동안의 가속도 값을 가진다.
/// </summary>
public struct AccelerationComponent : IComponentData
{
    public float3 Linear;
}

 

 


 

시스템

이제 가속도를 객체에 적용하는 시스템을 구현해보자.

 

가속도는 단순히 엔티티의 속도만 업데이트 해주면 된다.  그런데 주의할 점은 Unity.Physics의 Update이전에 이 작업이 수행되어야 한다는 것이다.

 

이런 건 그룹이라는 개념으로 처리할 수 있다. 

 

기본 시스템 그룹은 위와 같이 있는데, 기본 상태에서는 저렇게 Transform System Group다음에 Acceleration System이 위치한다. 

 

Acceleration System은 FixedStepSimulationSystemGroup 안에 있는 BeforePhysicsSystemGroup에 넣어줄 것이다.

[UpdateInGroup(typeof(BeforePhysicsSystemGroup))]
public partial struct AccelerationSystem : ISystem

이렇게 Attribute를 달아주면 된다.

 

이러면 이렇게 시스템이 해당 그룹안에 들어가게 된다.

 

 

그다음은 가속도 Job을 만들어보자

[BurstCompile]
public partial struct ApplyingAccelerationJob : IJobEntity
{
    public float DeltaTime;
    public void Execute(RefRW<AccelerationComponent> accel, RefRW<PhysicsVelocity> velocity)
    {
        accel.ValueRW.Linear = new float3(0, -9.81f, 0);    //임시로 중력을 구현
        velocity.ValueRW.Linear += accel.ValueRW.Linear * DeltaTime;
        accel.ValueRW.Linear = float3.zero; //적용 되었으니 값을 초기화시켜준다.
    }
}

Query는 가속도 컴포넌트와 PhysicsVelocity를 가진 엔티티를 불러온다.

 

가속도의 값에는 임시로 중력의 값을 넣어준다. 원래라면 저 부분이 빠지고 다른 곳에서 가속도 값을 넣어줄 것이다.

 

[BurstCompile]
[UpdateInGroup(typeof(BeforePhysicsSystemGroup))]
public partial struct AccelerationSystem : ISystem
{
    public void OnCreate(ref SystemState state)
    {
    }

    public void OnDestroy(ref SystemState state)
    {
    }

    public void OnUpdate(ref SystemState state)
    {
        new ApplyingAccelerationJob(){DeltaTime = SystemAPI.Time.fixedDeltaTime}.ScheduleParallel();
    }
}

그 다음에 위와 같이 가속도 적용 시스템을 만든다.

 

 

그 다음에 Scene에 공을 두개 만들어서 하나는 Acceleration 컴포넌트를 붙여준 뒤 gravity factor를 0으로 조절해서 유니티 물리시스템 자체의 중력을 무시해 줄 것이고 나머지 하나는 유니티 자체의 중력을 쓰도록 해서 비교를 해보자

 

 

 

 

 

과연 똑같이 동작할까?

 

응 아님 ㅎ

 

파란색이 가속도 컴포넌트에 중력 값을 넣어준 것이고, 빨간색은 유니티 자체 중력. 

 

사실 둘 다 자연스러워 보이는데 왠지 찝찝하다.

 

근데 이거 내 잘못 아닐 수도 있는 게 ...

더보기

Gravity Factor를 0으로 한 상태에서 

 

유니티 ECS 시스템의 몇몇 세팅값들을 변경해줄 수 있는 Singleton인 PyhsicsStep 이란걸 추가해준 뒤, 중력 값을 0으로 해두면...

 

 

어쩌피 Gravity Factor가 0이고 중력도 0이므로 로직에 아~무런 영향이 없어야 할 것 같지만 ..

 

 

영향이 있음.. 공이 튈때마다 점점 위로 올라감.

 

그런데 웃긴 건 Physics Step을 추가 한 다음 

 

기존의 중력 값을 사용하면 원래랑 똑같이 돌아간다 .. 

이건 진짜 버그같은데... 

 

아무튼 중력을 -8.2정도로 설정하면 유니티 자체 중력과 거의 비슷하게 동작한다. 

 

그래서 그냥 진행하는 걸로 결정~☆

 

 


 


컴포넌트

 

사실 힘은 질량과 가속도가 있으면 계산해낼 수 있는 거라 따로 컴포넌트가 필요 없다.

 

여기서 말하는 힘은, 다른 애들한테 힘을 주는 매개체를 말한다. (중력, 폭발 등)

 

또 힘은 여러 종류가 있을 수 있는데,

 

1. 대상의 질량과 상관 없이 가속도를 부여하는 경우(ex. 중력)와 대상의 질량에 따라 다른 가속도를 부여하는 경우. (ex. 밀치는 힘)

2. 방향이 고정된 경우(ex.중력)와 방향이 대상의 위치에 따라 다른 경우 (ex. 폭발)

3. 힘의 세기가 고정된 경우와 힘의 세기가 거리에 따라 다른 경우.. (미구현 예정)

 

그러면 데이터를 아래와 같이 추가해줄 수 있다.

 

1. 힘의 방향 (normalized 되어 있어야 함. 0인 경우 방향을 직접계산하도록)

2. 힘의 세기

 

구현하면..

 

public struct ForceComponent : IComponentData
{
    /// <summary>
    /// Normalized Vector3
    /// </summary>
    public float3 Direction;
    public float Intensity;
}
public class ForceAuthoring : MonoBehaviour
{
    public Vector3 Direction;
    public float Intensity;
}

public class ForceBaker : Baker<ForceAuthoring>
{
    public override void Bake(ForceAuthoring authoring)
    {
        AddComponent<ForceComponent>(new ForceComponent
        {
            Direction = authoring.Direction.sqrMagnitude > 0f ? authoring.Direction.normalized : float3.zero,
            Intensity = authoring.Intensity,
        });
    }
}

 

그래서 Main Idea는 오브젝트에 ForceComponent가 붙여줄수 있고 (ex. 맵에 붙어있는 점프패드) 아니면 직접 ForceComponent가 있는 Entity를 생성해주는 것임 (시간이 지나면 삭제되는 LifeTime Component와 함께) (ex. 폭발)

 

 


 

시스템

 

시스템의 경우 좀 까다로울 수 있음.

 

우선 Acceleration이 있는 애들 리스트를 쭉 받아온 다음, 충돌 했는지 안 했는지를 비교해와야함..

 

이때 충돌의 경우 OnCollsion으로 호출해올 수 있다. 중력과 같은 경우는 별도의 시스템으로 빼는 것이 좋아보인다.

 

어.. 근데 여기서부터 어렵다. PhysicsWorld니 CollisionQuery니 MotionVelocity니 이런 개념들이 등장한다. 

 

좀 더 공부해보고 구현해야 할듯. 

 

(이어서 계속)

 

 

 

 

 

 

 

 

 

 

 

'게임엔진 > DOTS' 카테고리의 다른 글

ECS - RequireForUpdate / Dependency  (0) 2023.03.05
Entities - 컴포넌트 구조  (0) 2023.02.25
Physics 1.0 써보기  (1) 2023.02.10
DOTS 1.0 - 2 (생성, MonoBehavour 연계)  (0) 2023.02.04
DOTS 1.0 - 기본 (Component,System,Aspect,Job)  (0) 2023.01.29