그냥 게임개발자

[UE5] - 2장 PlayerCharacter 만들기(C++) 본문

UE5_Tutorial1

[UE5] - 2장 PlayerCharacter 만들기(C++)

sudoju 2023. 9. 21. 15:12

https://sudoju.tistory.com/53에서 Visual Studio 연동이랑 C++ 클래스를 만들어보았다.

 

[UE5] - 1장 Visual Studio 연동 및 오류 해결, C++ 클래스 생성

언리얼 엔진 5.3 버전을 설치한 후 UE5 문서에 언리얼 엔진용 Visual Studio 구성을 살펴보았다. 여기서 5.3은 무조건 VS2022버전이어야 한다. VS 2019버전에선 .Net 6.0을 지원하지 않아서라고 한다. 사실 나

sudoju.tistory.com

 

본 글은 https://dev.epicgames.com/community/learning/tutorials/RBVX/unreal-engine-ue5-beginners-c-tutorial에서 보고 쓴 글이니 참고 바랍니다.

 

 

이제 본격적으로 C++로 게임을 만들어보자.

 

필자는 유니티로 게임을 개발하고 있다.

하지만 언리얼도 공부를 해봐야겠다 싶어서 이제부터 시작하려고 한다.

꾸준히 올릴 테니 이 글을 읽으시는 분들도 포기하지 않았으면 좋겠다.

근데 문법은 알아서 공부하자ㅇㅇ(언리얼은 유니티처럼 정말 정보가 많이 없다....)

 

일단 필자는 처음 공부 할 때 확실하게 내가 작성한 코드가 내 눈에 결과물이 만족스러워야 공부할 맛도 난다.

그래서 PlayerCharacter를 만들어 볼 것이다.

 

일단 먼저 알아야 할 지식은 Actor, Pawn, Character를 알아야 한다.

 

Actor

레벨에 배치할 수 있는 오브젝트
Actor는 이동, 회전, 스케일과 같은 3D 트랜스폼을 지원하는 범용 클래스
액터는 C++ or BP(BluePrint)를 통해 생성(스폰) 및 소멸이 가능하다.
C++에서 AActor는 모든 액터의 베이스 클래스다.

Pawn

플레이어나 AI가 제어할 수 있는 모든 Actor의 베이스 클래스
즉 C++ or BP를 통해 오브젝트를 움직일 수 있는 클래스다.

Character

캐릭터는 원래부터 약간의 기본적인 이족보행 운동 기능을 가지고 있는 Pawn이다.
즉 Pawn에서 확장된 것이 Character이다.

 

이제 C++ 캐릭터 클래스를 만들어보자

여기서 보면 아까 설명한 클래스들이 나온다.

그 마우스 휠을 아래로 내리다 보면 여러 가지 다른 클래스도 나온다.

이것은 나중에 차차 공부해 보자 일단 우리가 필요한 건 Character클래스이기 때문에 Character클래스를 선택하고 C++ 클래스를 생성해 보자.

 

여기서 우리는 코드를 효율적으로 관리하기 위해 Path맨뒤에 Player라고 적어두고 생성하자 Name은 아무렇게나 설정해도 된다.

이렇게 하고 나면 알아서 클래스를 생성하는데 Player라는 문자를 붙여서 생성하는 이유는

이렇게 Player라는 폴더가 생겨서 그 안에 Cpp파일과 H파일이 같이 생성이 되기 때문에 나중에 효율적으로 관리하기 위해서는 이게 꼭 필요하다고 한다.

 

이렇게 생성하게 되면 무조건 오류가 난다.

유는

이렇게 cpp 파일을 열어보면 #include "Player/PlayerCharacter.h"라고 쓰여있는데, 이 헤더파일을 찾지 못해서 오류가 나는 것인데, 지정해 주면 된다.

필자의 프로젝트 이름은 FirstProject이기 때문에 FirstProject를 앞에 써줘야 어디 있는지 찾을 수 있다.

그리고 다시 빌드를 하면 오류가 사라진다.

그러면 PlayerCharacter 클래가 제대로 생성이 된 걸 확인할 수 있다.

이제 여기서 이  클래스를 블루프린트로 만들어서 보기 쉽게 만들자.

오른쪽 버튼을 눌러 블루프린트로도 만들어보자.

이렇게 생성이 된 걸 볼 수가 있는데 필자는 폴더를 만들어서 정리를 했다.

폴더를 만드는 방법은 콘텐츠 브라우저에서 오른쪽 버튼을 눌러 폴더를 생성하면 된다.

Player 폴더를 만들어서 BP_PlayerCharacter를 넣어줘서 보기 좋게 관리해 주자.

BP_PlayerCharacter를 만든 것을 확인해 보자.

이 블루프린트를 더블클릭하게 되면 처음에는

이렇게 창이 뜬다.

오른쪽 상단에 보면 제대로 연결이 된 것을 확인할 수 있다 부모 클래스가 PlayerCharacter로 되어있어야 한다.

여기서 블루프린트를 자세히 보려면 

이렇게 파란색 글씨로 되어 있는 것을 클릭하자.

이런 창이 나오는데 여러분들은 카메라가 없을 것이다.

왜냐하면 필자는 이미 만들어 놓은 것을 기록하는 것이기 때문에 카메라가 있을 것이다.

 

이제 우리는 코드를 작성하기 전 라이브 코딩을 끄고 시작할 거다.

우리는 라이브코딩으로 컴파일하는 것이 아닌 vs에서 빌드를 할 것이기 때문이다.

라이브 코딩 끄는 것은 https://sudoju.tistory.com/53에서 추가했다.

 

PlayerCharacter.h파일에서 카메라 컴포넌트를 정의해 주자.

처음 헤더파일은 이렇게 정의가 되어 있을 것이다.

하나하나 다 파보자.

CoreMinimal.h

언리얼 엔진에서 오브젝트가 동작할 수 있는 최소 기능만 선언된 헤더 파일이다.
FString, FName, TArray 등을 포함한 최소 연산, 타입 등이 있다.

generated.h

언리얼 엔진은 컴파일하기 전 언리얼 헤더 툴이라는 도구를 사용하여 클래스를 분석하고 실행 환경에 필요한 부가 정보를 별도의 파일에 생성한다.
이 자동생성되는 부가파일이 generated.h파일이다.
코드를 작성할 때 존재하지 않지만 컴파일 과정에서는 필연적으로 발생하기에 반드시 선언해야 한다.

프로젝트이름_API

외부 모듈에 공개 여부
윈도우 DLL 시스템은 DLL 내 클래스 정보를 외부에 공개할지 결정하는 _declspec라는 키워드를 제공한다.
이 키워드를 사용하려면 (모듈명_API)라는 키워드를 클래스 선언 앞에 추가해야 한다.
이 키워드가 없으면 다른 모듈에서 해당 객체에 접근할 수 없다.

UCLASS

UClass 매크로는 UObject에게 자신의 언리얼에서 기반으로 삼은 유형에 대해 설명해 주는 UClass 로의 레퍼런스를 넘겨준다.
각 UClass는 Class Default Object, 줄여서 CDO라 불리는 오브젝트를 하나 유지한다.

언리얼 오브젝트에 대한 모든 정보를 기록하고 있으며, UCLASS 매크로가 선언이 되어야 이 스크립트에 접근해 정보를 찾아 열람하고 사용할 수 있음

GENERATED_BODY()

GENERATED_BODY()는 인수를 받지 않으나, 클래스가 엔진에 필요한 인프라 스트럭처를 지원하도록 구성한다.
모든 UCLASS에 필수이며 , Super:: 또는 ThisClass::와 같은 유용한 typedef를 사용할 수 있게 해 준다.

APlayterCharacter()

생성자

BeginPlay()

게임 시작했을 때 처음으로 불려지는 함수

Tick()

매 프레임마다 불려지는 함수

SetupPlayerInputComponent()

함수 기능을 입력에 바인딩하기 위해 호출되는 함수
*. 바인딩(Binding)이란? 
 프로그램 소스에 쓰인 각종 내부 요소, 이름 식별자들에 대해 값 또는 속성을 확정한 과정

이제 여기서 카메라 컴포넌트를 정의하자.

	UPROPERTY(EditAnywhere)
	class UCameraComponent* Camera;

UPROPERTY는 뭐고 EditAnywhere는 뭔지 설명하겠다.

UPROPERTY의 역할은 기본적으로 언리얼 리프렉션 시스템에 해당 프로퍼티가 있음을 알려준다.

빌드 시에 UHT(Unreal Header Tool)이 이 매크로를 감지하고 리플렉션 유형에 추가한다.

언리얼 리플렉션 시스템에 추가된 UPROPERTY는 가비지 컬렉션에 의해 생명 주기가 관리되며,

리플렉션을 통해 이 멤버 변수의 이름, 유형 등을 런타임 중에 확인할 수 있다.

언리얼이 메모리 관리를 자동으로 해준다 생각하자.

*리플렉션이란?
프로그램이 실행시간에 자기 자신을 조사하는 기능
위에서 사용하는 리플렉션은 '프로퍼티 시스템'이라고도 부른다.

이 프로퍼티 값에 여러 가지 지정자를 해줄 수 있는데 DefaultsOnly, InstanceOnly, Anywhere 이렇게 3가지를 지정해 줄 수 있다.

Edit가 앞에 붙으면 편집이 가능하게 할 경우

Visible이 앞에 붙으면 보여주기만 할 경우(블루프린트에서는 변경 가능)

이 주제는 나중에 또 자세히 다뤄보도록 하자.

 

이제 Cpp 파일에 들어가서 생성자 안에 컴포넌트를 추가할 건데

추가하는 방법은 CreateDefaultSubObjet<Type>(Name)을 사용하는 것이다.

이 함수는 새롭게 만들어진 SubObject의 주소값을 Return 한다.

즉 Pointer에 저장이 가능하다.

그렇다면 이렇게 쓰는 것이 가능하다.

	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Player Camera"));
	Camera->SetupAttachment(RootComponent);

SetupAttachmenet의 RootComponent는 RootComponent를 부모로 둔다는 함수이다.

RootComponent는 현재 클래스의 액터를 말한다.

 

이렇게 써놓고 빌드를 하고 성공하게 된다면

이렇게 카메라가 추가된 것을 확인할 수 있다.

 

카메라 로케이션은 이 정도로 설정해서 위로해 줬다.

 

이제 움직이는 기능을 만들어보자.

 

일단 움직이려면 이 캐릭터가 회전할 수 있어야 하며 좌우 앞뒤로 이동할 수 있어야 한다.

회전하고 싶게 하려면 Use Pawn ControlRotation을 체크해줘야 한다.

이것을 체크하는 이유는 단순하다. 회전 사용할지 안 할지 체크하는 부분이다.

우리는 회전을 하게 해야 하기 때문에 체크를 해준다.

하지만 이것은 수동으로 하게 되는 것이고 자동으로 체크해 주도록 코딩할 것이다.

Camera->bUsePawnControlRotation = true;

이렇게 말이다.

 

그러면 현재상태는

PlayerCharacter.h

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

#pragma once

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

UCLASS()
class FIRSTPROJECT_API APlayerCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	APlayerCharacter();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
	UPROPERTY(EditAnywhere)
	class UCameraComponent* Camera;
};

PlayerCharacter.cpp

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


#include "FirstProject/Player/PlayerCharacter.h"
#include "Camera/CameraComponent.h"

// Sets default values
APlayerCharacter::APlayerCharacter()
{
 	// 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;


	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Player Camera"));
	Camera->SetupAttachment(RootComponent);
	Camera->bUsePawnControlRotation = true;
}

// Called when the game starts or when spawned
void APlayerCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void APlayerCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

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

이 상태가 될 것이다.

이제 우리는 입력값(키보드, 마우스)을 통해 회전하거나 좌 우 앞 뒤로 움직이는 기능을 만들어야 하기에 그 입력값을 설정해야 한다.

 

현재 프로젝트 세팅에 들어가서 (Edit -> ProjectSetting) Input을 누른다.

ActionMappings에 이렇게 값을 추가해 주자

이름은 마음대로 해도 된다.

 

PlayerCharacter.h파일에 가서 함수를 정의해 주자

 

	void MoveForward(float InputValue);
	void MoveRight(float InputValue);

	void Turn(float InputValue);
	void LookUp(float InputValue);

 

PlayerCharacter.cpp에서 함수의 내용들을 적어보자.

void APlayerCharacter::MoveForward(float InputValue)
{
	FVector ForwardDirection = GetActorForwardVector();
	AddMovementInput(ForwardDirection, InputValue);
}

void APlayerCharacter::MoveRight(float InputValue)
{
	FVector RightDirection = GetActorRightVector();
	AddMovementInput(RightDirection, InputValue);
}

void APlayerCharacter::Turn(float InputValue)
{
	AddControllerYawInput(InputValue);
}

void APlayerCharacter::LookUp(float InputValue)
{
	AddControllerPitchInput(InputValue);
}

GetActorForwardVector

현재 캐릭터 앞의 방향을 구한다.

GetActorRightVector

현재 캐릭터의 오른쪽 방향을 구한다.

AddMovementInput 

방향벡터에 따라 이동 입력을 추가한다.

AddControllerYawInput

상하 축을 기준으로 회전값을 추가한다.

AddControllerPitchInput

좌우 축을 기준으로 회전값을 추가한다.

FVector는 그냥 C++ Vector이다.

 

이제 SetupPlayerInputComponent에 더 추가하자.

void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
	PlayerInputComponent->BindAxis("MoveForward", this, &APlayerCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &APlayerCharacter::MoveRight);

	PlayerInputComponent->BindAxis("TurnCamera", this, &APlayerCharacter::Turn);
	PlayerInputComponent->BindAxis("LookUp", this, &APlayerCharacter::LookUp);
}

자 느낌이 오시나요?

PlayerInputComponent(UInputComponent)에 BindAction과 BindAxis를 활용해서 움직이는 겁니다.

BindAction(입력 이름, 키 이벤트, 움직일 클래스, 기능)

BindAxis(입력 이름, 움직일 클래스, 기능)

그렇다면 우리는 아까 ProjectSetting -> Input에 입력 값들을 다 넣었습니다.

그래서 Jump, MoveForward 등등 그 입력 값을 통해 입력 액션을 넣어줍니다.

 

이제 빌드를 해보자.

 

이제 제대로 되었는지 확인해봐야 하는데, 그러기 전에 일단 새로운 레벨을 만들어준다.

이렇게 빈 공간을 만든 후 블루프린트 클래스를 하나 만들어주자.

필자는 PlayerGameMode라고 적었습니다.

블프 클래스를 더블 클릭해보면

이 창이 뜨는데 이것을 BP_PlayerCharacter로 바꿔준다.

 

그다음 월드세팅에 들어가자.

 

 

게임모드를 아까 만든걸로 바꿔줍시다.

 

이제 실행 해보면!?

 

 

 

 

후...

설명이 아직 좀 많이 부족한 거 같긴 하지만 모르면 댓글 달아주세요.

다음에 봅시다.