Game Development/Devlog

[UE5] 애니메이션 추가, Character 클래스 생성 (TPS - 개발일지2)

Dlaiml 2024. 7. 22. 20:38

출시를 앞둔 프로젝트가 있어 개인 프로젝트에 시간을 많이 쓰지 못하고 있다. 

저번 포스팅 이후 애니메이션 추가, Character 클래스 생성까지만 진행하였다.

 

[UE5] 프로젝트 생성, 캐릭터 모델 import, Animation Retargeting (TPS 개발일지 - 1)

언리얼 엔진 5.4의 신규 기능에 대한 학습이 어느 정도 완료되어 언리얼 엔진 5.4에서 TPS(3인칭 슈팅 게임)을 만들어보려 한다. 간단하게 구현한 기능들이나 진행한 작업들을 일지 형태로 포스팅

dlaiml.tistory.com

 

Animation Retargeting

 

저번 포스팅에서 애니메이션 스타터팩, Paragon: Belica, Mixamo의 애니메이션을 Retargeting해서 가져왔었다. 

에픽게임즈에서 제공하는 프로젝트 중 Lyra라는 고퀄리티의 TPS 예시 게임이 있다. 가끔 변수명이나 여러 코드 컨벤션에 대해 궁금할 때나 전체적인 게임의 구조를 파악하고 싶을 때 마다 참고하던 프로젝트인데, 애니메이션 시스템도 매우 잘 되어있어서 Lyra의 애니메이션 또한 추가로 Retargeting하였다.

 

 

 

 

 


Troubleshooting - Retargeting Scale 문제

 

Belica를 Target Skeleton으로 Retargeting 하고 나서 IK Retargeter에서 보았을 때는 문제가 없으나 실제로 Animation을 Export하고 보면 눈, 입의 위치가 맞지 않는 문제가 생겼다.

 

두 스켈레톤의 Height 차이, Retarget 설정의 문제일 것이라고 생각하고 Animation, Skeleton, Animation Scaled .. 등등 Head, Neck Bone에서 모두 설정해보았고, IK Goal을 추가해보았으나 결과는 동일하였다.

 

이후 Bone을 하나하나 확인하면서 문제를 파악하였는데 Belica의 더 복잡한 Bone 구조와 초기 Scale 세팅이 문제였다.

 

Mixamo, UEFN Mannequin, UE4,5 Manny, Quinn 모두 Head는 Neck에서 이어지는 1~2개의 Bone으로 Retarget Chain이 설정되어있다. 하지만 Belica의 경우 faceAttach라는 Bone아래에 눈, 입술, 치아, 눈썹 등의 뼈가 모두 지정되어 있었다. 

 

 


Auto IK Retargeter에서는 물론 하나의 Skeleton에만 있는 Bone들을 Retargeting 할 수 없기 때문에 Head에서 이들이 제외되었다. 직접 IK Rig을 만들고 이들을 제외하고 리타게팅 해보아도 결과는 동일하였다.

 

눈, 입의 위치가 둘다 동시에 올라가면서 문제가 생긴것같아 Root로 올라가면서 하나씩 확인해본 결과 문제는 faceAttach Bone이었다.

 

Skeleton을 보면 Scale이 0.785146으로 설정되어 있는데, Retargeting이 끝나면 Retarget Chain에 속해있지 않는 faceAttach의 Scale이 1로 변하게 된다. 

 

 

해결 방법은 여러가지가 있겠지만 애니메이션 중 faceAttach와 그 Children을 사용하는 애니메이션이 없어 모든 LOD에서 해당 Bone과 하위 Children을 제거하였다. 

 

이후 정상적으로 애니메이션이 재생되었다.

 


이렇게 애니메이션 스타터팩, Mixamo, Paragon, Lyra의 애니메이션을 모두 리타게팅하여 준비가 완료되었다.

 

Character 클래스 생성

Movement Coponent를 기반으로 여러 애니메이션을 세팅하기 위해 ACharacter 클래스를 상속받는 캐릭터 클래스를 만들었다.

 

 Enhanced Input을 사용하여 Move, Look, Jump 등 기본 액션을 정의하였고 TPS 시점 구현을 위해 SpringArm, Camera를 추가하였다.

 

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "WarComposerCharacter.generated.h"

class UInputAction;
class UInputMappingContext;
struct FInputActionValue;

UCLASS()
class WARCOMPOSER_API AWarComposerCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	AWarComposerCharacter();

	virtual void Tick(float DeltaTime) override;
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
	virtual void BeginPlay() override;

protected:

	// Enhanced Input
	void Move(const FInputActionValue& Value);
	void Look(const FInputActionValue& Value);

private:
	UPROPERTY(VisibleAnywhere, Category = Camera, meta = (AllowPrivateAccess="true"))
	class USpringArmComponent* CameraBoom;

	UPROPERTY(VisibleAnywhere, Category = Camera, meta = (AllowPrivateAccess="true"))
	class UCameraComponent* FollowCamera;
	
private:
	// Enhanced Input
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputMappingContext* DefaultMappingContext;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess="true"))
	UInputAction* MoveAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputAction* LookAction;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputAction* JumpAction;

public:


};

 

TPS 게임에서 사용자에게 가장 익숙한 조작인 WASD로 캐릭터를 조종, 마우스를 상하좌우로 움직여 시점 변경, 카메라(정확히는 Controller)가 향하는 방향으로 캐릭터가 움직이도록 설정하였다.

 

AWarComposerCharacter::AWarComposerCharacter()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	bUseControllerRotationYaw = false;
	GetCharacterMovement()->bOrientRotationToMovement = true;

	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(GetMesh());
	CameraBoom->TargetArmLength = 400.f;
	CameraBoom->bUsePawnControlRotation = true;

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
	FollowCamera->bUsePawnControlRotation = false;

}

 

AnimInstance

마찬가지로 가장 기본적인 애니메이션 재생을 위해 AnimInstance를 상속받는 C++ 클래스를 만들었다. 참조하는 Character 클래스의 Movemen Component를 받아와서 Is Falling, Swimming, Walk Speed, Velocity 등 애니메이션 재생을 위해 필요한 변수들을 멤버로 지정하였다.

 

void UWarComposerAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);

	if (WarComposerCharacter == nullptr)
	{
		WarComposerCharacter = Cast<AWarComposerCharacter>(GetOwningActor());
		if (WarComposerCharacter == nullptr) 
		{
			return;
		}
	}

	FVector Velocity = WarComposerCharacter->GetVelocity();
	Velocity.Z = 0.f;
	Speed = Velocity.Size();

	UCharacterMovementComponent* CharacterMovement = WarComposerCharacter->GetCharacterMovement();
	if (CharacterMovement != nullptr)
	{
		bIsInAir = CharacterMovement->IsFalling();
		bIsAccelerating = CharacterMovement->GetCurrentAcceleration().Size() > 0.f ? true : false;
	}


}

 

이후 C++ AnimInstance를 부모로 하는 Animation Blueprint를 생성하였다.

저번에 모션 매칭으로 UE Game Animation Sample Project의 모든 애니메이션 중 "걷기", "달리기" 등 큰 카테고리로 묶어 Pose Search Database를 만들어봤는데 오히려 Channel 설정이 복잡해지고 Channel이 3~4개가 되면 사람이 이해하기 매우 어려운 방식으로 작동하기 때문에(0.2초 전의 방향 * weight 0.1 + 0.6초후의 속력 * weight 0.7 + ...) 디버깅도 매우 불편하였다. [하단 포스팅 참고]

 

[UE5] Motion Matching - 3 (Chooser, Custom Channel)

저번 포스팅에 이어 Chooser를 사용하여 Pose Search Databases를 상황에 맞게 교체하고 직접 Pose Search Schema에 Channel을 추가하여 더 자연스러운 모션매칭을 완성해보려 한다. [UE5] Motion Matching - 2 (Pose Sear

dlaiml.tistory.com

 

결국 에픽 게임즈의 프로젝트를 뜯어보니 어느정도 기본 Animation을 State Machine에서 하던 것처럼 분류하는 것이 필요하다는 것을 알게되었다. (달리기 시작, 공중, 착지 등) 

 

일단 이전에 하던 것처럼 가장 간단한 State Machine을 정의하고 앞서 AnimInstance의 멤버 변수로 정의한 공중 체공 여부(IsInAir)를 기준으로 Trasition Rule을 정하였다. 체공이 시작되면 JumpBeing, 애니메이션이 끝나면 Jump, 체공이 끝나면 JumpEnd, 애니메이션이 끝나면 OnGround State로 Transition한다.

 

 

State마다 가장 적합한 애니메이션을 찾아 세팅해주었는데, 최근에 본 UE5 Game Animation Sample Project에 비해 애니메이션 개수가 턱없이 적었다. Jump End State의 경우 마음에 드는 애니메이션이 없어 따로 Mixamo를 뒤져보았으나 Lyra에 더 좋은 애니메이션이 있어서 이후 Lyra의 애니메이션들을 추가로 Retargeting 하였다.

 

OnGround의 경우 Blend Space를 사용하여 속도, 방향을 기준으로 애니메이션을 배치하였다. (Skeleton 수정 이후 Weapon, Pistol Root Bone의 Scale을 조정하지 않은 상태에서 캡쳐한 사진이라 아직 총이 보인다)

 

 

Controller의 Rotation과 Actor의 Rotation의 Global Space에서의 차를 구하고 Yaw값을 사용하여 Direction을 정의하였다. -180 ~ 180의 degree값을 가지며 -180, 180은 뒤로 이동 / -90은 좌측으로 이동 / 0은 앞으로 이동 / 90은 우측으로 이동하는 애니메이션을 배치할 예정이다.

 

속도에 따라 대기 애니메이션, 걷기 애니메이션, 달리기 애니메이션이 재생되도록 하였다. (방향에 따른 다른 애니메이션은 자연스러운 애니메이션을 찾거나 또는 모션매칭+Lyra or UE 애니메이션 샘플로 새로 세팅할 예정이라 미정)

 

만든 Blend Space를 OnGround State의 Pose로 지정하고 Character의 Animation class를 위에서 작업한 Anim Instance 블루프린트로 설정까지 완료하였다.

 

 

액션게임처럼 매우 복잡한 애니메이션 시스템이 아닌 TPS 게임이라서 많은 채널의 모션 매칭으로 수 천개의 애니메이션 시퀀스를 사용하는것은 적절하지 않아보인다. 애니메이션은 충분히 많이 모았으니 기획에 따라 하나씩 구현하면서 필요한 애니메이션들을 정의하고 그에 따라 적절하게 애니메이션 시스템을 구성할 예정이다. 

 

Reference

[0] https://dev.epicgames.com/documentation/en-us/unreal-engine/enhanced-input-in-unreal-engine

 

[1] https://dlaiml.tistory.com/entry/UE5-Motion-Matching-2-Pose-Search-%EB%AA%A8%EC%85%98-%EB%A7%A4%EC%B9%AD

 

[UE5] Motion Matching - 2 (Pose Search, 모션 매칭)

저번 포스팅에 이어서 모션매칭을 이어 진행해보자. [UE5] Motion Matching - 1 (모션매칭, UE 5.4)언리얼 엔진에서 500개가 넘는 무료 애니메이션이 포함된 캐릭터를 포함하는 Unreal Engine Game Animation Sample

dlaiml.tistory.com

 

[2] Unreal Engine ThirdPerson Template Code