KoreanFoodie's Study

이득우의 언리얼 C++ 5 : 폰의 제작과 조작 본문

Game Dev/Unreal C++ : Tutorial

이득우의 언리얼 C++ 5 : 폰의 제작과 조작

GoldGiver 2022. 2. 27. 16:30

이득우님의 "이득우의 언리얼 C++ 게임 개발의 정석" 책을 따라가며 실습한 내용을 정리한 포스팅입니다. 실습에 필요한 자료들은 이 링크에서, 제가 작업한 예제 소스 완성본은 여기에서 찾아보실 수 있습니다. (저는 언리얼 4.27.2 버전 기준으로 작업하였습니다)

폰의 구성 요소

폰은 플레이어가 조작할 수 있는 액터이다. 인간형 폰을 제작할 때는 다음과 같은 요소를 고려해야 한다.

  • 시각적 요소 : 인간형 폰이 되려면 애니메이션 기능이 필요하다. 애니메이션을 재생하도록 리깅(Rigging) 데이터를 추가한 메시를 스켈레탈 메시라고 한다(스켈레탈 메시 컴포넌트가 관리).
  • 충돌 요소 : 인간형의 경우 캡슐 컴포넌트를 사용한다.
  • 움직임 요소 : 폰 무브먼트 컴포넌트(FloatingPawnMovement 또는 CharacterMovement)를 제공한다.
  • 네비게이션(Navigation) : 폰은 언리얼 엔진의 네비게이션 시스템과 연동돼 있어서 목적지를 알려주면 스스로 목적지까지 이동하는 길 찾기 기능을 갖고 있다.
  • 카메라 출력 : 폰에 카메라 컴포넌트를 부착하면, 플레이어 컨트롤러가 폰에 빙의할 때 자동으로 폰에 부착된 카메라의 상을 플레이어의 화면으로 전송한다.

 

캐릭터 에셋을 이용해 폰을 제작해 보자. 사용할 컴포넌트는 다음과 같다 :

  • Capsule : 충돌 컴포넌트. 루트 컴포넌트로 설정한다.
  • SkeletalMesh : 캐릭터 에셋을 보여주는 동시에 애니메이션을 담당한다. 언리얼 엔진에서 액터의 기준 위치는 정중앙이고, 캐릭터 에셋은 주로 발바닥에 기준을 잡으므로 Z축으로 절반 높이만큼 내려줘야 한다. 바라보는 방향도 맞춰준다(Yaw).
  • FloatingPawnMovement : 플레이어의 입력에 따라 캐릭터를 움직이도록 설정한다. 이 무브먼트는 중력을 고려하지 않은 간단한 움직임을 구현할 수 있다.
  • SpringArm : 스프링암은 삼인칭 시점으로 카메라 구도를 편리하게 설정할 수 있는 부가 컴포넌트다.
  • Camera : 폰에 카메라 컴포넌트를 부착하면 카메라가 바라보는 화면이 플레이어의 화면으로 전송된다. 카메라를 스프링암의 자식으로 설정하고 트랜스폼을 초기화하면 카메라는 자동으로 스프링암의 끝에 걸린다.

 

폰의 컴포넌트를 설정한 코드는 다음과 같다.

ABPawn.h

#include "GameFramework/FloatingPawnMovement.h"

...

class ARENABATTLE_API AABPawn : public APawn
{

...

public:
	UPROPERTY(VisibleAnywhere, Category=Collision)
	UCapsuleComponent* Capsule;

	UPROPERTY(VisibleAnywhere, Category=Visual)
	USkeletalMeshComponent* Mesh;

	UPROPERTY(VisibleAnywhere, Category=Movement)
	UFloatingPawnMovement* Movement;

	UPROPERTY(VisibleAnywhere, Category=Camera)
	USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere, Category=Camera)
	UCameraComponent* Camera;   
}

 

ABPawn.cpp

Capsule = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CAPSULE"));
Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MESH"));
Movement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MOVEMENT"));
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SPRINGARM"));
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("CAMERA"));

RootComponent = Capsule;
Mesh->SetupAttachment(Capsule);
SpringArm->SetupAttachment(Capsule);
Camera->SetupAttachment(SpringArm);

Capsule->SetCapsuleHalfHeight(88.0f);
Capsule->SetCapsuleRadius(34.0f);
Mesh->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -88.0f), FRotator(0.0f, -90.0f, 0.0f));
SpringArm->TargetArmLength = 400.0f;
SpringArm->SetRelativeRotation(FRotator(-15.0f, 0.0f, 0.0f));

static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_CARDBOARD(TEXT("/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard"));
if (SK_CARDBOARD.Succeeded())
{
    Mesh->SetSkeletalMesh(SK_CARDBOARD.Object);
}

Mesh의 위치를 -88.0f 만큼, 회전(Yaw)을 -90.0f 만큼 조정해 주었다. SpringArm 은 -15.0f 만큼 조정해 캐릭터의 어깨 너머의 배경이 보이도록 설정했다.

위와 같은 설정을 하면 카드보드 액터가 등장한다.

이미 애니메이션을 적용해서 달리고 있음

 

 

폰의 조작

이제 폰을 움직일 수 있도록 입력에 대한 처리 로직과 무브먼트를 설정해 보자. 프로젝트 세팅으로 들어가 입력 매핑을 추가해 보자.

액션 매핑은 일회성 값을, 축 매핑은 지속적인 입력 값을 측정한다

바인딩을 설정한 후에 InputComponent 의 BindAxis 와 BindAction 이라는 함수를 이용해 폰의 움직임을 연결시켜 준다.

 

ABPawn.h

UCLASS()
class ARENABATTLE_API AABPawn : public APawn
{
    GENERATED_BODY()

...

private:
	void UpDown(float NewAxisValue);
	void LeftRight(float NewAxisValue);

...

 

ABPawn.cpp

// Called to bind functionality to input
void AABPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AABPawn::UpDown);
	PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AABPawn::LeftRight);
}

void AABPawn::UpDown(float NewAxisValue) {
	//ABLOG(Warning, TEXT("%f"), NewAxisValue);
	AddMovementInput(GetActorForwardVector(), NewAxisValue);
}

void AABPawn::LeftRight(float NewAxisValue) {
	//ABLOG(Warning, TEXT("%f"), NewAxisValue);
	AddMovementInput(GetActorRightVector(), NewAxisValue);
}

먼저 UInputComponent 의 BindAxis 함수를 이용해 입력 바인딩을 연결하고, AddMovementInput 함수를 이용해 액터의 벡터 방향으로 입력값을 더해준다.

 

추가적으로, 플레이어 컨트롤러에게 UI 를 배제하고 게임에게만 입력을 전달하도록 명령을 내려 앞으로 편리하게 게임을 테스트해 보자.

 

ABPlayerController.h

protected:
    virtual void BeginPlay() override;

 

ABPlayerController.cpp

void AABPlayerController::BeginPlay()
{
	Super::BeginPlay();

	FInputModeGameOnly InputMode;
	SetInputMode(InputMode);
}

 

 

애니메이션의 설정

애니메이션 애셋을 불러들이는 코드는 다음과 같이 설정할 수 있다.

 

ABPawn.cpp

Mesh->SetAnimationMode(EAnimationMode::AnimationSingleNode);

UAnimationAsset* AnimAsset = LoadObject<UAnimationAsset>(nullptr, TEXT("/Game/Book/Animations/WarriorRun.WarriorRun"));
if (AnimAsset != nullptr)
{
    Mesh->PlayAnimation(AnimAsset, true);
}

위의 코드는 EAnimationMode::AnimationSingleNode 로 보아, 메시가 단일 애니메이션을 사용하는 것을 알 수 있다. 하지만, 애니메이션 블루프린트(애님 그래프로 구성된)를 사용하면 상황에 따른 애니메이션 전환이 더 자유로워진다.

 

간이로 만든 AnimGraph

위의 애니메이션 블루프린트를 사용하는 코드는 다음과 같다.

 

ABPawn.cpp

Mesh->SetAnimationMode(EAnimationMode::AnimationBlueprint);

static ConstructorHelpers::FClassFinder<UAnimInstance> WARRIOR_ANIM(TEXT("/Game/Book/Animations/WarriorAnimBlueprint.WarriorAnimBlueprint_C"));
if (WARRIOR_ANIM.Succeeded())
{
    Mesh->SetAnimInstanceClass(WARRIOR_ANIM.Class);
}

 

Comments