unreal 5기

260122 언리얼엔진 본캠프 113일차 GASP(7)

parkjinnam 2026. 1. 22. 20:50

최종 프로젝트 시작

드디어 최종 프로젝트가 시작되었다. 사실 어제 시작했는데 프로젝트 발제와 초기 프로젝트 세팅을 하는 데에만 하루가 꼬박 지나가게 되어 사실상 작업은 오늘 시작하게 되었다. 우리의 최종 프로젝트는 다크소울 1과 엘든링과 같은 소울류의 게임을 제작하는 것을 목표로 꽤나 높은 목표를 갖고 시작하게 된 만큼 최선을 다해야 할 것이다. 특히나 이러한 RPG 장르의 핵심이라 할 수 있는 캐릭터 파트를 맡게 되어 그 책임이 막중한 지금 기존까지 공부했던 MotionMatching과 ChooserTable을 이용한 애니메이션을 먼저 구현해두려고 한다.

 

기본적인 세팅을 해주자

기존의 프로젝트가 아닌 새프로젝트로 작업을 시작하는 만큼 기본적인 프로젝트의 플러그인을 활성화해주어야 한다. 활성화해주어야 하는 플러그인으로는 PoseSearch, Chooser, AnimationLocomotionLibrary, MotionTrajectory로서 이 네 개의 플러그인은 Blank 탬플릿으로 프로젝트를 생성하면 기본적으로 비활성화되어 있으며 나머지 ControlRig와 같은 플러그인들은 기본적으로 활성화되어 있을 것이다. 해당 플러그인들을 활성화해준 뒤 받아둔 애셋을 활용해서 빠르게 PSD와 PSS를 작성해 준다. 특히 PSS는 지난 프로젝트에서도 진행했듯 GASP의 것 중 사용할 것들만 마이그레이션 해서 사용하도록 하고 PSD의 작업을 시작하려 한다. 일단 애니메이션 애셋의 스켈레톤을 기본이 될 SK_Mannequin으로 교체해주어야 한다.

스켈레톤 변경

 

그다음 Paladin의 애니메이션에 사용될 PSD 작업을 해주어야 한다. 크게 Idle, Combat_Idle, Run, Combat_Run, Walk, Combat_Walk를 먼저 구성해서 캐릭터의 기본적인 이동을 구현하려고 한다. 해당 PSD를 구성하여 기본적인 이동을 구현해 보자

PSD 분류

 

캐릭터는 전투상태와 일반상태 2개의 상태로 분류하기로 했기에 Chooser를 구성할 때 상태 변수에 따라 다른 NestedChooser를 통해서 들어가도록 해야 한다.

구분할 변수는 나중에 추가

일단 CHT의 구조는 다음과 같다. CHT_PlayerCharacter ->(직업 클래스 여부에 따라) CHT_Paladin ->(전투 상태 여부에 따라) CHT_Paladin_General -> 일반 상태에 다른 분류 -> 애니메이션이 담긴 PSD의 순서로 Chooser계층을 구성하였다. 이제 팀원이 작성한 T3 CharacterBase로 BP를 만들고 ABP를 만들어 BP에 등록해 주고 ABP의 이벤트 그래프에서 다음처럼 필요한 변수들을 등록해 준다.

 

필요한 변수들로는 BP_T3 CharacterBase의 이동 경로를 계산하는 CharacterTrajectory 컴포넌트와 CharacterMovement 컴포넌트 그리고 우리가 추가한 T3 PlayerInputState의 값을 각각 변수로 승격하여 Set 해준다. 이제 준비는 끝났다. 해당 값을 토대로 ChooserTable을 구성해 보자.

 

ChooserTable과 ABP 세팅

ChooserTable의 기본 세팅에서 파라미터를 구조체로 선택한 후 우리가 작성한 T3 PlayerInputState로 변경해 준다.

 

이제 미리 작성한 PlayerInputState의 변수들을 이용해서 대략적인 Chooser 메커니즘을 구성해 주면 된다.

직업 종류 구분은 일단 패스
전투 상태 유무

 

해당 변수들은 기본적으로 false로 되어있고 아직 해당 로직은 작성하지 않았으니 ABP에 연결하는 작업만 시작해 보도록 하자 MotionMatching의 OnUpdate에 새로운 함수를 바인딩해서 다음과 같이 작성해 주고 AnimGraph에서는 다음처럼 작업해 준다.

 

이제 각 변수들을 각각 상황에 맞게 값이 변화하도록 코드를 작성해주어야 한다.

 

각 변수들의 로직을 작성하자

일단 내가 작성한 T3 PlayerInputState에서의 변수들은 다음과 같이 구성되어 있다.

 

bWantsToMove부터 작업을 해보자 해당 변수 값은 플레이어가 정지해 있는 상태에서 인풋을 주었을 때 true가 되어야 하는 변수이다. bIsMoving은 움직이고 있는 상태일 때에만 true가 되어야 하므로 성질의 차이가 있다 이 변수는 어떻게 로직을 짜야 알맞은 애니메이션을 선택하게 할 수 있을까?

 

일단 bWantsToMove와 bIsMoving의 경우에는 모두 Tick에서 갱신을 해주기로 결정했다. Move에서 호출하면 되지 않을까 싶긴 하지만 Move는 인풋이 종료되는 시점에 IsMoving의 갱신을 못하고 별도로 MoveEnd라는 함수를 만들어서 인풋 종료시에 호출되게 하면 되겠지만 일단은 간편하게 Tick에다 구현하기로 하자.

void AT3CharacterBase::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	float CurrentAcceleration = GetCharacterMovement()->GetCurrentAcceleration().Size();
	PlayerInputState.bWantsToMove = CurrentAcceleration > KINDA_SMALL_NUMBER;
	
	float CurrentGroundSpeed = GetVelocity().Size2D();
	const float MoveThreshold = 3.0f;
	
	PlayerInputState.bIsMoving = CurrentGroundSpeed > (MoveThreshold);
	
	PlayerInputState.CurrentSpeed = CurrentGroundSpeed;
}

 

요는 이렇다 플레이어가 움직이기 시작하는 것을 감지하는 bWantsToMove는 캐릭터의 가속도가 0보다 크면(KINDA_SMALL_NUMBER는 0에 한 없이 가까운 양의 실수) true 아니면 false가 되게끔 매 Tick마다 검사하게 되며 캐릭터가 움직이고 있음을 감지하는 bIsMoving은 캐릭터의 속도를 가져와 해당 속도가 임계값(MoveThreshold)보다 크면 true 아니면 false가 되게 만들었다. 그리고 CurrentGroundSpeed 값을 CurrentSpeed에 넣어 나중에 사용할 캐릭터의 현재 속도 또한 가져가게끔 해두었다. 이런식으로 Chooser에 사용할 변수들을 필요에 따라 작성해 주면서 좀더 세밀한 선택이 가능하게끔 구성해 나가면 훌륭한 Chooser기반 Motion Matching 애니메이션 구축이 가능할 것이다.