언리얼 엔진 5/개발 일지

[UE5] 블루아카이브 TPS게임 개발일지 (6) - TPS 애니메이션 (Layered Blend, Blend Space)

ciel45 2023. 12. 15. 18:51

지금까지 조준 기능이 카메라 줌 인 부분만 구현되어있었다.

 

이번에는 조준한 채로 걸어다닐 수 있고, 카메라를 좌우로 돌리면 그 방향을 조준하도록 하였다.

 

 

 

먼저 조준한 채로 걸어다니는 애니메이션을 만들었다.

 

언리얼 엔진 마켓플레이스에는 에픽게임즈에서 만든 여러 애니메이션이 들어있고, 무료인 AnimStarterPack 에셋이 있다. 그 안에는 라이플을 조준한 채로 앞, 뒤, 좌, 우 4방향으로 걷는 애니메이션이 있다.

현 캐릭터와 달리 권총이 아닌 라이플을 들고있는 애니메이션이기는 하지만, 필요한 것은 하체 애니메이션 뿐이므로 그것을 사용하기로 했다.

 

IK Retargeter의 Export Selected Animations를 통해 애니메이션을 생성했다.

 

 

 

이제 플레이어의 이동 방향에 따라 4방향 애니메이션이 적절히 섞여서 재생되어야 한다.

이를 위해 블렌드 스페이스를 이용하였다.

 

 

 

https://docs.unrealengine.com/5.3/ko/blend-spaces-in-unreal-engine/

 

블렌드 스페이스

블렌드 스페이스는 원하는 수의 애니메이션을 구성하여 여러 입력값에 따라 서로 블렌딩할 수 있는 그래프입니다.

docs.unrealengine.com

 

블렌드 스페이스를 이용하면 변수의 값에 따라 애니메이션이 자연스럽게 블렌딩 되도록 할 수 있다.

 

 

 

 

캐릭터의 방향과 속도에 따른 애니메이션이 재생된다.

 

 

 

 

이제 이 블렌드 스페이스를 블루프린트 애니메이션에서 사용할 수 있다.

 

그 전에, 블렌드 스페이스에서 사용할 변수도 만들어줄 필요가 있다.

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/KannaAnimInstance.h"
#include "Character/KannaCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"

void UKannaAnimInstance::NativeInitializeAnimation()
{
	Super::NativeInitializeAnimation();

	KannaCharacter = Cast<AKannaCharacter>(TryGetPawnOwner());
	if (KannaCharacter)
	{
		KannaCharacterMovement = KannaCharacter->GetCharacterMovement();
	}
}

void UKannaAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
	Super::NativeUpdateAnimation(DeltaTime);

	if (KannaCharacterMovement)
	{
		GroundSpeed = UKismetMathLibrary::VSizeXY(KannaCharacterMovement->Velocity);
		IsFalling = KannaCharacterMovement->IsFalling();
		CharacterState = KannaCharacter->GetCharacterState();
		ActionState = KannaCharacter->GetActionState();

		CharacterSpeed = KannaCharacter->GetVelocity().Length();
		CharacterDirection = CalculateDirection(KannaCharacter->GetVelocity(), KannaCharacter->GetActorRotation());
	}
}

애니메이션 블루프린트의 부모 클래스 C++ 코드이다. 매 틱마다 CharacterSpeed는 GetVelocity().Length를 통해 구하고, CharacterDirection은 CalculateDirection 함수를 통해 구한다.

 

 

 

이제 블루프린트에서의 블렌드 스페이스 노드의 인풋으로 이 두 변수를 꽂아주면 된다.

BS_Aiming이 방금 만든 블렌드 스페이스이다.

 

 

 

Layered blend per bone은 상,하체 애니메이션을 분리해주는 노드이다. 아래의 동영상을 많이 참고하였다.

 

https://www.youtube.com/watch?v=kVDFc419fuI&t=105s&pp=ygUPdWU1IGJsZW5kIHNwYWNl

 

 

노드를 보면 블렌드 스페이스의 아웃풋에 해당하는 애니메이션이 Base Pose로, 기존 권총 조준 애니메이션이 Blend Poses 0로 들어간 것을 볼 수 있다.

 

상,하체 분리를 위한 노드의 세팅은 예상 외로 굉장히 단순했다. Layered blend per bone 노드의 디테일 패널을 보면 Layer Setup 메뉴가 있고, 드롭다운을 확장하다보면 Branch Filters가 있다. 여기에 분리하고싶은 기준 bone의 이름을 적어주면 된다. 

 

이렇게 하면 해당 bone과 그 모든 자식 bone들은 전부 Base Pose가 아닌 Blend Poses 0에 들어간 애니메이션의 움직임을 따르게 된다.

현재 캐릭터의에서 상체의 루트 역할을 하는 bone은 Spine이다. 따라서 Spine을 적어주었다.
위에서 상체의 시작인 Spine만 Branch Filter에 넣어줌으로써, 하체는 블렌드 스페이스의 애니메이션을 사용하도록 할 수 있다.

 

 

 

 

적용 결과:

아직 고칠 부분이 많지만, 최소한 상하체 애니메이션 분리가 잘 된 것은 확인할 수 있다.

 

 

 

이제 조준한 채로 시점을 좌우로 돌리면 캐릭터도 회전하도록 할 차례이다.

우선 조준 중에는 방향키 입력에 의해 캐릭터가 멋대로 회전하지 않도록 해야한다.

 

 

이를 위해서는 Character Movement 컴포넌트의 Orient Rotation to Movement를 off로 해야한다.

상응하는 코드는 다음과 같다.

GetCharacterMovement()->bOrientRotationToMovement = false;

 

 

또한 조준 중에는 이동속도가 감소하도록 하는 것이 좋을 것 같다. 

다음은 이를 반영하는 코드이다.

void AKannaCharacter::Aim()
{
	if(CharacterState == ECharacterState::ECS_Unarmed) return; //비무장 상태일 시 Action State 바꾸지 않음, 카메라만 줌 인

	ActionState = EActionState::EAS_Aiming;

	GetCharacterMovement()->MaxWalkSpeed = 200.f; // 조준 중에는 이동속도 감소
	GetCharacterMovement()->bOrientRotationToMovement = false; // 캐릭터가 방향키에 따라 회전하지 않음 (조준 방향 유지)
}

void AKannaCharacter::ReleaseAim()
{
	ActionState = EActionState::EAS_Neutral;

	SetNeutralStateSpeed();
	GetCharacterMovement()->bOrientRotationToMovement = true;
}

 

 

 

캐릭터의 회전은 블루프린트 노드로 구현하였고, 다음과 같다.

조준중일 때는 매 틱마다 Set Actor Rotation을 통해 캐릭터를 회전시킨다. Get Control Rotation의 Control이란 시점을 말하는 것이다.

 

하지만 이렇게 하니까 회전이 부드럽지 못한 문제가 있었다.

 

 

 

이를 해결하기 위해서 다음 동영상을 참고하였다.

https://www.youtube.com/watch?v=EGnuIAuKMZA&ab_channel=JonatanIsaksson

 

 

 

 

회전을 부드럽게 만들어주는 노드

 

 

요점은 선형 보간(Lerp)을 이용하는 것이다.

조준을 하면 우선 캐릭터의 현재 Rotator를 Starting Rotation에 저장한다.

그리고 뒤의 Lerp를 보면 A(시작점)에 해당 Rotator가 들어가있는 것을 볼 수 있다.

Roll, Pitch는 바꾸지 않을 것이므로 Starting Rotation의 값으로 집어넣는다.

 

 

중요한 것은 Yaw와 Alpha이다.

Lerp의 Yaw 인풋으로 들어가는 것은 Control Rotation의 Yaw인 것을 볼 수 있다. Control은 시점을 의미하는 것으로, 해당 Yaw값이 곧 캐릭터가 바라보게 하고 싶은 값이다. 따라서 Lerp 노드의 도착점에 해당하는 B의 Yaw에 연결해준다.

 

 

Alpha는 A에서 B 사이의 어떤 값을 사용할 지 결정해주는 값이다. Alpha 값을 0에서 천천히 1로 올려줌에 따라, 회전이 시작점에서 도착점까지 부드럽게 이어지도록 할 수 있는 것이다.

 

 

이 Alpha 값을 설정해 주기 위해 Timeline이 필요한 것이다. Timeline 내부는 다음과 같다.

0.1초의 시간동안 0에서 1로 증가하는 Float 값을 출력해준다.

 

마지막엔 이 모든 과정의 output인 Lerp노드의 Rotator를 Set Actor Rotation에 연결해주면 된다.

참고로, Lerp의 Shortest Path는 꼭 true로 해야한다. 그러지 않으면 캐릭터가 360도로 빙글빙글 도는 불상사가 일어난다.

 

 

 

 

실행 결과

 

 

 

칸나가 위나 아래를 쳐다볼 때 상체의 각도가 변하는 것은 Aim Offset을 이용하여 다음에 구현할 것이다.