[Unreal] 02.C++ 스크립트로 변환

본 문서는 어소트락 언리얼엔진 게임프로그래머 양성과정의 강의를 토대로 필기한 내용입니다

C++로 스크립트 생성

Player

  • 새로운 C++ 파일을 생성
  • 헤더 파일(.h)과 실행 파일(.cpp)이 생성됨
    • 헤더 파일 : 클래스의 선언부
    • 실행 파일 : 클래스의 구현부
    • #include "PlayerCharacter.generated.h" : 자동으로 생성되는 헤더, 가장 아래에 있어야 한다

PlayerCharacter.h

#pragma once

// ../ 는 이전폴더를 의미한다.
#include "../GameInfo.h"
#include "GameFramework/Character.h"

#include "PlayerCharacter.generated.h"

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

public:
	APlayerCharacter(); // 생성자

protected:
	// USpringArmComponent* mArm;
	// 언리얼 UObject용 포인터 선언
	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USpringArmComponent>	mArm;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UCameraComponent>	mCamera;

protected:
	virtual void BeginPlay() override; // 게임 시작 시 실행

public:	
	virtual void Tick(float DeltaTime) override; // 매 프레임 호출

	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // 충돌처리 (사용X)

};
  • UPROPERTY : Unreal Property
    • 언리얼 리플렉션 시스템에 해당 프로퍼티가 있음을 알림
    • 디테일 탭에 나타낼 수 있음
UPROPERTY(VisibleAnywhere)

PlayerCharacter.cpp

#include "PlayerCharacter.h" // 생성한 헤더파일

APlayerCharacter::APlayerCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

	// CreateDefaultSubobject 함수는 생성자에서만 사용한다
    // 템플릿에 지정된 타입의 객체 하나를 생성하고 그 메모리 주소를 반환해준다.
	mArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("Arm"));
    mCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
  
	mArm->SetupAttachment(RootComponent); // SpringArm은 RootComponent의 Child로 붙여줌
	mCamera->SetupAttachment(mArm); // Camera는 SpringArm의 Child로 붙여줌

    mArm->TargetArmLength = 500.f;

    mArm->SetRelativeLocation(FVector(0.0, 0.0, 70.0));
    mArm->SetRelativeRotation(FRotator(-10.0, 0.0, 0.0)); // Pitch(Y회전), Yaw(z회전), Roll(X회전)
}

// 게임 시작 혹은 스폰될 때 호출
void APlayerCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

// 매 프레임 호출
void APlayerCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

// 기능을 입력에 바인딩할 때 호출
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

}
  • CreateDefaultSubobject
    • UObject를 만들어 내는 함수
    • 위 함수는 생성자에서만 사용한다
    • 위 함수는 템플릿에 지정된 타입의 객체 하나를 생성하고 그 메모리 주소를 반환해 줌
    • mArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("Arm"));으로 헤더에서 선언한 mArm 포인터변수에 만든 객체 할당

게임 모드

  • 게임 모드에서 BP_TestPawn을 디폴트 폰 클래스 값에 넣어 놨기 때문에 BP로 만든 Pawn이 컨트롤러에 빙의됨
  • 게임할 때 필요한 공용 데이터를 따로 추가해 놓을 수 있다

GameInfo.h

  • UE에서 만든 것이 아닌 VS에서 만든 파일은 UE상에 나타나지 않는다
  • 상대 경로를 이용해 헤더파일 접근 가능
#pragma once

#include "EngineMinimal.h"

GameMode.h

#pragma once

#include "../GameInfo.h" // ../는 이전폴더를 의미한다
#include "GameFramework/GameModeBase.h"
#include "DefaultGameMode.generated.h"

UCLASS()
class UE20241_API ADefaultGameMode : public AGameModeBase
{
	GENERATED_BODY()
	
public:
	ADefaultGameMode();
};

GameMode.cpp

#include "DefaultGameMode.h"
#include "../Player/PlayerCharacter.h"

ADefaultGameMode::ADefaultGameMode()
{
	// 모든 언리얼 클래스들은 클래스이름::StaticClass() 함수를 이용해서 어떤 타입인지를 반환하게 만들어두었다.
	DefaultPawnClass = APlayerCharacter::StaticClass();
}

입력

[ProjectName].Build.cs

프로젝트에 필요한 모듈이 담기는 파일

InputData.h

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

#pragma once

#include "../GameInfo.h"
#include "InputMappingContext.h"
#include "InputAction.h"
#include "UObject/NoExportTypes.h"
#include "InputData.generated.h"

/**
 * 
 */
UCLASS()
class UE20241_API UDefaultInputData : public UObject
{
	GENERATED_BODY()
	
public :
	UDefaultInputData();

public:
	UInputMappingContext* mDefaultContext = nullptr;

public:
	UInputAction* mMoveFB = nullptr;
	UInputAction* mMoveLR = nullptr;
	UInputAction* mAttack = nullptr; // InputAction은 헤더안에서 이미 만들어져 있고 포인터 변수를 로딩만 해주면 된다 어떻게?
};

InputData.cpp

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


#include "InputData.h"

UDefaultInputData::UDefaultInputData()
{
	// 생성자에서 에셋을 로드하는 방법

	// ConstructorHelpers : 오로지 생성자에서만 사용할 수 있는 구조체
	ConstructorHelpers::FObjectFinder<UInputMappingContext>Context(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Test/Input/IMC_Test.IMC_Test'"));

	// 참조를 성공했을 경우 Context.Object를 미리 만든 mDefaultContext에 할당
	if (Context.Succeeded()) mDefaultContext = Context.Object;

	ConstructorHelpers::FObjectFinder<UInputAction>MoveFB(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Move_FB.IA_Move_FB'"));

	if (MoveFB.Succeeded()) mMoveFB = MoveFB.Object;

	ConstructorHelpers::FObjectFinder<UInputAction>MoveLR(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Move_LR.IA_Move_LR'"));

	if (MoveLR.Succeeded()) mMoveLR = MoveLR.Object;

	ConstructorHelpers::FObjectFinder<UInputAction>Attack(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Attack.IA_Attack'"));

	if (Attack.Succeeded()) mAttack = Attack.Object;
}

PlayerCharacter.cpp

  • BeginPlay() 시 APlayerController
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"

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

	// GetController로 컨트롤러를 찾고 PlayerController로 형 변환

	// PlayerController로 부터 LocalPlayer를 얻어옴
	// 미리 PlayerController를 얻어 두고, nullptr이 아니면(찾음) LocalPlayer를 얻어옴
	APlayerController* PlayerController = GetController<APlayerController>();
	if (nullptr != PlayerController)
	{
		ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer();

		// LocalPlayer를 이용해서 EnhancedInputLocalPlayerSubsystem을 얻어옴
		UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(LocalPlayer);

		// UDefaultInputData의 CDO를 꺼내온다
		const UDefaultInputData* InputData = GetDefault<UDefaultInputData>();

		Subsystem->AddMappingContext(InputData->mDefaultContext, 0);
	}
}


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

	// Cast : 언리얼 UObject 객체들의 형변환 함수
	UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent);

	// UDefaultInputData의 CDO를 꺼내온다
	const UDefaultInputData* InputData = GetDefault<UDefaultInputData>();

	// 원하는 InputAction이 동작할 때 호출될 함수의 함수 포인터를 지정
	EnhancedInput->BindAction(InputData->mMoveFB, ETriggerEvent::Triggered, this, &APlayerCharacter::OnMoveFB);
}

void APlayerCharacter::OnMoveFB(const FInputActionVaule& InputValue)
{
	const FVector ActionValue = InputValue.Get<FVector>();

	float MoveDir = ActionValue.X + ActionValue.Y;

	AddMovementInput(GetActorForwardVector(), MoveDir);

	GEngine->AddOnScreenDebugMessage(-1, 20.f,
		FColor::Red,
		FString::Printf(TEXT("x : %.5f y : %.5f"), ActionValue.X, ActionValue.Y)
	);
}


UE Reflection

Java나 C#의 리플렉션 기능을 C++ 표준 문법에서는 제공하지 않으므로 언리얼 엔진이 자체적으로 프레임워크를 만들어 제공

  • UE Object 생성과정 : 실제 컴파일 전에 언리얼 헤더 툴에 의해 헤더 파일을 분석하는 과정이 선행되며, 이 과정이 완료되면 Intermediate 폴더에 언리얼 오브젝트의 정보를 담은 메타 파일이 생성됨
  • 언리얼 엔진이 컴파일 전에 먼저 메타 소스 파일과 헤더 파일을 생성하는 목적은 여러가지가 있겠지만, 기존의 C++ 문법에서 제공하지 못하는 런타임에서의 빠른 클래스 정보의 검색이 있음
  • 이 메타 정보는 언리얼 엔진이 지정한 UClass라는 특별한 클래스를 통해 보관됨
  • UClass에는 언리얼 오브젝트에 대한 클래스 계층 구조 정보와 멤버 변수, 함수에 대한 정보를 모두 기록함. 하지만 단순히 검색하는 것에서 더 나아가, 런타임에서 특정 클래스를 검색해 형(Type)을 알아내 인스턴스의 멤버 변수 값을 변경하거나 특정 인스턴스의 멤버 함수를 호출하는 것이 가능.
  • Java나 C#과 같은 C++ 다음 세대의 언어에서는 이와 유사한 기능을 리플렉션(Reflection)이라는 이름으로 제공

CDO (Class Default Object)

클래스의 기본 객체 : 컴파일 단계에서 언리얼 오브젝트마다 UClass가 생성된다면, 실행 초기의 런타임 과정에서는 언리얼 오브젝트마다 클래스 정보와 함께 언리얼 오브젝트의 인스턴스가 생성됨

  • 언리얼 오브젝트의 기본 세팅을 지정하는데 사용됨

  • 언리얼 엔진에서 CDO를 만드는 이유

    • 언리얼 오브젝트를 생성할 때마다 매번 초기화 시키지 않고, 기본 인스턴스를 미리 만들어 놓고 복제하는 방식으로 메커니즘이 구성되어 있기 때문
    • 하나의 언리얼 오브젝트가, 예를 들어 복잡한 기능을 수행하는 캐릭터까지 담당할 정도로 기능이 확장되면, 굉장히 큰 덩어리의 객체로 커질 수 있다. 만일 게임 실행 중, 런타임에서 이 캐릭터를 한번에 100명을 스폰시킨다고 가정했을 때, 캐릭터를 하나씩 처음부터 생성하고 초기화시키는 방법보다, 미리 큰 기본 객체 덩어리를 복제한 후에 속성 값만 변경하는 방법이 보다 효과적
    • 정리하자면 하나의 언리얼 오브젝트가 초기화 될 때에는 두 개의 인스턴스가 항상 생성됨.

  • 언리얼 오브젝트의 생성자는 인스턴스를 초기화해 CDO를 제작하기 위한 목적으로 사용됨
  • 이 생성자 코드는 초기화에서만 실행되고 실제 게임 플레이에서 생성자 코드는 사용할 일이 없다고 보면 됨
  • 참고로 언리얼 엔진에서 게임 플레이에서 사용할 초기화 함수는 생성자 대신 Init 이나 혹은 BeginPlay 함수를 제공

  • GetDefault() : const * 타입을 리턴하기 때문에 return 받을 값도 const로 지정

UE 로딩 과정

PlayerCharacter.h

실제 MoveFB 함수를 헤더에 선언 FInputActionValue 타입을


...
#include "InputActionValue.h"
...

UCLASS()
class STUDY_240601_API APlayerCharacter : public ACharacter
{

...

protected:
	void OnMoveFB(const FInputActionValue& InputValue);
};

© 2023 Jinsoo Lee. All rights reserved.

Powered by Hydejack v9.1.6