ChooserTable
지난 글에서는 PSD하나에 모든 애니메이션 시퀀스를 때려 박고 Pose Search와 Motion Matching을 통해서 적절한 애니메이션을 찾게끔 해보았다. 하지만 영상에서도 보이지만 애니메이션이 너무 많고 그 와중에서 적절한 모션을 찾기는 많이 어려워 중간중간 애니메이션이 밀리거나 이상한 애니메이션이 재생되는 것을 볼 수 있었다. 그렇기에 GASP에서도 PSD와 PSS를 경우에 따라서 나누어 각 경우별로 어떠한 PoseSearchDatabase를 어떠한 가중치(PoseSearchSchema)에 따라 선택할지를 고르게끔 구현되어 있는데 이것이 바로 Chooser Table이다.
일단 PSD(PoseSearchDatabase)를 경우마다 나누어 보자
속편하게 하기 위해서는 GASP의 애셋을 그대로 들고 오는 것이 편하겠지만 그렇게 되면 프로젝트가 불필요하게 무거워지게 된다. 또한 우리가 만들 최종 프로젝트에는 Sprint, Jump, Traversal와 같은 애니메이션은 필요하지 않기 때문에 PSD는 직접 GASP를 참고해서 작성해 보도록 하고 PSS와 같은 경우는 GASP의 것을 그대로 가져오도록 하자

자 이제 지독한 노가다의 시간이다. 우리 캐릭터의 상태는 일단 Idle, Run, Walk가 이렇게 있을 것이고 각 상태별로 높은 곳으로부터의 착지, 낮은 곳으로부터의 착지, 출발, 정지, 방향전환의 상태가 있을 것이다. 이 상태 별로 PSD를 작성해 준다.

새로 작성한 PSD들을 모두 이전에 생성했던 PSN(PoseSearchNormalize)에 추가해주는 것도 잊지 말자.
PlayerInputState
ChooserTable을 생성하기 전에 먼저 ChooserTable이 보고 판단할 플레이어의 인풋 상태를 담을 구조체인 PlayerInputState를 만들어 주도록 하자 새로운 C++클래스를 부모클래스를 none로 설정해 주고 만들어주자. cpp파일은 건들지 말고 h에 있는 내용은 날리고 다음과 같이 언리얼 구조체인 UStruct의 작성 방식에 맞추어 작성해 준다.
// PlayerInputState.h
#pragma once
#include "CoreMinimal.h"
#include "PlayerInputState.generated.h"
USTRUCT(BlueprintType)
struct PROJECTM_API FPlayerInputState
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerInputState")
bool bWantsToMove =false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerInputState")
bool bWantsToDodge = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PlayerInputState")
bool bIsAiming = false;
};
이 구조체의 변수들의 값이 무엇인지를 근거로 ChooserTable이 적절한 PSD를 고르게 될 것이다. 추후에 여기에 bIsFalling과 같은 변수를 추가해서 좀 더 세밀한 애니메이션 선택이 될 수 있게 도와줄 수 있다. 지금 당장은 움직임만 구현해 볼 테니 저 세 변수들로 ChooserTable을 구성해 보도록 하자.
ChooserTable의 구성
일단 우리들만의 ChooserTable을 구성하기에 앞서 GASP에서는 어떻게 ChooserTable을 구성했는지 확인해 보자

큰 분류로 우선 나누고(Idles, Walks, Runs,... ) 그 내부에 세부 PSD를 배치한 형식이다. 새로 ChooserTable을 생성하기 위해서는 콘텐츠 브라우저를 우클릭하여 Animation > ChooserTable을 골라주면 되며 ABP는 우리가 만든 캐릭터의 ABP를 골라주고 원래라면 Animation Asset을 반환하면 되겠지만 우리는 PSD를 반환하게끔 해야 하니 반환 값에 PoseSearchDatabase를 넣어 PSD를 반환하게 생성해 주자.

이제 우리는 행을 하나씩 추가하면서 ChooserTabled을 구성해 주겠다. Add Row를 클릭해서 Nasted Chooser을 골라주면 되는데 이것이 큰 분류에 해당하는 부분이고 edit을 눌러서 내부에 세부적으로 조건을 명시할 수 있게 된다. 일단 조건에 맞추어서 큰 분류부터 행을 추가하면서 채워 넣어보자

이제 Idles의 Edit을 눌러서 세부 설정으로 들어가 주자 GASP에서는 다음처럼 되어있다.

보니 가볍게 착지하는지, 무겁게 착지하는지, 회전을 하려는지 여부와 캐릭터의 속도에 따라서 조건을 준 것을 알 수 있다. 일단 열에 해당하는 변수들은 추후에 작업하는 걸로 하고 최대한 GASP를 따라서 PSD를 행에 채워 넣는 것부터 해보도록 하겠다. 아까 만든 PSD 중에서 Idle상태와 관련된 PSD만 넣어주면 된다. 각 상태에서 정지하는 PSD와 Idle 자체 그리고 제자리에서 회전하는 PSD를 찾아 넣어준다.

일단 작성을 해주면서 큰 분류의 Land는 각 세부 분류에서 Land Heavy 등을 추가했기에 필요 없을 것이라 판단 최종적으로 큰 분류는 Idles, Run, Walk고 내부로는 Loop, Stop(Idle의 경우), Start(Idle 외 나머지), Pivot, TurnInPlace, Land Heavy, Land Light로 구성되게 되었다.
Chooser Table의 핵심 열(Column)을 설정해 보자
다시 위의 GASP의 이미지를 확인해 보면 Movement Mode 등의 구조체나 이 넘을 통해서 열의 변수들을 획득하고 있는 것을 볼 수 있다. 우리는 저 중에서 일부분의 애니메이션만 구현하기에 모두 다 구현할 필요는 없고 우리한테 맞는 것이 무엇일지 분석해 보도록 해보자.
일단 첫 번째는 속도(Speed) 일 것이다 캐릭터가 이동 중 멈춘다고 생각해 보자, ChooserTable은 이 캐릭터가 뛰다가 멈추는지 걷다가 멈추는지 알아야 한다. 그렇기에 멈출 때의 캐릭터의 속도를 근거로 특정 수치 이상이면 Run상태에서 멈추는 것 아니면 Walk상태에서 멈추는 것으로 판단해서 적절한 애니메이션을 찾아 줄 것이다.
두 번째로는 회전속도(Angular Speed)이다. 사실 회전속도 자체를 구할 필요까지는 모르겠으나 적어도 캐릭터의 회전속도가 0보다 클 때 회전 여부를 판단해 줄 정도면 될 것 같다.
세 번째로는 낙하여부(IsFalling)이다. 우리는 점프는 구현하지 않을 테지만 캐릭터가 낙하하는 상황은 가정하여 Land 애니메이션을 PSD에 구성해 놓았다. 특히 가벼운 착지, 무거운 착지 등 낙하 시간도 고려하여 구별해 두었기에 이 또한 필수적으로 필요한 열이 될 것이다.
일단 변수는 대충 추렸으니 나중에 문제가 생기면 추가하기로하고 해당 변수들을 얻어보도록 하자. 아까 생성한 구조체 PlayerInputState에 새로운 변수를 다음처럼 선언해 준다.

그다음 해당 변수들을 Character에서 매 Tick마다 갱신해 주면 될 것이다. 다음처럼 코드를 작성해 주자
void AProjectMCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UCharacterMovementComponent* MovementComp = GetCharacterMovement();
if (MovementComp != nullptr)
{
PlayerInputState.bIsFalling = MovementComp->IsFalling();
PlayerInputState.CurrentSpeed = GetVelocity().Size2D();
PlayerInputState.FallingSpeed = GetVelocity().Z;
}
}
이제 이 변수들을 바탕으로 한번 ChooserTable을 구성해 보도록 하자 일단 우리가 만든 구조체를 읽어드리기 위해 파라미터 중 Struct Parameter를 PlayerInputState로 설정해 주자

이제 Add Column을 눌러서 bool값을 선택하면 PlayerInputState의 값들이 보이게 될 것이다. 이제 조건에 맞게 설정해 보도록 하자

Idle은 움직이면 안 되니 false로 두고 Aim 중인지 여부에 따라 Walk와 Runs로 나누게 했다. 그다음 세부로 들어가 보자

캐릭터의 속도와 낙하 속도에 따라 다음처럼 설정해 주고 문제는 새로운 Should Rotation을 추가했는데 이는 TurnInPlace여부를 판단하기 위해서 새로 구조체에 추가해 준 변수이다. 구조체에 bool값을 선언해 주고 이 또한 Tick마다 액터의 로테이션과 컨트롤러의 로테이션의 차이를 계산해서 갱신하게끔 해두었다.
void AProjectMCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UCharacterMovementComponent* MovementComp = GetCharacterMovement();
if (MovementComp != nullptr)
{
PlayerInputState.bIsFalling = MovementComp->IsFalling();
PlayerInputState.CurrentSpeed = GetVelocity().Size2D();
PlayerInputState.FallingSpeed = GetVelocity().Z;
float ActorYaw = GetActorRotation().Yaw;
float ControlYaw = GetControlRotation().Yaw;
float YawDelta = FRotator::NormalizeAxis(ControlYaw - ActorYaw);
PlayerInputState.ShouldRotation = FMath::Abs(YawDelta)> 5.0f;
}
}
이제 해당 변수들을 이용해서 ChooserTable을 작성해 보도록 하자!
애님 그래프 연결
기존의 ABP의 애님그래프에서 다음 사진처럼 수정을 한다.



이제 애님그래프 노드를 연결하고 제대로 작동하는지 테스트 하면 된다.
'unreal 5기' 카테고리의 다른 글
| 260122 언리얼엔진 본캠프 113일차 GASP(7) (0) | 2026.01.22 |
|---|---|
| 260120 언리얼엔진 본캠프 111일차 GASP(6) ChooserTable-2 (0) | 2026.01.20 |
| 260115 언리얼엔진 본캠프 109일차 GASP(4) MotionMatching (1) | 2026.01.15 |
| 260114 언리얼엔진 본캠프 108일차 GASP(3) (0) | 2026.01.14 |
| 260113 언리얼엔진 본캠프 107일차 GASP(2) (0) | 2026.01.13 |