5장 폰의 제작과 조작
5.1 폰의 구성 요소
폰은 움직이는 액터에 조종당하는 기능이 추가된 액터로, 인간형 폰을 제작하기 위해 다음과 같은 요소를 고려해야 한다.
- 시각적 요소: 폰의 애니메이션을 재생하도록 리깅(rigging) 데이터를 추가한 메시를 스켈레탈 메시라고 한다.
- 충돌 요소: 충돌을 담당할 충돌 컴포넌트, 가령 캡슐 컴포넌트를 사용한다.
- 움직임 요소: 폰의 움직임을 위해 플레이어 입력에 따라 반응하는 폰 무브먼트 컴포넌트를 제공한다.
- 내비게이션: 폰은 내비게이션 시스템과 연동되어 있어서 스스로 목적지까지 이동하는 길 찾기 기능을 갖고 있다.
- 카메라 출력: 폰에 카메라 컴포넌트를 부착해 자동으로 플레이어 화면으로 전송한다.
스켈레탈 메시 애셋을 사용해 인간형 폰을 만들어본다. 언리얼 엔진 런처의 마켓플레이스에서 InfinityBlade: Warriors 패키지를 검색하여 다운로드하고 프로젝트에 추가한다.
C++ 코드를 사용해 폰을 제작한다. 우리가 사용할 컴포넌트는 다음과 같다.
- Capsule: 폰의 움직임을 담당하는 충돌 컴포넌트이다. 충돌 영역은 캐릭터 메시가 딱 들어갈 만큼의 크기로 설정한다.
- SkeletalMesh: 캐릭터 애셋을 보여주고 애니메이션도 담당한다.
- FloatingPawnMovement: 플레이어의 입력에 따라 중력을 고려하지 않고 캐릭터가 움직이도록 설정한다.
- SpringArm: 3인칭 시점으로 카메라 구도를 설정한다.
- Camera: 폰에 부착하여 카메라가 바라보는 게임 세계의 화면을 플레이어의 화면으로 전송한다.
/* Chapter 05 ABPawn.h */
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "ABPawn.generated.h"
UCLASS()
class ARENABATTLE_API AABPawn : public APawn
{
...
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual void PostInitializeComponents() override;
virtual void PossessedBy(AController* NewController) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
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;
};
/* Chapter 05 ABPawn.cpp */
#include "ABPawn.h"
// Sets default values
AABPawn::AABPawn()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// 컴포넌트를 생성합니다.
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);
// 캐릭터의 절반 높이를 88, 몸둘레를 34로 설정합니다.
Capsule->SetCapsuleHalfHeight(88.0f);
Capsule->SetCapsuleRadius(34.0f);
// 스켈레탈 메시 컴포넌트를 z축으로 -90도만큼 회전시키고 절반 높이만큼 내립니다.
Mesh->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -88.0f), FRotator(0.0f, -90.0f, 0.0f));
// 카메라 지지대의 길이를 4미터, y축으로 -15도만큼 회전시킵니다.
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);
}
}
...
5.2 폰의 조작
■ 5.2.1 키보드 입력
폰에 입력에 대한 처리 로직과 폰 무브먼트를 설정해본다. 프로젝트 세팅 > 엔진 > 입력의 바인딩 설정에서 액션 매핑(action mapping)과 축 매핑(axis mapping) 두 가지 항목을 볼 수 있다.
- 액션 매핑: 조이스틱 버튼(WASD, ABXY)의 신호를 설정한다.
- 축 매핑: 조이스틱 레버 신호를 설정한다. 레버가 중간 위치에 있으면 0, 가장 안쪽과 바깥쪽에 있으면 스케일 값에 따라 1 혹은 -1의 값을 게임 로직에 전달한다.
축 매핑 오른쪽에 위치한 +버튼을 눌러 상하 축 신호를 담당할 UpDown과 좌우 축 신호를 담당한 LeftRight 입력 값을 새롭게 생성하고 각각 W와 S, A와 D키를 배정한다.
입력 설정이 완료되면 이를 사용하도록 코드를 작성한다. 폰의 SetupInputComponent 함수에 InputComponent 언리얼 오브젝트를 사용하여 입력 설정의 이름과 이를 연동할 언리얼 오브젝트 인스턴스의 함수 포인터를 지정한다.
/* Chapter 05 ABPawn.h */
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "ABPawn.generated.h"
UCLASS()
class ARENABATTLE_API AABPawn : public APawn
{
...
private:
void UpDown(float NewAxisValue);
void LeftRight(float NewAxisValue);
};
입력 값이 확인되면 폰 무브먼트 컴포넌트의 AddMovementInput 함수로 입력 값을 폰 움직임으로 활용한다. 이 함수는 -1부터 1 사이의 입력 값을 폰 무브먼트 컴포넌트에 전달해서 폰을 움직이게 한다. 이 함수에는 이동할 방향으로 월드 좌표계를 기준으로 하는 방향 벡터 데이터를 전달해야 한다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABPawn.h"
...
// Called to bind functionality to input
void AABPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// BindAxis 함수에 함수 포인터를 지정합니다.
PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AABPawn::UpDown);
PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AABPawn::LeftRight);
}
void AABPawn::UpDown(float NewAxisValue) {
// 방향 벡터 GetActorForwardVector 함수를 WorldDirection 인자에 전달합니다.
AddMovementInput(GetActorForwardVector(), NewAxisValue);
// ABLOG(Warning, TEXT("%f"), NewAxisValue);
}
void AABPawn::LeftRight(float NewAxisValue) {
// 방향 벡터 GetActorRightVector 함수를 WorldDirection 인자에 전달합니다.
AddMovementInput(GetActorRightVector(), NewAxisValue);
// ABLOG(Warning, TEXT("%f"), NewAxisValue);
}
■ 5.2.2 뷰포트 입력
플레이 버튼을 눌러 콘텐츠를 테스트할 때마다 뷰포트를 클릭하지 않고 입력 신호를 게임에게 전달하도록 하면 편리하게 게임을 테스트할 수 있다.
/* Chapter 05 ABPlayerController.h */
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/PlayerController.h"
#include "ABPlayerController.generated.h"
UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
...
};
/* Chapter 05 ABPlayerController.CPP */
#include "ABPlayerController.h"
...
void AABPlayerController::BeginPlay() {
Super::BeginPlay();
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
}
5.3 애니메이션의 설정
콘텐츠 브라우저에서 애니메이션 애셋이 담길 Animations 폴더를 생성하고, 임포트 버튼을 눌러 Resource > Chapter5 폴더에 있는 FBX 파일들을 선택한다. 애니메이션 임포트 설정 창에서 스켈레톤 애셋인 SK_Mannequin_Skeleton을 선택한다. 그리고 임포트 버튼을 눌러 모든 애니메이션을 추가한다.
애니메이션 블루프린트(animation blueprint)는 언리얼 엔진에서 애니메이션 시스템을 시각적 도구로 설계하도록 하는 시스템이다. 이 애니메이션 시스템은 애님 인스턴스(anim instance)라는 클래스로 관리한다.
애니메이션 블루프린트로 달리기 애니메이션 애셋을 캐릭터의 기본 애니메이션으로 지정한다. 콘텐츠 브라우저에서 애니메이션 폴더를 선택하고 추가 > 애니메이션 > 애니메이션 블루프린트 메뉴를 선택한다. 그리고 캐릭터 스켈레톤 애셋으로 SK_Mannequin_Skeleton을 선택한다.
애셋의 이름을 WarriorAnimBlueprint라고 지어주고 더블 클릭해 연다. 애님 그래프(anim graph) 작업 환경에서 애셋 브라우저 윈도우 목록에 위치한 WarriorRun을 끌어다 놓는다. WarriorRun 재생 노드의 핀을 최종 애니메이션 노드에 연결하고 컴파일하면 애니메이션이 재생된다.
스켈레탈 메시 컴포넌트가 이 이 애니메이션 블루프린트를 실행시키려면 클래스 정보를 애님 인스턴스 속성에 지정해야 한다. 애니메이션 블루프린트 애셋의 경로를 레퍼런스 복사하고 _C를 추가로 붙여 클래스 정보를 가져오는 경로를 생성한다. 그리고 이를 스켈레탈 메시 컴포넌트의 애니메이션 블루프린트 클래스에 등록한다.
/* Chapter 05 ABPawn.cpp */
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABPawn.h"
// Sets default values
AABPawn::AABPawn()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
...
Mesh->SetAnimationMode(EAnimationMode::AnimationBlueprint);
static ConstructorHelpers::FClassFinder<UAnimInstance> WARRIOR_ANIM(TEXT("/Game/Animations/WarriorAnimBlueprint.WarriorAnimBlueprint_C"));
if (WARRIOR_ANIM.Succeeded()) {
Mesh->SetAnimInstanceClass(WARRIOR_ANIM.Class);
}
}
...
'Game Engines > 언리얼 엔진' 카테고리의 다른 글
언리얼 C++ 게임 개발의 정석 | Chapter 04 게임플레이 프레임워크 (0) | 2022.07.16 |
---|---|
언리얼 C++ 게임 개발의 정석 | Chapter 03 움직이는 액터의 제작 (0) | 2022.07.03 |
언리얼 C++ 게임 개발의 정석 | Chapter 02 액터의 설계 (0) | 2022.03.28 |
댓글