개요

웹스윙을 구현하기 위해 처음에는 원운동 이론을 기반 으로 접근했다. 등속 원운동부터 시작해서 중력의 영향을 받아 속력이 변화하는 비등속 원운동으로 발전시킬 계획이 었는데… 문제가 있었다.


게임은 시뮬레이션이 아니기 때문에, 정해진 원운동 궤적으로만 움직이는 경우에 플레이어의 입력 처리, 외부 힘, 지형과의 충돌처리 적인 부분에서 예외 처리하는 것이 한계가 있었고 동적인 움직임을 구현하는 게 힘들었다.


따라서, 다른 접근 방법을 찾아보던 중 줄의 장력을 이용한 방식으로 전환했다.



장력 방식

장력 방식은 줄이 캐릭터를 부착점 쪽으로 당기는 힘을 매 프레임 계산한다.

이는 원운동의 구심력과 유사하지만, 고정된 궤적이 아닌 현재 상태(속력, 방향) 에 반응하도록 되어 있어서 캐릭터가 어떤 Velocity 를 가지던 상관없게 되었다.


구현 방식은 간단하다.

캐릭터가 줄과 반대방향으로 갈수록 더 세게 줄 방향으로 캐릭터를 당긴다.

FVector UAT_WebSwing::CalculateStringTension(float InVelocityClampMin, float InVelocityClampMax) const
{
    auto MovementComp = Cast<ACharacter>(GetAvatarActor())->GetMovementComponent();
    FVector CurrentVelocity = MovementComp->Velocity.GetClampedToSize(InVelocityClampMin, InVelocityClampMax);
    FVector ToCharacterDirection = GetAvatarActor()->GetActorLocation() - TargetAttachPoint;

    float Dot = FVector::DotProduct(CurrentVelocity, ToCharacterDirection);
    // 현재 줄과 캐릭터의 Velocity 방향이 비슷할 수록 장력이 더 세진다.

		float TensionPower = -1.f;
    return ToCharacterDirection.GetSafeNormal() * Dot * TensionPower; 
    //반대 방향으로 힘 주기
}

void UAT_WebSwing::TickTask(float DeltaTime)
{
		...
		FVector StringTension = CalculateStringTension(...)
		ResultForce = StringTension + ForwardPushingForce + IncreaseSpeedAtBottomOfArc
		MovementComp->AddForce(ResultForce);
		
		...
}

캐릭터에 가하는 여러 힘

void UAT_WebSwing::TickTask(float DeltaTime)
{
    Super::TickTask(DeltaTime);

    auto AvatarActor = GetGameplayTaskAvatar(this);
    auto MovementComp = Cast<ACharacter>(AvatarActor)->GetCharacterMovement(); 
    auto CameraBoom = Cast<ABSCharacter>(AvatarActor)->CameraBoom;
    auto CameraDirection = CameraBoom->GetForwardVector();
    CameraDirection.Z = 0.0f;

    // 1. Forward Pushing Force
    FVector ForwardPushingForce = CameraDirection * 130000.f;

    // 2. Tension Force
    FVector TensionForce = CalculateStringTension(100,3000);

    // 3. Increase Swing Speed it reached at the Bottom of the Arc
    FVector IncreasedSpeed = IncreaseSpeedAtBottomOfArc();
    
    // Result Force
    FVector ResultForce = ForwardPushingForce + TensionForce + IncreasedSpeed;
    MovementComp->AddForce(ResultForce);
    
    UpdateCharacterRotation(DeltaTime);
}

여기서 ResultForce에 캐릭터가 계속 앞으로 나아가기 위한 ForwardPushingForce 와 캐릭터가 궤적을 그릴때 Velocity 의 방향이 중력의 방향과 비슷할 때(내적) 궤적(호)의 가장 아래에 있어서 그때 속도를 올려주는 힘을 더해서 최종 힘을 더한다.


void UAT_WebSwing::UpdateKeyboardInput(const FVector2D& MovementValue)
{
    auto AvatarActor = GetGameplayTaskAvatar(this);
    auto Character = Cast<ACharacter>(AvatarActor);
    if (!Character) return;

    if (MovementValue.X != 0.0f)
    {
       auto RightDir = Character->GetActorRightVector().GetSafeNormal();
    
       float Power = 110000.f;
       FVector Force = RightDir * MovementValue.X * Power;
    
       Character->GetCharacterMovement()->AddForce(Force);
    }
}

그리고 캐릭터가 원하는 방향으로 갈 수 있도록 키보드 Input 을 받아서 그 쪽 방향으로 힘을 준다.


캐릭터의 회전

캐릭터는 단순히 이동 방향을 바라보는 것이 아니라, 줄에 매달려 기울어진 자세를 취한다.

현재 스윙 중에 줄에 매달린 각도를 계산해서 캐릭터를 회전 시킨다.

void UAT_WebSwing::UpdateCharacterRotation(float DeltaTime)
{
    auto AvatarActor = GetGameplayTaskAvatar(this);
    auto Character = Cast<ACharacter>(AvatarActor);
    if (!Character) return;

    auto MovementComp = Character->GetCharacterMovement();
    FVector CurrentVelocity = MovementComp->Velocity;

    FRotator SwingAngle = CalculateSwingSideAngle();
    FRotator SwingRotation = FRotator(SwingAngle.Pitch,CurrentVelocity.Rotation().Yaw, SwingAngle.Roll);

    FRotator ResultRotation = FMath::RInterpTo(Character->GetActorRotation(), SwingRotation, DeltaTime, 20.f);
    Character->SetActorRotation(ResultRotation);
}

줄에 매달린 각도를 구하는 방법은 다음과 같다.

FRotator UAT_WebSwing::CalculateSwingSideAngle()
{
    auto AvatarActor = GetGameplayTaskAvatar(this);
    auto MovementComp = Cast<ACharacter>(AvatarActor)->GetCharacterMovement();

    FVector ToAttachPointDir = (TargetAttachPoint - AvatarActor->GetActorLocation()).GetSafeNormal();
    FVector StandardAxis = FVector::CrossProduct(ToAttachPointDir, MovementComp->Velocity.GetSafeNormal());

    FRotationMatrix Result = FRotationMatrix::MakeFromZY(ToAttachPointDir,StandardAxis);

    return FRotator(Result.Rotator());
}

현재 캐릭터에서 줄까지의 방향(검은색) 과 현재 캐릭터가 진행중인 방향(빨강) 을 외적해서 기준 축을 만들고, Z 축: 줄까지의 방향으로, Y축: 외적 결과 로 새로운 회전 평면을 구한다.

약간 옆으로 기울어진 상태의 회전 평면이 만들어진다.


이 기울어진 회전 평면을 Rotator 로 변환해서 Yaw,Pitch,Roll 을 사용할 수 있게 한다.


이 Rotator 의 Roll 값으로 캐릭터가 기울게 된다.