언리얼엔진5 공부

[TIL] UE5 C++: 캐릭터 강제 이동 및 회전 시스템 구현 (애니메이션 동기화 및 물리적 궤도 이탈 해결)

Client Side 2026. 2. 4. 13:33

📌 개요

게임 내 연출이나 특정 기믹을 위해 플레이어의 조작권을 회수하고 캐릭터를 특정 지점으로 이동시킨 뒤, 특정 타겟(예: 보물상자)을 바라보게 하는 시스템을 구축했다. 구현 과정에서 발생한 애니메이션 미재생, 물리적 궤도 이탈, 회전축 간섭 문제를 해결한 과정을 상세히 기록한다.


🚀 1. 1차 시도: 단순 좌표 이동과 "얼어붙은 캐릭터"

[구현 방법]

  • FMath::VInterpConstantTo를 이용해 매 프레임 캐릭터의 좌표(SetActorLocation)를 강제 갱신.
  • SetIgnoreMoveInput(true)를 호출하여 플레이어의 WASD 입력을 차단.

[직면한 문제: 애니메이션 미재생]

  • 현상: 캐릭터가 목적지로 이동은 하지만, 다리가 움직이지 않고 'Idle' 상태나 'T-Pose'로 미끄러지듯 이동함.
  • 원인 분석: 언리얼의 애니메이션 블루프린트(AnimBP)는 일반적으로 CharacterMovementComponent의 Velocity 값을 참조하여 이동 애니메이션을 결정한다. SetActorLocation은 물리적 가속도가 없는 '순간이동'의 연속이기에 엔진은 캐릭터가 정지 상태라고 판단한 것이다.

🛠️ 2. 2차 시도: Velocity 직접 주입과 애니메이션 활성화

[해결 방안]

  • Tick 함수 내에서 이동 방향 벡터에 속도를 곱해 GetCharacterMovement()->Velocity에 강제로 값을 대입했다.
  • 결과: 엔진이 캐릭터가 이동 중임을 인식하여 달리기 애니메이션이 정상적으로 출력되기 시작했다.

[새로운 난관: 시선 처리의 부재]

  • 캐릭터가 이동은 하지만 몸의 방향(Actor Rotation)이 목적지를 보지 않아 옆으로 게걸음을 치거나 뒤로 뛰는 어색한 연출이 발생했다.
  • UKismetMathLibrary::FindLookAtRotation을 도입하여 이동 방향을 바라보게 구현했다.

🔍 3. 3차 시도: "시선 vs 경로"의 충돌 (궤도 이탈 이슈)

[발생한 문제]

  • 이동 중에 목적지 근처의 특정 오브젝트(보물상자 등)를 바라보게 하기 위해 TargetRotation을 섞어주는 로직을 추가했다.
  • 현상: 캐릭터가 목적지를 향해 직선으로 가지 못하고, 몸을 트는 방향에 맞춰 이동 경로가 휘어지는 '궤도 이탈'이 발생했다.

[심층 원인 분석 (Senior's Perspective)]

  • 초기 코드에서 Velocity를 설정할 때 GetActorForwardVector()를 사용한 것이 문제였다.
  • 물리적 모순: 몸을 보물상자 쪽으로 틀면 ForwardVector 방향도 같이 변하게 된다. 이때 Velocity 방향도 함께 꺾이면서 물리 엔진은 캐릭터를 원래 목적지가 아닌 '캐릭터가 현재 바라보는 정면'으로 밀어버린 것이다.

🎯 4. 최종 해결책: 로직의 완전 분리 (Sequencing)

복잡하게 얽힌 물리 문제를 해결하기 위해 "이동(Movement)과 정렬(Alignment)" 단계를 철저히 분리하는 시퀀스 구조를 채택했다.

[Phase 1: 이동 단계 (Movement Focus)]

  • 캐릭터는 오직 목적지 좌표만을 바라보며 최단 거리로 이동한다.
  • Velocity를 계산할 때 Actor의 정면이 아닌 (목표지점 - 내위치).GetSafeNormal()을 사용하여 물리적 이동 경로를 고정했다.

[Phase 2: 전환 및 정지 (Stop & Reset)]

  • 목적지 도달 시 StopMovementImmediately()를 호출하여 관성을 제거하고, 이동 플래그(bIsForcedMoving)를 끈다.

[Phase 3: 회전 단계 (Rotation Focus)]

  • 이동이 완전히 멈춘 후, bIsRotatingToTarget 플래그를 활성화하여 목표 타겟(보물상자)을 향해 부드럽게 회전한다.
  • 연출 고도화: InterpSpeed를 3.0f로 낮게 설정하여 기계적인 회전이 아닌, 캐릭터가 대상을 인지하고 천천히 고개를 돌리는 듯한 부드러운 연출을 구현했다.

⚠️ 5. 시행착오: 데이터 무결성과 입력 제한

[Roll(X축) 값의 간섭]

  • 블루프린트에서 Target Rotation 입력 시 실수로 X(Roll) 값에 90도를 입력하여 캐릭터가 옆으로 누운 채 이동하는 참사가 발생했다.
  • 교훈: 언리얼의 회전축(Z-Yaw, Y-Pitch, X-Roll) 개념을 명확히 하고, 캐릭터 회전 시에는 반드시 Yaw 값을 주력으로 사용해야 함을 체득했다.

[입력의 완전 차단]

  • SetIgnoreMoveInput은 축 입력만 막을 뿐, 공격이나 구르기 같은 액션 매핑은 막지 못했다.
  • 이를 위해 모든 액션 함수 입구에 if (bIsForcedMoving) return;과 같은 상태 체크(CanExecuteAction) 로직을 추가하여 시스템의 견고함을 더했다.

💡 6. 결론 및 회고 

  1. 엔진 동작 원리 파악: SetActorLocation과 AnimBP 사이의 동기화 문제를 Velocity 수동 제어로 해결하며 언리얼 엔진의 구조적 특성을 깊이 이해했다.
  2. 설계적 판단 (Divide & Conquer): 복잡한 물리 연산을 하나에 뭉뚱그리지 않고, 단계별 시퀀스(이동 후 회전)로 분리하여 코드의 안정성과 가독성을 동시에 확보했다.
  3. 디펜시브 프로그래밍: 도착 판정 시 DistSquared를 이용한 성능 최적화, 프레임 드랍 대비 Over-shooting 방지 로직 등을 적용하여 실무 수준의 디테일을 챙겼다.