나만의 작은 도서관
[TIL][C++] 251021 MMO 서버 개발 119일차: [언리얼] 보간(Interpolation) 함수에 대해 알아보자, CMC의 DisableMovement()의 진실? 본문
Today I Learn
[TIL][C++] 251021 MMO 서버 개발 119일차: [언리얼] 보간(Interpolation) 함수에 대해 알아보자, CMC의 DisableMovement()의 진실?
pledge24 2025. 10. 21. 22:21주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
[언리얼] 보간(Interpolation) 함수에 대해 알아보자
- 보간(Interpolation)이란 알려진 두 점 사이에 위치한 점의 값을 예측하거나 추정하는 기법을 의미한다.
- 언리얼에서 보간은 두 지점의 중간 위치를 구할 때나 두 회전값의 중간 회전값을 구할 때 유용하게 쓰이며, 보간을 하기 위한 함수들이 다양하게 제공되고 있다.
FMath::Lerp
- 선형 보간(Linear Interpolation) 방식이며, 인자로 넣어주는 alpha 값으로 보간 계수를 설정한다.
출발지(Src): 0
목적지(Dst): 100
보간 계수(Alpha): 0.3
=> 보간 위치는 30
FMath::FInterpTo / VInterpTo / RInterpTo
- 감쇠율(Damping Rate)을 기반으로 한 보간 방식이며, 지수적(expotential)으로 목적지에 가까워진다. 인자로 넣어주는 InterpSpeed 값으로 감쇠율의 배수를 설정할 수 있다.
- 즉, DeltaTime * InterpSpeed은 Alpha가 된다.
- UE5의 InterpTo 계열의 함수들인 이 세 함수는 인자로 총 4개를 받으며, 각각 Current, Target, DeltaTime, InterpSpeed가 된다. 보간공식은 아래와 같다.
New Value = Current Value + (Target Value - Current Value) * (DeltaTime * Interp Speed)
감쇠율이 보간에 적용되는 방식의 예
- 쉽게 생각하면 공을 땅바닥에 떨어뜨렸을 때 점점 높이가 줄어드는 걸 생각하면 된다. 감쇠율이 20%이고 공의 처음 위치가 100일 때 다음 위치는 80, 그다음 위치는 64가 된다. 이를 공식을 적용하여 정리하면 아래와 같다.
출발지: 100 | 목적지: 0
공의 높이: 100
공의 높이: 100 + (0 - 100) * 0.2 = 80
공의 높이: 80 + (0 - 80) * 0.2 = 64
공의 높이: 64 + (0 - 64) * 0.2 = 51.2
========================================
목적지에 가까워진 거리
20 -> 16 -> 12.8
=> 지수적으로 가까워진다!
FInterpTo 계열의 보간 함수는 Tick 환경에 맞게끔 설계된 함수들
- 인자로 DeltaTime을 받는 것을 보면 알겠지만, InterpTo 계열의 보간 함수들은 Tick함수에 넣어서 사용하게끔 설계된 함수들이다. 이는 각 클라이언트가 프레임에 구애받지 않고 동일한 보간 속도를 적용하는데 용이하게 한다.
각 함수를 구분하는 방법?
- InterpTo 계열의 보간 함수의 이름을 보면 접두사만 다르고 이름이 같은데, 각각의 접두사 의미를 알고 있으면 구분이 쉽다. 의미는 아래와 같다.
- FInterTo의 ‘F’ = Float. 그래서 Src, Dst의 인자 타입은 Float이다.
- VInterTo의 ‘V’ = Vector. 그래서 Src, Dst의 인자 타입은 Vector이다.
- RInterTo의 ‘R’ = Rotator. 그래서 Src, Dst의 인자 타입은 Rotator이다.
FMath::FInterpConstantTo / VInterpConstantTo / RInterpConstantTo
- InterpConstantTo 계열의 보간 함수는 동일한 속도로 보간하는 함수들로, 인자로 넣어주는 InterpSpeed자체가 초당 이동량이 된다.
- UE5의 InterpTo 계열의 함수들인 이 세 함수는 인자로 총 4개를 받으며, 각각 Current, Target, DeltaTime, InterpSpeed가 된다.
Current = 0
Target = 100
InterpSpeed = 50
DeltaTime = 0.0167 (≈ 60FPS)
Frame당 이동 = 50 * 0.0167 = 0.8335
→ 매 프레임 0.8335씩 증가, 약 2초 후 목표 도달
- InterSpeed가 50, 즉, 초당 이동량이 50이므로 100만큼의 거리를 가는 데 걸리는 시간은 2초가 된다.
[언리얼] CMC의 DisableMovement()의 진실?
- CMC::DisableMovement() 함수는 호출시 CMC가 처리하는 모든 이동을 중단하게 된다.
root motion과의 관계성
- CMC를 이야기하다보면 root motion 이야기를 하게 된다. 만약 DisableMovement함수를 호출했다면, root motion을 활성화한 몽타주를 재생하는 경우 이동을 할까?
- 결론을 말하자면 이동하지 않는다. 그 이유는 DisableMovement() 함수의 내부를 보면 알 수 있는데, 그 내부는 아래와 같다.
void UCharacterMovementComponent::DisableMovement()
{
if (CharacterOwner)
{
SetMovementMode(MOVE_None);
}
else
{
MovementMode = MOVE_None;
CustomMovementMode = 0;
}
}
- 정말 별 거 없다. 그저 캐릭터의 MovementMode를 Move_None으로 바꿔주기만 한다. MOVE_None이 되면 해당 캐릭터는 절대 움직이지 않기 때문에 root motion이더라도 움직이지 않는 것이다. 세부적인 동작 과정을 나열하자면 아래와 같다.
세부 동작
- DisableMovement() → MovementMode = MOVE_None으로 설정됨.
- → 캐릭터 이동 속도 계산과 위치 갱신(물리기반 이동)은 멈춤.
- → Velocity는 0으로 유지되고 Tick 시 위치 업데이트도 안 함.
- 하지만 Root Motion은 AnimInstance 단에서 계산되어 USkeletalMeshComponent::TickPose()에서 발생. 이때:
- Root Motion이 활성화되어 있다면(bRootMotionMode != IgnoreRootMotion),
- UCharacterMovementComponent는 여전히 ConsumeRootMotion()을 호출하여 Root Motion Transform을 받아서 처리하려고 함.
- 그러나 MovementMode == MOVE_None 상태에서는 실제 이동 적용이 제한됨.
- 즉, 루트모션의 Transform 데이터는 계산되지만 위치 반영이 안 된다.
| 항목 | 상태 | 설명 |
| Root Motion 계산 | 유지됨 | 애니메이션 블렌딩과 루트 본 이동은 계속 계산됨 |
| 캐릭터 실제 이동(Transform 변경) | 중단됨 | CMC가 MOVE_None 상태라 이동 반영 안 됨 |
| Velocity | 0 | 물리 이동 정지 |
| Root Motion 애니메이션 자체 | 재생됨 | 시각적으로는 움직이는 것처럼 보이지만 실제 위치는 안 바뀜 |
EnableMovement()가 없는데 어떻게 되돌릴까?
- 왜인지는 모르겠지만 UE5에는 DisableMovement() 함수는 있으면서 반대인 EnableMovement() 함수는 없다. 그럼 DisableMovement()를 호출하면 되돌릴 수 없는 건가?
- 위에서 내부 로직을 봐서 알겠지만 그저 SetMovementMode에 Move_None으로 인자를 넣어서 호출할 뿐이므로, 움직이게 하고 싶다면 Move_Walking 같은 걸 넣어주면 된다.
// 이동 비활성화
CharacterMovement->DisableMovement();
// 일정 조건 후 이동 재활성화
CharacterMovement->SetMovementMode(MOVE_Walking);
// 또는
CharacterMovement->SetMovementMode(EMovementMode::MOVE_Walking);
