Pawn의 이동, 회전 구현(3)

정혜창·2025년 2월 4일
0

내일배움캠프

목록 보기
27/43
post-thumbnail

이전에 구현했던 Pawn기반 Character 는 bUseControllerRotationYaw = true 로 인해 컨트롤러의 회전에 따라 Pawn의 회전이 동일하게 이루어졌었다. 또한 마우스 이동에 따라 컨트롤러의 Yaw, Pitch 값을 변환시키고 있다. 즉, 플레이어가 바라보는 방향의 yaw, pitch 값을 카메라로 조절이 되고 있었다. 그러다 보니 마우스를 돌릴 때 캐릭터의 이동이 애니메이션을 적용하더라도 매우 부자연스러웠다.

애니메이션이 적용된 영상은 아니지만 마우스를 돌릴 때 마다 캐릭터가 확 돌아가는 것을 볼 수 있다. 그래서 나는 몬스터헌터라는 게임 처럼 마우스의 움직임에 따라 화면의 움직임이 돌아가는 건 같지만 pawn회전은 따로돌아가고, 이동관련 입력값에 따라 pawn또한 해당 방향을 바라보면서 이동하는 움직임을 구현하고 싶었다.

그래서 bUseControllRotationYaw = false로 전환하고 이전에 비행기를 구현했을 때 처럼 마우스의 입력을 통해 캐릭터의 회전값이 변하도록 해주었다.

void APawnByCharacter::Tick(float DeltaTime)
{
	.
	. 
	. 
	.

	// 폰 회전 관련 로직
	if (Controller) 
	{
		FRotator CarmeraRotation = Controller->GetControlRotation();
		if (!MoveInput.IsNearlyZero()) // 이게 없으면 입력을 떼는 순간 캐릭터가 부자연스럽게 정면을 바라봄.
		{
			float radians = FMath::Atan2(-1 * MoveInput.Y, MoveInput.X);
			float angles = -1 * radians * (180 / PI);
			TargetYaw = CarmeraRotation.Yaw + angles;
            TargetYaw = fmod(TargetYaw, 360.0);
		}
	}
	
	FRotator CurrentRotaion = GetActorRotation();
	float DeltaYaw = FMath::FindDeltaAngleDegrees(CurrentRotaion.Yaw, TargetYaw); // 각도 래핑 문제를 해결하기 위한 각도 보정(최단거리) 
	float NewYaw = CurrentRotaion.Yaw + DeltaYaw * FMath::Clamp(DeltaTime * YawInterpSpeed, 0.0f, 1.0f); // 보간
	CurrentRotaion.Yaw = NewYaw;
	SetActorRotation(CurrentRotaion);
}
  • if (Controller)는 방어적 코딩인데 BeginOverlap에 따라 비행기로 Possess가 바뀌면서 Controller의 유무를 체크하는것이 좋기때문에 추가하였다.

  • if (!MoveInput.IsNearlyZero()) 가 없으면 입력을 마친 순간 즉시 캐릭터가 정면을 바라본다. radians 값이 0이 되면서 캐릭터의 회전값이 순간적으로 바로 0이 되어버리기 때문. 따라서 해당 조건문을 통해 입력이 없으면 회전로직이 작동하지 않게 했다. 그렇게 되면 키를 떼는 순간 마지막 키를 누른 방향이 마지막 회전값이기 때문에 해당 방향을 바라보며 회전을 마치게 된다.

  • float radians = FMath::Atan2(-1 * MoveInput.Y, MoveInput.X); 여기서 atan2 는 두개의 인수를 받는 arctan(아크탄젠트)를 말한다. 아크탄젠트는 탄젠트의 역함수로 tanθ = x 를 만족하는 각도 θ를(arctan(x) = θ)반환한다. 쉽게 말해서 벡터(FVector)와 축(Axis)사이의 각도를 구할 때 쓰는 함수이다. 우리는 MoveInput의 X,Y를 입력받고 이는 2차원 평면 XY를 생각하면 된다. X,Y는 -1, 0, 1 값만 가지고 있고 X의 입력을 받았을 때는 θ = 0, π Y의 입력을 받았을 때는 θ = π / 2, - π / 2 을 가져야 한다.
    FMath::Atan2(-1 * MoveInput.Y, MoveInput.X); 를 수학적으로 표현하면 arctan(- y / x) 이다. 계산해보면

    • y = 1, x = 0 일때 : θ = (-1) * π / 2
    • y = -1, x = 0 일때 : θ = π / 2
    • y = 0, x = 1 일때 : θ = - π
    • y = 0, x = -1 일때 : θ = 0
  • 이를 토대로 (180 / PI)를 곱해주어서 우리에게 익숙한 각도로 변환해주고 알맞는 방향을 위해 앞에 -1을 곱해주면

    • y = 1, x = 0 일때 : θ = (-1) * π / 2 = 90⁰
    • y = -1, x = 0 일때 : θ = π / 2 = -90⁰
    • y = 0, x = 1 일때 : θ = - π = 0⁰
    • y = 0, x = -1 일때 : θ = 0 = 180⁰
  • 이를 토대로 현재 카메라가 보는 방향을 기준으로 앞뒤 좌우로 움직여야하므로 목표 Yaw, TargetYaw = CaremraRotation.Yaw + angles 인 것이다. 여기서 CarmeraRotation은 GetControlRotation()

  • 하지만 입력이 생길 때 방향전환에 있어서 예를 들어 350도를 바라보고 있는 상황에서 10도 방향으로 회전을 한다고 가정해보자. 이제 20도만 틀면 원하는 방향으로 갈 수 있는데 10 - 350 = 340으로 계산하여 이 과정에서 부자연스럽게 큰 각도로 회전을 하게 된다. 따라서 최단 경로로 회전각을 보정해주어야 한다. FMath::FindDeltaAngleDegrees가 최단경로로 회전각을 보정해주는 함수이다.

  • 그리고 캐릭터를 움직이지 않고 카메라만 빠르게 회전시키다가 캐릭터를 회전하면 '각도 래핑' 문제 현상이 일어나게 된다. 카메라만 움직이게 되면 내부적으로 TargetYaw값이 카메라의 회전에 따라 크게 바뀌게 된다. 이 각도 값이 0⁰와 360⁰ 경계 근처에서 넘나들게 되면 두 각도 사이의 차이를 계산할 때 단순한 뺄셈으로는 올바른 최단 회전 각도를 얻기 어려워진다. 누적된 TargetYaw값이 제대로 래핑되지 않은 상태라면 이 차이가 아주 커지면서 보간 단계에서 매우 큰 각도 변화가 발생할 수 있다. 이런경우 캐릭터가 제자리에서 무한히 빠르게 회전하는 것 처럼 보이게 된다.
    띠리사 fmod라는 모듈러 연산을 활용하거나 FMath::NormalizeAxis(TargetYaw)를 이용해서 해서 각도를 정규화 해준다.
    (현재 FMath::NormalizeAxis 함수를 찾을 수 없다고 나옴 나만 안보이나?..)

  • 보간 시 보간 계수가 0이면 전혀 회전을 하지 않고, 1이면 한 번의 보간으로 목표 값에 도달하게 된다. 따라서 0, 1사이로 제한함으로써 한 프레임에 지나치게 큰 보간 값이 적용되어 값이 과도하게 변하거나 오버슈팅하는 문제를 방지할 수 있도록 한다.

  • 이렇게 세밀하게 로직을 짜면 아래 움짤처럼 자연스럽게 캐릭터가 이동한다.

profile
Unreal 1기

0개의 댓글

관련 채용 정보