본문 바로가기

bUseControllerRotation과 bUseControllerDesiredRotation의 차이

@iamrain2025. 11. 26. 19:22

1. 개요

언리얼 엔진에서 캐릭터의 회전을 제어할 때 APawnbUseControllerRotation 계열 옵션과 UCharacterMovementComponentbUseControllerDesiredRotation 옵션은 매우 혼동하기 쉽다. 두 옵션 모두 컨트롤러(AController)의 회전 값을 액터(Actor)의 회전에 반영한다는 공통점이 있지만, 동작 방식과 주체, 그리고 결과에서 명확한 차이를 보인다.


2. APawn::bUseControllerRotation (Pitch, Yaw, Roll)

이 속성들은 APawn 클래스에 직접 정의되어 있으며, bUseControllerRotationPitch, bUseControllerRotationYaw, bUseControllerRotationRoll 세 가지 bool 플래그로 구성된다. 이 플래그가 true로 설정되면, 폰의 회전은 매 틱(Tick)마다 컨트롤러의 회전(ControlRotation)으로 즉시, 강제로 설정된다.

2.1. 내부 구조 및 구현 방식 (엔진 코드 레벨)

bUseControllerRotation의 핵심 로직은 APawn::FaceRotation 함수 내에 있다.

    1. APawn::Tick(): 매 프레임 폰의 Tick 함수가 호출될 때, 특정 조건 하에서 FaceRotation() 함수를 호출한다.
    2. APawn::FaceRotation(FRotator NewControlRotation, float DeltaTime): 이 함수는 컨트롤러의 새로운 회전 값을 인자로 받는다.
    3. 플래그 확인: 함수 내부에서는 bUseControllerRotationPitch, bUseControllerRotationYaw, bUseControllerRotationRoll 플래그를 각각 확인한다.
    4. 
      // Engine/Source/Runtime/Engine/Private/Pawn.cpp
      void APawn::FaceRotation(FRotator NewControlRotation, float DeltaTime)
      {
          // 현재 액터의 회전 값을 가져옴
          FRotator CurrentRotation = GetActorRotation();
      
          // bUseControllerRotationYaw 플래그가 true이면, 컨트롤러의 Yaw 값을 액터의 Yaw 값으로 설정
          if (bUseControllerRotationYaw)
          {
              CurrentRotation.Yaw = NewControlRotation.Yaw;
          }
          // Pitch와 Roll도 동일한 방식으로 처리
          if (bUseControllerRotationPitch)
          {
              CurrentRotation.Pitch = NewControlRotation.Pitch;
          }
          if (bUseControllerRotationRoll)
          {
              CurrentRotation.Roll = NewControlRotation.Roll;
          }
      
          // 최종적으로 계산된 회전 값을 액터에 적용
          if (CurrentRotation != GetActorRotation())
          {
              SetActorRotation(CurrentRotation);
          }
      }
      
    5. `SetActorRotation()`: 변경된 회전 값(CurrentRotation)이 AActor::SetActorRotation() 함수를 통해 액터에 직접 적용된다. 이 과정에는 어떠한 보간(Interpolation)도 포함되지 않으므로, 회전은 즉각적으로 반영된다

2.2. 동작 계층 구조

bUseControllerRotation의 동작은 다음과 같은 계층을 통해 이루어진다.

  1. 사용자 입력 계층 (Hardware/OS): 플레이어가 마우스, 키보드, 게임패드 등으로 입력을 생성한다.
  2. 입력 처리 계층 (PlayerController): APlayerController가 하드웨어 입력을 받아 AddControllerYawInput, AddControllerPitchInput 등의 함수를 통해 자신의 ControlRotation 멤버 변수를 업데이트한다.
  3. 폰 틱 계층 (Pawn): APawn::Tick() 함수가 실행되면서 Controller->GetControlRotation()으로 업데이트된 ControlRotation을 가져온다.
  4. 회전 적용 계층 (Pawn): APawn::FaceRotation() 함수가 bUseControllerRotation 플래그를 확인하고, true일 경우 SetActorRotation()을 호출하여 폰의 트랜스폼을 직접 수정한다.

이 방식은 컨트롤러의 회전이 폰의 회전을 1:1로, 지연 없이 '지배'하는 구조다.


3. UCharacterMovementComponent::bUseControllerDesiredRotation

이 속성은 ACharacter가 기본으로 사용하는 UCharacterMovementComponent에 정의된 bool 플래그이다. 이 플래그가 true가 되면, 캐릭터는 컨트롤러의 ControlRotation'목표(Desired) 회전'으로 설정하고, 현재 회전 값에서 목표 회전 값까지 부드럽게 보간하며 회전한다.

3.1. 내부 구조 및 구현 방식 (엔진 코드 레벨)

bUseControllerDesiredRotation의 로직은 UCharacterMovementComponent의 틱 함수, 특히 PhysicsRotation에서 처리된다.

    1. UCharacterMovementComponent::TickComponent(): 컴포넌트의 틱 함수는 캐릭터의 이동 및 회전을 포함한 물리 계산을 총괄한다.
    2. ControlledCharacterMove(): 틱 함수 내에서 호출되며, 여기서 회전 방향이 결정된다. bUseControllerDesiredRotationtrue이면, 컨트롤러의 회전(ControlRotation)이 목표 회전으로 설정된다.
    3. 
      // Engine/Source/Runtime/Engine/Private/CharacterMovementComponent.cpp
      void UCharacterMovementComponent::ControlledCharacterMove(const FVector& InputVector, float DeltaSeconds)
      {
          // ... 다른 이동 로직 ...
      
          // bUseControllerDesiredRotation이 true이고 캐릭터가 움직일 때
          if (bUseControllerDesiredRotation && !InputVector.IsNearlyZero())
          {
              // 컨트롤러의 회전을 목표 회전으로 설정
              DesiredRotation = CharacterOwner->GetControlRotation();
          }
          // ...
      }
      
    4. UCharacterMovementComponent::PhysicsRotation(float DeltaTime): 실제 회전 보간이 일어나는 곳이다.
    5. 
      // Engine/Source/Runtime/Engine/Private/CharacterMovementComponent.cpp
      void UCharacterMovementComponent::PhysicsRotation(float DeltaTime)
      {
          // ...
          // 목표 회전(DesiredRotation)이 현재 회전과 다른 경우에만 실행
          if (!CurrentRotation.Equals(DesiredRotation, 0.01f))
          {
              // 이번 프레임에 회전할 수 있는 최대 각도
              const float TurnSpeed = GetMaxTurnSpeed(); // RotationRate.Yaw 값 기반
      
              // RInterpTo는 현재 값에서 목표 값까지 일정 속도로 보간하는 함수
              FRotator NewRotation = FMath::RInterpTo(CurrentRotation, DesiredRotation, DeltaTime, TurnSpeed);
      
              // MoveUpdatedComponent를 통해 부드럽게 회전 적용
              MoveUpdatedComponent(FVector::ZeroVector, NewRotation, true);
          }
      }
      
    6. RotationRate: UCharacterMovementComponentFRotator RotationRate 멤버 변수는 각 축(주로 Yaw)의 최대 회전 속도를 (degrees/sec) 단위로 정의한다. PhysicsRotation은 이 값을 사용하여 FMath::RInterpTo 함수로 점진적인 회전을 구현한다.

3.2. 동작 계층 구조

bUseControllerDesiredRotation의 동작 계층은 다음과 같다.

  1. 사용자 입력 계층 (Hardware/OS): 동일.
  2. 입력 처리 계층 (PlayerController): 동일. ControlRotation 업데이트.
  3. 컴포넌트 틱 계층 (CharacterMovementComponent): UCharacterMovementComponent::TickComponent()가 실행된다.
  4. 목표 설정 계층 (CharacterMovementComponent): bUseControllerDesiredRotation 플래그를 확인하고, ControlRotation을 '목표 회전'으로 내부 변수에 저장한다.
  5. 회전 보간 계층 (CharacterMovementComponent): PhysicsRotation() 함수가 현재 회전과 목표 회전, 그리고 RotationRate를 사용하여 이번 프레임에 적용할 회전 값을 '계산'한다.
  6. 이동 및 적용 계층 (CharacterMovementComponent): MoveUpdatedComponent() 함수가 계산된 회전 값을 캐릭터에 적용하여 부드러운 회전을 만들어낸다.

이 방식은 CharacterMovementComponent가 중간에서 '중재자' 역할을 하여, 컨트롤러의 회전 목표를 점진적으로 따라가는 구조다.


4. 핵심 차이점 비교 및 사용 사례

구분 bUseControllerRotation bUseControllerDesiredRotation
적용 주체 APawn UCharacterMovementComponent
동작 방식 직접적, 강제적 (Direct, Forced) 간접적, 보간 (Indirect, Interpolated)
결과 즉각적이고 기계적인 회전 부드럽고 자연스러운 회전
관련 속성 없음 RotationRate (회전 속도)
주요 제어 축 Pitch, Yaw, Roll 모두 가능 주로 Yaw (캐릭터의 좌우 회전)

4.1. 장단점 및 사용 사례

bUseControllerRotation

  • 장점:
    • 최고의 반응성: 입력과 동시에 회전이 완료되므로 지연이 전혀 없다.
    • 간단한 설정: 플래그 하나만 켜면 즉시 동작한다.
  • 단점:
    • 부자연스러움: 회전 보간이 없어 움직임이 딱딱하고 기계적으로 보인다.
    • 애니메이션과 부조화: 부드러운 회전 애니메이션과 함께 사용하기 어렵다.
  • 주요 사용 사례:
    • 1인칭 슈팅(FPS) 게임: 플레이어의 시점(카메라)과 캐릭터의 상체가 컨트롤러 방향과 항상 일치해야 할 때. 보통 bUseControllerRotationYawfalse로 두고 캐릭터의 회전은 MovementComponent에 맡기고, bUseControllerRotationPitchtrue로 하여 카메라의 상하 회전을 구현한다.
    • 비행 시뮬레이션: 조종사의 시점이 기체의 회전과 직접 연결될 때.
    • 관찰 카메라(Spectator Pawn): 맵을 자유롭게 둘러보는 카메라처럼 즉각적인 제어가 필요할 때.

bUseControllerDesiredRotation

  • 장점:
    • 자연스러운 움직임: RotationRate에 따라 캐릭터가 현실적으로 몸을 돌리는 듯한 느낌을 준다.
    • 애니메이션 친화적: 회전 속도에 맞춰 발을 움직이는 등 자연스러운 회전 애니메이션을 블렌딩하기 용이하다.
  • 단점:
    • 느린 반응성: RotationRate 값에 따라 반응이 한 박자 느리게 느껴질 수 있다.
    • 추가 설정 필요: 원하는 느낌을 얻기 위해 RotationRate 값을 세심하게 조정해야 한다.
  • 주요 사용 사례:
    • 3인칭 슈팅(TPS) / 액션 게임: 카메라 방향에 따라 캐릭터가 자연스럽게 몸을 돌려야 할 때. 예를 들어, 오른쪽으로 카메라를 돌리면 캐릭터가 그 방향으로 스르륵 몸을 트는 연출.
    • AI 캐릭터: AI가 목표물을 향해 몸을 돌릴 때 자연스러운 움직임을 부여하고 싶을 때.
    • 탑다운 뷰 게임: 마우스 커서나 조이스틱 방향으로 캐릭터가 부드럽게 회전해야 할 때.

4.2. 두 옵션의 조합

두 옵션은 종종 함께 사용된다. 가장 대표적인 예는 3인칭 게임 캐릭터이다.

  • bUseControllerRotationYaw = false
  • GetCharacterMovement()->bOrientRotationToMovement = true 또는 GetCharacterMovement()->bUseControllerDesiredRotation = true
  • APawn::bUseControllerRotationPitch = true (카메라 암(Camera Boom)에 적용)

위와 같이 설정하면,

  1. 캐릭터의 좌우 회전(Yaw)은 컨트롤러 방향을 부드럽게 따라가거나(bUseControllerDesiredRotation), 혹은 캐릭터가 이동하는 방향을 바라보게(bOrientRotationToMovement) 된다.
  2. 카메라의 상하 회전(Pitch)은 컨트롤러의 움직임에 즉각적으로 반응하여, 플레이어가 위아래를 조준하는 대로 카메라 각도가 바로 변경된다.

5. 요약

두 옵션의 가장 큰 차이는 "회전을 즉시 적용하느냐, 아니면 목표로 삼고 부드럽게 따라가게 하느냐" 입니다.

bUseControllerRotation은 폰의 회전을 컨트롤러의 회전 값에 매 프레임 강제로 '고정'시키는 옵션입니다. 그래서 1인칭 게임처럼 카메라와 몸이 즉각적으로 함께 돌아야 할 때처럼 아주 빠른 반응이 필요할 때 사용합니다.

반면에 bUseControllerDesiredRotation은 캐릭터 무브먼트 컴포넌트가 컨트롤러의 회전을 '목표 지점'으로 삼고, 우리가 설정한 회전 속도(RotationRate)에 맞춰 캐릭터를 부드럽게 그쪽으로 회전시키는 방식입니다. 3인칭 게임에서 플레이어가 카메라를 돌렸을 때 캐릭터가 현실적으로 몸을 스르륵 트는 모습을 생각하시면 쉽습니다.

따라서 즉각적이고 기계적인 반응이 필요하면 bUseControllerRotation을, 자연스럽고 사실적인 회전 연출이 필요하면 bUseControllerDesiredRotation을 사용한다고 정리할 수 있습니다.

iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차