KoreanFoodie's Study
이득우의 언리얼 C++ 5 : 폰의 제작과 조작 본문
이득우님의 "이득우의 언리얼 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 로 보아, 메시가 단일 애니메이션을 사용하는 것을 알 수 있다. 하지만, 애니메이션 블루프린트(애님 그래프로 구성된)를 사용하면 상황에 따른 애니메이션 전환이 더 자유로워진다.
위의 애니메이션 블루프린트를 사용하는 코드는 다음과 같다.
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);
}
'Game Dev > Unreal C++ : Tutorial' 카테고리의 다른 글
이득우의 언리얼 C++ 7 : 애니메이션 시스템의 설계 (0) | 2022.03.01 |
---|---|
이득우의 언리얼 C++ 6 : 캐릭터의 제작과 컨트롤 (0) | 2022.02.28 |
이득우의 언리얼 C++ 4 : 게임 모드(GameMode), 플레이어 컨트롤러(PlayerController) 제작 (1) | 2022.02.21 |
이득우의 언리얼 C++ 3 : 로그, 액터 움직이기, 액터 삭제 (0) | 2022.02.16 |
이득우의 언리얼 C++ 2 : 액터의 설계 (0) | 2022.02.12 |