[C++ 기초 3편] Character.h와 Character.cpp로 클래스 구조 이해하기

김여울·2025년 5월 14일

사전캠프

목록 보기
22/24

📄 Game1Character.h

// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once  // 중복 포함 방지

// 필수 헤더 파일 포함
#include "CoreMinimal.h"                          // 언리얼의 핵심 기능 포함
#include "GameFramework/Character.h"              // ACharacter 클래스 포함
#include "Logging/LogMacros.h"                    // 로그 출력용 매크로
#include "Game1Character.generated.h"             // 1️⃣ 밑에 GENERATED_BODY(클래스의 정보르 언리얼로 인식시켜줌) 기능과 관련

// 2️⃣ 전방 선언: 헤더 파일 경량화 목적 
class USpringArmComponent;        // 카메라 붐 컴포넌트 (카메라를 따라오는 암)
class UCameraComponent;           // 실제 카메라 컴포넌트
class UInputMappingContext;       // 입력 매핑 컨텍스트 (Enhanced Input용)
class UInputAction;               // 입력 액션 클래스
struct FInputActionValue;         // 함수, 멤버 변수 등을 가질 수 있는 구조체, **public으로 속성 선언**

// 이런 종류의 매크로 만들겠다 선언
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);

// 3️⃣ UCLASS 매크로로 언리얼 오브젝트로 등록
UCLASS(config=Game)   // 클래스의 정보를 언리얼에 알려줌 (Reflection) 
class AGame1Character : public ACharacter  // 기본 캐릭터 클래스 상속
{
	GENERATED_BODY()  // 리플렉션, 직렬화, 블루프린트 지원을 위한 필수 매크로

	// 4️⃣포인터
	/** 캐릭터 뒤에 위치하는 스프링암 (카메라 붐) */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	USpringArmComponent* CameraBoom;

	/** 플레이어가 보는 카메라 컴포넌트 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	UCameraComponent* FollowCamera;
	
	/** 기본 입력 매핑 컨텍스트 (Input Mapping Context) */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputMappingContext* DefaultMappingContext;

	/** 점프 입력 액션 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputAction* JumpAction;

	/** 이동 입력 액션 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputAction* MoveAction;

	/** 카메라 회전(시야 이동) 입력 액션 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
	UInputAction* LookAction;

public:
	/** 생성자: 컴포넌트 생성 및 초기 설정 */
	AGame1Character();

protected:
	// 5️⃣ const 6️⃣ &
	/** 이동 입력 처리 함수 (Enhanced Input 시스템에서 호출됨) */
	void Move(const FInputActionValue& Value);  

	/** 마우스나 스틱 입력에 따라 시야 회전 처리 */
	void Look(const FInputActionValue& Value);  

protected:
	/** 플레이어 입력 바인딩 함수 */
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	
	/** 게임 시작 시 입력 매핑 컨텍스트 추가용 */
	virtual void BeginPlay();

public:
	/ 7️⃣ FORCEINLINE
	/** CameraBoom 컴포넌트를 반환하는 인라인 함수 */
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }

	/** FollowCamera 컴포넌트를 반환하는 인라인 함수 */
	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};

📌 정리

1️⃣ #include "Game1Character.generated.h"

항목설명
generated.h언리얼 헤더 툴이 생성한 자동 코드 포함
역할GENERATED_BODY()가 이 파일 내용을 통해 클래스 등록 처리
위치헤더 파일의 모든 include 중 가장 마지막이어야 함
  • UCLASS(), UPROPERTY(), UFUNCTION() 같은 매크로들은 혼자만으로는 작동 안 함
  • 이 매크로들은 Unreal Header Tool이 자동으로 만들어주는 코드에 의해 실제 기능이 완성
  • 그 자동 생성 코드가 "Game1Character.generated.h"에 들어있음
  • GENERATED_BODY() 매크로에서 이 파일의 내용을 불러와 사용하는 구조

2️⃣ 전방 선언

  • 헤더 파일 경량화 목적
  • 헤더 파일에서는 선언만 하고 실제로 포함하는 건 소스 파일에서 포함
  • 미리 있다고 선언만 해둠

3️⃣ 매크로의 역할
"이 클래스는 언리얼에서 인식해야 하니, 리플렉션 시스템에 등록해줘"

매크로설명
UCLASS()클래스를 언리얼에 등록 (리플렉션 메타데이터 생성)
UPROPERTY()멤버 변수를 언리얼에 노출 (에디터에서 보이게)
UFUNCTION()함수를 언리얼에 등록 (블루프린트 호출 등 가능)

4️⃣ 포인터
❓ 왜 컴포넌트나 객체를 대부분 "포인터"로 다루는가?

  • 복사본이 아닌 "원본"을 다루기 위해
    • A a; 처럼 객체를 그대로 전달하면 복사본이 생성됨
    • 이 복사본을 수정해도 원래 객체에는 영향이 없음
    • 포인터(A* a)로 넘기면 원본 주소를 넘기므로 그걸 통해 직접 수정 가능
  • 큰 객체는 복사 비용이 크다
    • 큰 구조체나 클래스는 복사 시 많은 메모리와 시간이 소모됨
    • 포인터(4 or 8바이트)를 넘기는 게 훨씬 효율적임
  • null 여부 확인 가능
    • 포인터는 nullptr로 초기화 가능
    • 객체가 존재하는지 여부 체크가 쉬움
  • 상속 & 다형성(polymorphism)을 쓸 수 있다
    • 부모 클래스 포인터로 자식 클래스 인스턴스를 다룰 수 있음

5️⃣ const

  • 상수값 = 변하지 않는 값
  • "변경할 수 없음"
  1. 변수에 const
const int Health = 100;
Health = 200; // ❌ 에러! 값을 바꿀 수 없음
  • const가 붙으면 처음 정해준 값 이후로 수정 불가
  • 실수로 값을 바꾸는 것을 방지해주는 안전장치
  1. 함수 인자에 const
void PrintName(const FString& Name);

➡ 함수 안에서 Name 값을 절대 수정하지 않겠다는 약속

  1. 함수 뒤에 const
int GetHealth() const;
  1. 포인터 + const
const int* A;   // 값을 못 바꿈 (가리키는 대상이 const)
int* const B;   // 포인터 위치 못 바꿈 (주소 고정)
const int* const C; //  값도 못 바꾸고 주소도 못 바꿈
  1. 언리얼에서 자주 보는 const 예시
FORCEINLINE class UCameraComponent* GetFollowCamera() const

➡ GetFollowCamera() 함수는 카메라를 가져오기만 할 뿐 이 캐릭터의 상태는 바꾸지 않겠다는 뜻

6️⃣ & (참조)

void Move(const FInputActionValue& Value);

➡ FInputActionValue 객체를 복사하지 않고, 읽기 전용으로 참조해서 쓰겠다

  • 원본 데이터를 복사하지 않고 안전하게 읽기만 하겠다
  • 구조체는 크기가 클 수 있음 → 복사보다 참조가 훨씬 빠름
  • const를 붙이면 실수로 수정 못 하게 막음
  • 읽기 전용 입력값 처리에 적합

✔️ 포인터(Pointer) vs 참조 (Reference)

항목포인터 (*)레퍼런스 (&)
개념변수의 주소를 저장하는 변수변수의 또 다른 이름 (별칭)
선언 방법int* ptr = #int& ref = num;
null 가능 여부가능 (nullptr)불가능 (항상 참조 대상이 있어야 함)
재지정 가능 여부가능 (다른 주소를 가리킬 수 있음)불가능 (처음 참조한 대상만 계속 참조)
역참조 방식*ptr그냥 ref
메모리 접근간접 접근 (주소를 통해)직접 접근 (이름으로 접근)
안전성실수로 null 접근 등 위험상대적으로 안전

상황추천
null 허용, 존재 여부 체크 필요포인터
값이 항상 존재해야 하고, 간단하게 다루고 싶을 때레퍼런스
다형성, 배열 접근 등 주소가 필요할 때포인터
함수 매개변수로 "원본 수정"을 원할 때보통 레퍼런스

7️⃣ FORCEINLINE
인라인 함수

  • 보통 C++ 함수는 호출 시 스택에 쌓였다가 리턴됨
    • 함수 호출 ➡ 그 함수로 가서 실행 ➡ 시간 걸림
  • 인라인 함수는 호출하지 않고 함수 본문을 그대로 복사해서 붙여넣는 방식
    • 짧은 함수 같은 경우 그 코드를 복사해서 위치에 붙여넣음



✨ Game1Character.cpp

 // 1️⃣
#include "Game1Character.h"
#include "Engine/LocalPlayer.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/Controller.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"

// 로그 카테고리 정의 (로그 출력 시 사용)
DEFINE_LOG_CATEGORY(LogTemplateCharacter);

//////////////////////////////////////////////////////////////////////////
// AGame1Character

// 생성자 함수 - 객체가 생성될 때 호출됨
AGame1Character::AGame1Character()
{
	// 충돌 캡슐 크기 설정 (가상의 충돌 박스)
	GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

	// 캐릭터가 카메라 방향으로 회전하지 않도록 설정
	bUseControllerRotationPitch = false;
	bUseControllerRotationYaw = false;
	bUseControllerRotationRoll = false;

	// 캐릭터 움직임 관련 설정
	GetCharacterMovement()->bOrientRotationToMovement = true; // 이동 방향으로 캐릭터가 회전함
	GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // 회전 속도 설정

	// 점프, 공중 제어, 속도 등 이동 세부 설정
	GetCharacterMovement()->JumpZVelocity = 700.f; // 점프 높이
	GetCharacterMovement()->AirControl = 0.35f; // 공중에서 조작 가능 정도
	GetCharacterMovement()->MaxWalkSpeed = 500.f; // 최대 걷기 속도
	GetCharacterMovement()->MinAnalogWalkSpeed = 20.f; // 최소 걷기 속도 (아날로그 입력용)
	GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; // 걷기 감속
	GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f; // 낙하 감속
	
    // 2️⃣
	// 카메라 붐(팔) 생성 및 설정 - 카메라 거리 유지용 스프링암
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent); // 루트 컴포넌트(캡슐)에 붙임
	CameraBoom->TargetArmLength = 400.0f; // 캐릭터로부터의 거리
	CameraBoom->bUsePawnControlRotation = true; // 컨트롤러 회전에 따라 회전

	// 실제 카메라 생성 및 설정
	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 붐 끝에 카메라 부착
	FollowCamera->bUsePawnControlRotation = false; // 카메라 자체는 회전하지 않음

	// 참고: 메쉬와 애니메이션은 블루프린트에서 지정하도록 설정되어 있음
}

// 게임 시작 시 호출됨
void AGame1Character::BeginPlay()  // 3️⃣
{
	Super::BeginPlay(); // 4️⃣ 부모 클래스의 BeginPlay 호출
}

//////////////////////////////////////////////////////////////////////////
// 입력 바인딩 관련

// 키보드/컨트롤러(Input)를 캐릭터에 연동
void AGame1Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	// Enhanced Input 시스템을 위한 매핑 컨텍스트 추가
	if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))  // 5️⃣
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			Subsystem->AddMappingContext(DefaultMappingContext, 0); // 입력 매핑 컨텍스트 등록
		}
	}
	
	// 액션에 따른 입력 이벤트 바인딩
	if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) {
		
		// 점프 시작 및 종료
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
		EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);

		// 이동 입력 처리
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AGame1Character::Move);

		// 카메라 회전(마우스/스틱) 입력 처리
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AGame1Character::Look);
	}
	else
	{
		// 6️⃣ EnhancedInputComponent가 없으면 오류 로그 출력
		UE_LOG(LogTemplateCharacter, Error, TEXT("'%s' Failed to find an Enhanced Input component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
	}
}

// 이동 입력을 처리하는 함수
void AGame1Character::Move(const FInputActionValue& Value)
{
	// Value는 2D 벡터 (X: 좌우, Y: 앞뒤)
	FVector2D MovementVector = Value.Get<FVector2D>();

	if (Controller != nullptr)
	{
		// 카메라가 바라보는 방향 기준으로 회전값 얻기
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0); // Yaw만 사용

		// 전방 벡터 구하기
		const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	
		// 오른쪽 벡터 구하기
		const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

		// 이동 방향으로 캐릭터를 움직임
		AddMovementInput(ForwardDirection, MovementVector.Y);
		AddMovementInput(RightDirection, MovementVector.X);
	}
}

// 마우스/스틱 입력으로 시야 조절하는 함수
void AGame1Character::Look(const FInputActionValue& Value)
{
	// Value는 2D 벡터 (X: 좌우, Y: 위아래)
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	if (Controller != nullptr)
	{
		// Yaw(좌우), Pitch(위아래) 회전 입력 적용
		AddControllerYawInput(LookAxisVector.X);
		AddControllerPitchInput(LookAxisVector.Y);
	}
}

📌 정리

1️⃣ 전방 선언했던 모든 기능 / 컴포먼트 다 가져오기

2️⃣ < > 템플릿 📎강의 자료

  • 자료형에 따라 재사용 가능한 코드를 만들기 위해 사용하는 문법
  • 데이터 타입에 의존하지 않는 코드를 작성할 수 있게 해주는 기능
  • 타입은 나중에 정함
  • 클래스나 함수에 붙음
CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));

➡ 템플릿 함수 CreateDefaultSubobject가 어떤 타입의 오브젝트를 만들지를 < >로 지정

🧩 예시

  • 같은 도장을 찍는데 잉크 색(타입)만 바꿔서 여러 색깔 도장을 찍고 싶음
  • 도장 틀은 그대로 두고 잉크만 바꾸는 느낌

3️⃣ BeginPlay()

  • 게임이 시작(Start) 될 때
  • 액터가 월드에 스폰(Spawn) 될 때
    ➡ BeginPlay() 함수가 자동 실행됨

4️⃣ Super = 부모클래스의

  • 부모 클래스(AActor, ACharacter 등)의 BeginPlay()도 실행
  • 호출 안 하면 상속된 기능 중 일부가 작동 안 할 수도 있다

✔️ virtual / override / super

키워드역할예시설명
virtual"자식이 오버라이드 가능하게"virtual void Speak();부모가 말함: "이 함수는 자식이 바꿔 써도 돼"
override"부모의 virtual을 내가 재정의한다"void Speak() override;자식이 말함: "내가 새로 쓸게요!"
super (C++에선 Super::)"부모 함수도 같이 실행해줘"Super::BeginPlay();자식이 말함: "부모 함수도 실행할게요"

5️⃣ cast

  • 객체의 타입을 원하는 타입으로 안전하게 변환
  • Unreal에서는 보통 부모 타입을 자식 타입으로 변환할 때 사용함
  • GetController()는 부모 타입인 AController* 를 돌려줌
if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
{
    // 형변환 성공! 이제 PlayerController를 안전하게 사용 가능
    PlayerController->SetShowMouseCursor(true);
}
부분의미
GetController()현재 액터(예: 캐릭터)의 컨트롤러를 가져옴 → AController* 타입
Cast<APlayerController>(...)자식 타입으로 안전하게 변환
if (APlayerController* PlayerController = ...)형변환 결과를 변수에 담으면서 널인지 체크
{ ... }형변환이 성공했을 때만 실행되는 영역

🧩 예시

실제 행동프로그래밍에서의 의미
공룡 장난감을 장난감 상자에 넣음자식 객체(공룡)를 부모 타입(장난감)으로 저장
상자에서 꺼냄부모 타입으로 받아옴 (장난감*)
"얘 원래 공룡이었지!" 하며 다시 공룡으로 인식형변환(Cast) 해서 자식 타입(공룡*)으로 다시 변환

6️⃣ UE_LOG

  • 개발자가 남긴 로그(기록)를 출력하는 도구
  • 디버깅하거나 작업 내역 추적할 때 유용함

0개의 댓글