언리얼엔진3 에 있는 루트모션에 대해 알아본다.
루트모션은 캐릭터의 역동적인 좌표이동을 에니메이터가 직접 제어하게 하기위해 고안되었다.
기본 원리는 이러하다. 캐릭터의 최상위 루트 노드의 위치변화(translation delta)량을 캐릭터의 월드 좌표에 곧바로 반영한다.
일반적인 게임의 캐릭터 이동 로직은 고정의 acceleration 을 사용하여 시간의 흐름에 따라 선형적으로 velocity 를 증가시키고 그것이 캐릭터의 location 에 반영되는 구조이다. 이 구조에서는 acceleration 과 velocity 에 불규칙한 패턴을 주기가 어렵다. 하지만 루트모션을 이용하면 이것이 쉽게 가능해진다.
엔진에서는 다양한 루트모션 방식이 존재하는데, 다양한 만큼 이들의 이용법이 약간씩 틀리다.
enum ERootMotionMode { RMM_Translate, // move actor with root motion RMM_Velocity, // extract magnitude from root motion, and limit max Actor velocity with it. RMM_Ignore, // do nothing RMM_Accel, // extract velocity from root motion and use it to derive acceleration of the Actor RMM_Relative, // if bHardAttach is used, then affect relative location instead of location };
중요한 것만 살펴보자면
acceleration 과 velocity 로 인한 좌표이동에 추가적으로 루트모션의 위치 변화량을 더해준다. 일반 이동과는 별개로 적용된다는 얘기
루트모션의 위치 변화량1)으로부터 velocity 의 magnitude 를 구하고 그것을 속도한계치로 사용한다.
즉, acceleration 은 프로그래머가 직접 제어할 수 있지만2) velocity 는 magnitude 까지만 올라간다.
중요한(웃긴?) 것은 루트모션에서 magnitude 만 뽑아내고 direction 은 acceleration 의 것을 사용한다는 것이다. 이건 원치 않는 결과가 나올 수도 있는데 예를 들어 달리다가 180도 턴하는 동작이 있다고 치자. 이 동작의 루트모션 내용은 전진→정지→후진 정도가 될 것이다.3) 하지만 이 동작에 RMM_Velocity 를 적용하면 전진→정지→후진 의 magnitude 만 뽑아내고 acceleration 은 별도로 구하여 (사용자의 방향키 입력따위로부터) 적용한다. 즉 사용자가 전진키만 누르고 있다면 전진→정지→전진 이라는 엉뚱한 결과가 나온단 얘기. 좀 어이 없을진 모르지만 RMM_Velocity 의 진짜 용도는 따로 있으니 암튼 잘 알고 써야하겠다. 글고 방금의 예에서는 아래 설명할 RMM_Accel 을 사용해야 원하는 결과가 나온다.
루트모션의 위치 변화량으로부터 velocity 를 구하고 그것으로 다시 acceleration 을 구한다.
에니메이션 루트모션에 의해 velocity 와 acceleration 이 정해지기 때문에 프로그래머가 이를 직접 제어할 수 없다. (값을 직접 수정해도 소용없다.)
가장 많이 사용되는 모드이기도 하다.
루트모션을 수행하려면 그에 앞서 시퀀스의 RootBoneOption 에서 원하고자 하는 축의 옵션을 RBA_Translate 로 변경해 주어야 한다. 이것은 축별로 설정 가능하고 일반적으로 X,Y,Z 축 모두 RBA_Translate 로 변경하고 루트모션이 끝난 후에 다시 Default 값으로 롤백시켜준다.
RBA_Translate 옵션이 활성화 되야 루트본의 델타량 (이동/회전량) 을 별도의 변수에 추출하여 저장해두고 그 값을 사용하여 루트모션을 수행하기 때문이다. 더 자세한 사항은 UAnimNodeSequence::ExtractRootMotion 을 참고
위 세가지 루트모션은 추구하는 바는 같지만 그 과정이 같지가 않다. 이로인해 약간 쓰임새가 다른데 각각의 적절한 예를 들어보면 다음과 같다.
void USkeletalMeshComponent::ProcessRootMotion( FLOAT DeltaTime, FBoneAtom& ExtractedRootMotionDelta, INT& bHasRootMotion ) { ... if ( bHasRootMotion ) { ... // 이번 tick 에 in-game physics 가 적용되었기 때문에 다음 프레임에 루트모션을 적용하기 위해 PreviousRMM 과 비교한다. const UBOOL bCanDoTranslation = ( RootMotionMode == RMM_Translate && PreviousRMM == RMM_Translate ); // 이하 코드 참조 ... } ... }
void APawn::CalcVelocity( FVector &AccelDir, FLOAT DeltaTime, FLOAT MaxSpeed, FLOAT Friction, INT bFluid, INT bBrake, INT bBuoyant ) { ... }
개발을 하다보면 방향키 입력을 받는 동안 루트모션이 있는 에니메이션을 반복적으로 재생할 필요가 생길 것이다.
예를 들어, 전사가 방패를 들고 가드를 올린 상태에서 앞쪽방향키를 입력하면 특정 템포를 가지고 전진한다던지 (이 경우 선형 운동이 아니라 템포가 있는 전진이기 때문에 루트모션으로 제작되는 것이 더 바람직하다.) 아니면 GoW2 의 경우 DBNO 에 빠진 병사가 방향키를 누르면 비틀거리며 해당 방향으로 기어가는 경우가 그러하다.
이와 같이 '방향키가 눌렸을 경우 루트모션이 수행되는 경우' 라면 RMM_Velocity 와 AnimTree 의 노드분기를 사용하는 것이 좋다. 이유는 아래와 같다.
RMM_Velocity 를 사용하고 AnimTree 의 노드분기가 Acceleration 을 기준으로 분기가 이뤄지게 한다면 베스트다. 다만 Autonomous 와 Authority 는 정확한 Acceleration 값을 가지지만 Simulated 의 Acceleration 은 단지 dir 역할만 한다. (노멀라이징 된다.)