개요

스파이더맨의 웹 스윙을 물리 기반으로 구현하려고 시도했는데, 알고 보니 비등속 원운동(속력과 방향 모두 변화)이라 복잡했다. 중력도 받아야 하고, 여러 가지 제약이 있는 것으로 보여서 하나씩 풀어나가기로 했다. 따라서 등속 원운동 구현부터 해보았다. 속력 변화 없이 방향만 바꾸는 힘과 초기 속도로만 이루어진다. 이 다음에 비등속 원운동으로 확장해보자.

등속 원운동

등속 원운동은 속력은 일정하지만, 운동 방향은 계속 변하면서 원 궤적을 그리는 운동


등속 원운동은 두 성분으로 나눌 수 있다.

구심력 (궤적의 유지) + 접선 방향 속도(움직임의 시작)



구심력을 구하는 방법

구심력을 구하는 방법은 다음과 같다.

$F = ma$ 에 따라 질량과 가속도가 필요하다.

이때 가속도 공식에 따라 $a = \frac{Δ v}{Δt}$ 이다. (나중 속도/처음 속도)

(등속 원운동에서 구심 가속도의 크기($a_c$)는 일정하지만, 방향은 계속 중심을 향해 변함)

(방향 변화만을 담당함. 계속 원 중심으로 끌어당길 때 어떤 방향으로 끌어당길지 결정)


이제 속도를 구해보자.

각속도와 접선 속도가 필요하다.

각속도를 이용해 접선 속도를 편리하게 구할 수 있다.


각속도 : 단위 시간 동안 회전하는 각도 [rad/s]

$$ w = \frac{Δ \theta}{t} $$

속도(접선 속도) : 물체가 단위 시간 동안 이동한 거리(변위) [m/s]

$$ v = \frac{Δ \vec s}{t} $$

라디안에서 호의 길이는 $l = r *\theta$ 으로 표현할 수 있다.

다시 보면 $l$ 은 시작점에서 끝점까지 이동한 변위라고 볼 수 있기 때문에

$Δs = r *Δ\theta$ 라고 할 수 있다.

그리고 이 식은 각속도의 공식과 합쳐서 최종적으로 $v= r *w$ 가 된다.


가속도 (구심 가속도)

이제 속도를 구했으니, 가속도를 구하면 된다.

근데 $a=Δv/Δt$ (나중속도/지금속도) 근데 게임에서는 나중 속도를 예측하기 힘들다

그럼 어떻게 구할 수 있을까?


그래서 이 공식으로 가속도를 구할 수 있다.

속도의 크기가 일정하기 때문에 이 공식을 사용할 수 있다고 한다.

구체적인 유도식은 복잡하기 때문에 생략했다…

$$ a = \frac{v^2}{r} $$

구심력

물체가 원형 궤도를 따라 움직일 때,

그 물체를 원의 중심으로 잡아당겨 운동 방향을 지속적으로 바꾸게 하는 힘이다.

속도는 바꾸지 않고, 방향만을 바꾸게 하는 힘.

밑의 코드는 중력은 제외하고, 구심력만을 적용해 원의 중심으로 잡아당기는 코드이다.

void ACentripetalTestActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    UPrimitiveComponent* PrimitiveComp = Cast<UPrimitiveComponent>(GetRootComponent());
    if (!PrimitiveComp || !PrimitiveComp->IsSimulatingPhysics())
    {
       UE_LOG(LogBS, Warning, TEXT("CentripetalTestActor is not simulating physics."));
       return;
    }
    
    // 등속 원운동
    // 속력 일정, 운동 방향이 바뀜

    float Mass = 80.0f; // 캐릭터 질량 (m)
    Radius; // 원의 반지름 (r)
    
    //* 구심력 구하기 *//
    // 각속도 (단위 시간 동안 회전하는 각도 rad/s) w = theta / t
    AngularSpeed = 1.0f; // 초당 1 radian 회전

    // 속도 (단위 시간 동안 변화한 변위 m/s) v = r * w
    float Speed = Radius* AngularSpeed;

    // 구심 가속도 a = v^2 / r
    float CentripetalAccelerationMagnitude = (Speed * Speed) / Radius; // a = v^2 / r

    // 구심력 = m * a = m * 구심 가속도
    float CentripetalForceMagnitude = Mass * CentripetalAccelerationMagnitude; // F = m * a
    
    // 최종 힘 : 구심력 * 방향
    FVector DirectionUnitVector = (AttachPoint - GetActorTransform().GetLocation()).GetSafeNormal();
    FVector Force = DirectionUnitVector * CentripetalForceMagnitude;

    PrimitiveComp->AddForce(Force);
}


구심력 + 초기 접선 속도

구심력만으로는 원운동이 만들어지지 않는다.

(구심력은 이미 원운동하고 있는 물체를 중심으로 당기는 힘이기 때문에)

기존 구심력에 초기 접선 속도(그네를 타기위해 발을 굴러 얻는 첫 속도)가 필요하다.

초기 접선 속도는 궤적을 따라 앞으로 나아가게 만드는 힘이 된다!


정리하면

1. 직선으로 날아가려는 성질을 갖게 하고

2. 구심력이 원의 중심 쪽으로 당겨서 원 운동을 만들게 함.

void ACentripetalTestActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    UPrimitiveComponent* PrimitiveComp = Cast<UPrimitiveComponent>(GetRootComponent());
    if (!PrimitiveComp || !PrimitiveComp->IsSimulatingPhysics())
    {
       UE_LOG(LogBS, Warning, TEXT("CentripetalTestActor is not simulating physics."));
       return;
    }
    
float Mass = 80.0f; // 캐릭터 질량 (m)
    Radius; // 반지름 (r)

    // 현재 위치에서 중심점까지의 벡터
    FVector ToCenter = AttachPoint - GetActorLocation();
    FVector DirectionToCenter = ToCenter.GetSafeNormal();

    if (!bInitialVelocitySet)
    {
//* 1. 초기 접선 속도 구하기*//
       // 접선 방향 = 중심 방향과 수직인 방향 (외적으로 계산)
       FVector TangentDirection = FVector::CrossProduct(DirectionToCenter, FVector::UpVector).GetSafeNormal();

       // 각속도 (단위 시간 동안 회전하는 각도 rad/s) w
       AngularSpeed = 3.0f; // 초당 3 radian 회전
        
       // 속도 (단위 시간 동안 변화한 변위 m/s) v = r * w
       float InitialSpeed = Radius * AngularSpeed;

       // 초기 속도 적용 (접선 방향으로 날아가게)
       FVector InitialVelocity = TangentDirection * InitialSpeed;
       PrimitiveComp->SetPhysicsLinearVelocity(InitialVelocity);
        
       bInitialVelocitySet = true;
       UE_LOG(LogTemp, Log, TEXT("Initial tangent velocity set: %s"), *InitialVelocity.ToString());
    }

//* 2. 구심력 구하기 *//
    // 현재 속도 v
    FVector CurrentVelocity = PrimitiveComp->GetPhysicsLinearVelocity();
    float CurrentSpeed = CurrentVelocity.Size();
    
    // 구심 가속도 a = v^2 / r
    float CentripetalAccelerationMagnitude = (CurrentSpeed * CurrentSpeed) / Radius;

    // 구심력 = m * a = m * 구심 가속도
    float CentripetalForceMagnitude = Mass * CentripetalAccelerationMagnitude; // F = m * a
    
    // 최종 힘 : 구심력 * 방향
    FVector DirectionUnitVector = (AttachPoint - GetActorTransform().GetLocation()).GetSafeNormal();
    FVector Force = DirectionUnitVector * CentripetalForceMagnitude;

    PrimitiveComp->AddForce(Force);
}