-
[Unreal] 언리얼 모션 매칭 Motion Matching #2 - Chooser Table쾌락없는 책임 (공부)/Unreal 2025. 6. 29. 12:01반응형
지금까지 모션 매칭에 대해서 필요한 3가지인 PSD, PSS, PSN을 알아보았습니다. 하지만 이들의 경우 기본적인 스키마 설정에 따른 애니메이션 분류가 가능합니다. 이 말인 즉 같은 속도와 방향을 가는 걷기 애니메이션과 뛰기, 앉아 걷기 애니메이션을 구분할 여지가 없다는 것입니다. 또한 한 PSD에 모든 애니메이션들을 넣게 되면 탐색에 있어 시간을 많이 소요하게 될 것입니다. 에픽에서도 이에 대한 생각이 있어 함께 준비를 해둔 것들이 있습니다. 바로 [선택기 테이블 - Chooser Table] 입니다.
해당하는 플러그인을 통해 어떠한 애니메이션 데이터베이스를 찾아낼지 알 수 있도록 해주며 기존 ABP에서 사용하는 스테이트 간 전환을 해당 테이블에 설정한 값으로 대체할 수 있습니다. 어떠한 방식으로 ABP의 스테이트들을 대체할 수 있는지, 에픽의 샘플에서는 어떤 방식으로 애니메이션들을 분류했는지 알아보겠습니다.필요 플러그인
일단 활성화하기 전 Chooser라는 플러그인을 활성화해야 합니다. 아직까지 개선의 여지가 많이 보이는 것 같지만 1.0 버전으로 실험 버전을 떼고 나온 플러그인입니다.
Chooser Table 사용법플러그인 설치 후 콘텐츠 브라우저에서 우클릭 > miscellaneous > Chooser Table을 통해 테이블을 제작할 수 있습니다.
Chooser Table을 만들 때 어떠한 것들을 반환할지 지정해야 합니다. 이를 보면 애니메이션 외 다른 에셋들에도 사용할 수 있지만 지금은 애니메이션에 대해서만 알아보겠습니다. 저는 Anim Class 를 제 ABP 로 지정하였습니다.새로 Chooser Table 을 만든 뒤 상단 메뉴의 Table Setting을 누르면 오른쪽에서 해당하는 테이블의 Detail을 볼 수 있습니다. 오른쪽 Paremeter에서 해당하는 테이블의 Input과 Output 이 지정 가능하며 저는 In/Out으로 ABP를, In으로 추가적인 정보를 담는 제 구조체인 'ChooserPLayerSettings'를 넣었습니다.
- Input : Chooser Table에서 사용할 값들을 지정, Input 으로 들어온 값들을 기반으로 테이블을 구성하게 됩니다.
- Output : Chooser Table 에서 반환할 값으로 테이블에서 선택된 Row에 따라 값을 만들 수 있습니다. 추후 각 애니메이션 상태 (Idle, Run, Walk 등)을 반환하는 데 사용할 수도 있습니다.
- Input/Output : Input, Output으로 둘 다 쓸 수 있는 인자입니다.
Add Row를 눌러 테이블에서 반환하는 걸 구성할 수 있게 됩니다.
항목 설명 Asset 컨텐츠 브라우저의 Asset 을 반환하며 일반 포인터 타입입니다. Asset (Soft Reference) 위 Asset 반환을 약참조로 반환하게 됩니다. 애니메이션이 아닌 경우에는 약참조로 유용하게 사용 할 수 있을 것으로 보입니다. Evaluate Chooser 또다른 Chooser Table 을 넣을 수 있습니다.
아래 예시를 적겠지만 애니메이션 LOD 등을 구현하는데 활용 가능한 옵션입니다.Nested Chooser Evaluate Chooser 는 다른 Chooser Table 에셋을 만들어야 하지만 이 옵션은 Chooser Table 안에서 만들 수 있습니다. Lookup Proxy 다른 외부의 값을 통해 테이블의 반환값을 결정하게 됩니다. (아래에서 설명)
여기서 짚을 건 Nested Chooser로 추가 시 위 사진처럼 테이블 오른쪽 'Nested Choosers' 메뉴에서 확인이 가능합니다. 이 경우 Chooser Table 안에 새로운 테이블을 만들게 되는데 따로 Chooser Table을 새로 생성하는 방식이 아니라 콘텐츠 브라우저에 추가하지 않아서 간편합니다. 이런 Nested Chooser 들의 경우 ABP의 State처럼 사용 가능하며 프로젝트의 상황에 맞게 선언 가능합니다. 저의 경우 주로 Locomotion 과 관련된 것들만 선언하였습니다.적절히 Row 들을 Add Column을 통해 어떤 값들을 기준으로 해서 Row를 산정할지를 정해야 합니다. 위처럼 여러 변수들을 기반으로 활용 가능하며 저 같은 경우 보통 플래그(bool), 상태값(enum), 속도 범위(float range)를 활용했으며 그 모습은 아래와 같습니다.
위 상태에서는 IsFalling 이 false, IsCrouching 이 false, IsMoving 이 false 라면 맨 위 Stand Idle (Nested Chooser) 이 선택되게 됩니다.
Stand Idle에서는 하위에 수많은 PSD 들을 두고 있으며 역시 이 값들을 바탕으로 해서 적합한 PSD를 반환하게 됩니다. 이런 식으로 기존 ABP의 State 들을 표로 대체하여 현재 상황에 맞는 애니메이션(데이터베이스)을 반환하게 됩니다.
Motion Matching 노드의 On Update 에 바인딩한 블루프린트 함수 이후 ABP로 와 Motion Matching의 On Update에 함수를 정의해 넣습니다. (이때 함수는 Thread Safe로 선언합니다)
이곳에서 Evaluate Chooser 노드를 추가한 뒤 설정한 Input 들을 넣으면 결괏값들을 받아볼 수 있습니다. Evaluate Chooser 노드 설정에서 결과는 'First Result', 'All Results'로 설정할 수 있는데 모션 매칭에서 애니메이션들을 선택하니 주로 'All Results'로 설정합니다.
이후 'Set Database to Search'에 결괏값을 넣어주면 모션 매칭에서는 Chooser Table에서 반환한 값을 기반으로 모션 매칭을 시도하게 됩니다. 이를 통해 모션 매칭이 모든 PSD 들을 보지 않아 성능이 증가되고 속도가 같은 Idle 들(Crouch, Stand, Swim 등)을 테이블에서 미리 걸러내기에 모션 매칭 결과물을 의도한 결과물로 만들기 쉬워집니다.
Chooser Table로 애니메이션 LOD 설정하기
(애니메이션들은 상황 따라 필요한 변수와 함수들이 다르니 예시로만 보면 됩니다.)
모션 매칭에서는 데이터베이스에 애니메이션이 많을수록 더 많은 연산이 필요해집니다. 실시간으로 이들의 가중치들을 봐야 하기에 저사양에서는 애니메이션이 많을수록 성능을 잡아먹게 됩니다. 여기서 Chooser Table을 활용해 다른 리소스들(텍스쳐 등)처럼 LOD와 같은 설정이 가능합니다.
에픽에서는 Chooser Table 을 통해 애니메이션 LOD (Level of Detail) 을 구현하였다
아직 제 사이드 프로젝트에서는 LOD 분류를 준비하지 않아 에픽의 샘플 사진을 가져와 봤습니다. 샘플에서는 ABP에서 float 값을 두고 이 값의 범위에 따라 LOD를 분류했습니다. 이를 통해 Sparse인 경우에는 더 적은 양의 애니메이션들을 가진 데이터베이스들을 살펴보면서 성능적인 이득을 볼 수 있습니다. 때에 따라 더 다양한 LOD를 만들 수 있겠지만 일정 사양 이하인 경우에는 그냥 Sparse를 재생하게 하는 등 2개의 레벨만 준비하면 충분할 듯합니다.그러면 무기마다 애니메이션이 다른 경우 어떻게 해야 하나
이런 게임은 무기가 교체되지 않으니 고려할 필요는 없다
직업별로 무기가 이미 다르고 무기 탈부착이 되지 않는다면 사실 고려하지 않아도 되는 부분입니다. 다만 기획에 따라 무기의 탈부착이 되거나 무기별로 애니메이션을 다르게 가져가야 한다면 Chooser Table을 어떻게 해아 할지 애매해집니다. 무기별로 전부 PSD를 만드는 건 어쩔 수 없지만 이대로 가다가는 테이블도 계속 만들어야 할 판이죠. 샘플에는 없지만 이를 위해 에픽에서 또 준비한 게 있습니다. 바로 Proxy Table입니다.'Lookup Proxy'
기존의 Chooser Table에서는 값을 변동할 수 없지만 Proxy Table을 사용하면 변동 가능한 값을 반환하게 할 수 있습니다. 이를 활용하기 위해서는 Proxy Asset과 Proxy Table을 둘 다 알아야 하는데 개발자라면 Proxy Asset 은 Key값이고 Proxy Table은 Key/Value 쌍으로 알면 이해가 쉽습니다.본격적인 예시가 있으면 더 이해가 되기 쉬우니 샘플 프로젝트의 'Stand Idle'에 대해서 Proxy Table을 적용해 보겠습니다. 사용할 Proxy Asset을 만든 뒤
Chooser Table에서 Add Row > Lookup Proxy를 통해 만든 뒤 오른쪽 Detail에서 만들어둔 Proxy Asset을 지정합니다. 에픽의 실수인지 이상하게 표에서는 바로 Proxy Asset 이 지정되지 않습니다.
이후 Input으로 들어가는 ABP에 Proxy Table 변수를 선언한 뒤 (디폴트 값으로 지정하거나 상황에 따라 적절한 Table을 대입하면 됩니다)
해당 Row에서 변수를 바인딩해주면 됩니다.
이후 ABP에 지정한 Proxy Table에셋으로 가 Proxy Table 에 해당하는 PSD를 반환하도록 만들면 됩니다.
만일 무기에 따른 Idle 이 필요하다 하면 Stand Idle Proxy Asset을 만든 뒤 각 무기별 Proxy Table을 만들어 ABP에서 갈아 끼우면 되는 것입니다. 간단하죠?
Proxy Table은 왜 함수 바인딩은 되지 않는가
모션 매칭의 경우 만일 적절한 애니메이션이 없다면 T, A 포즈가 나오면서 재생이 되지 않거나 오류 메시지가 나오게 됩니다. 때문에 Proxy Table을 사용할 때 해당 테이블에서 무조건적으로 애니메이션이 나올 수 있도록 해야 합니다. 보통의 프로그래머들이라면
UProxyTable* GetCurrentProxyTable() const { return IsValid(CurrentProxyTable) ? CurrentProxyTable : DefaultProxyTable; }
과 같이 만일을 대비해 기본값을 넘겨주는 등의 장치를 남겨 안전을 도모하고 싶어 질 겁니다. 또는 위에서 이야기한 대로 무기에 Proxy Table을 가지게 하고 애니메이션 함수에서 이를 가져오게 해 편리함을 넣고 싶어 할 수도 있습니다.다만 안타깝게도 Chooser Table에서는 UProxyTable* 을 반환하는 함수를 바인딩할 수 없습니다. 스레드 세이프, const, pure 함수들도 동일합니다. 이는 에픽에서 애초에 되지 않게 만들어 둔 것으로 LookupProxy.h 헤더 파일을 보면 확인이 가능합니다.
// BoolColumn.h UPROPERTY(EditAnywhere, Meta = (BindingType = "bool", BindingAllowFunctions = "true", BindingColor = "BooleanPinTypeColor"), Category = "Binding") FChooserPropertyBinding Binding; // LookupProxy.h UPROPERTY(EditAnywhere, Meta = (BindingType = "UProxyTable*"), Category = "Binding") FChooserPropertyBinding Binding;
보시면 Bindings에서 BindingAllowFunctions = “true”로 지정하는 부분이 Proxy Table 에는 없는 것을 확인할 수 있습니다. 정확한 의도까지 파악하기는 어려우나 일단 코드 단에서 함수 바인딩이 안되도록 막아둔 걸 확인이 가능합니다.강제로 Proxy Table을 반환하는 함수를 바인딩할 수 있게 만들기
언리얼의 장점인 엔진 코드 개조가 가능하다는 점을 활용, 함수 바인딩이 되도록 강제할 수 있습니다. 소스코드로 언리얼을 빌드한 경우 아래 파일에 해당하는 구조체, 함수를 변경하면 됩니다.
// LookupProxy.h
USTRUCT() struct PROXYTABLE_API FProxyTableContextProperty : public FChooserParameterProxyTableBase { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Meta = (BindingType = "UProxyTable*", BindingAllowFunctions = "true"), Category = "Binding") FChooserPropertyBinding Binding; virtual bool GetValue(FChooserEvaluationContext& Context, const UProxyTable*& OutResult) const override; CHOOSER_PARAMETER_BOILERPLATE(); };
// ChooserPropertyAccess.h
template <typename T> bool FChooserPropertyBinding::GetValuePtr(FChooserEvaluationContext& Context, T*& OutResult) const { using namespace UE::Chooser; FResolvedPropertyChainResult Result; if (ResolvePropertyChain(Context, *this, Result)) { if (Result.Function == nullptr) { OutResult = reinterpret_cast<T*>(Result.Container + Result.PropertyOffset); return true; } else { UObject* Object = reinterpret_cast<UObject*>(Result.Container); if (Result.Function->IsNative()) { switch (Result.PropertyType) { case EChooserPropertyAccessType::Float: case EChooserPropertyAccessType::Double: case EChooserPropertyAccessType::Int32: case EChooserPropertyAccessType::Bool: break; default: FFrame Stack(Object, Result.Function, nullptr, nullptr, Result.Function->ChildProperties); Result.Function->Invoke(Object, Stack, &OutResult); return true; } } else { switch (Result.PropertyType) { case EChooserPropertyAccessType::Float: case EChooserPropertyAccessType::Double: case EChooserPropertyAccessType::Int32: case EChooserPropertyAccessType::Bool: break; default: UObject* OutObject = nullptr; Object->ProcessEvent(Result.Function, &OutObject); OutResult = reinterpret_cast<T*>(&OutObject); return true; } } } } return false; }
캡쳐하려면 창이 꺼져서 움짤을 캡쳐해 가져왔습니다. 이제 이렇게 함수를 볼 수 있습니다. 단 BlueprintThreadSafe이며 const (블루프린트에서는 Pure) 함수여야 바인딩이 됩니다. 또한 여기서 블루프린트 함수는 꼭 반환값의 변수 명이 ‘ReturnValue’로 되어야 테이블에서 인식이 가능합니다. 이유는 모르겠으나 만일 테이블에서 바인딩할 함수가 보이지 않는다면 반환하는 변수의 변수명을 확인하세요.
(Return Value로 띄어쓰기 있어도 인식을 못하는 문제가 있습니다)
반응형'쾌락없는 책임 (공부) > Unreal' 카테고리의 다른 글
[Unreal] 언리얼 모션 매칭 Motion Matching #4 - Rewind Debugger, Chooser Table 디버깅 (0) 2025.07.04 [Unreal] 언리얼 모션 매칭 Motion Matching #3 - Trajectory (0) 2025.07.04 [Unreal] 언리얼 모션 매칭 Motion Matching #1 - Motion Matching 의 기본 요소 (1) 2025.06.28 [Unreal] 언리얼 엔진을 소스코드로 빌드할 때 나오는 오류 - Unreal build by source code error (0) 2025.05.29 [Unreal] missing a MarkArrayDirty on element add/remove? (0) 2025.05.16