// 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 중 가장 마지막이어야 함 |
GENERATED_BODY() 매크로에서 이 파일의 내용을 불러와 사용하는 구조2️⃣ 전방 선언
3️⃣ 매크로의 역할
"이 클래스는 언리얼에서 인식해야 하니, 리플렉션 시스템에 등록해줘"
| 매크로 | 설명 |
|---|---|
UCLASS() | 클래스를 언리얼에 등록 (리플렉션 메타데이터 생성) |
UPROPERTY() | 멤버 변수를 언리얼에 노출 (에디터에서 보이게) |
UFUNCTION() | 함수를 언리얼에 등록 (블루프린트 호출 등 가능) |
4️⃣ 포인터
❓ 왜 컴포넌트나 객체를 대부분 "포인터"로 다루는가?
A a; 처럼 객체를 그대로 전달하면 복사본이 생성됨A* a)로 넘기면 원본 주소를 넘기므로 그걸 통해 직접 수정 가능5️⃣ const
const int Health = 100;
Health = 200; // ❌ 에러! 값을 바꿀 수 없음
void PrintName(const FString& Name);
➡ 함수 안에서 Name 값을 절대 수정하지 않겠다는 약속
int GetHealth() const;
const int* A; // 값을 못 바꿈 (가리키는 대상이 const)
int* const B; // 포인터 위치 못 바꿈 (주소 고정)
const int* const C; // 값도 못 바꾸고 주소도 못 바꿈
FORCEINLINE class UCameraComponent* GetFollowCamera() const
➡ GetFollowCamera() 함수는 카메라를 가져오기만 할 뿐 이 캐릭터의 상태는 바꾸지 않겠다는 뜻
6️⃣ & (참조)
void Move(const FInputActionValue& Value);
➡ FInputActionValue 객체를 복사하지 않고, 읽기 전용으로 참조해서 쓰겠다
✔️ 포인터(Pointer) vs 참조 (Reference)
| 항목 | 포인터 (*) | 레퍼런스 (&) |
|---|---|---|
| 개념 | 변수의 주소를 저장하는 변수 | 변수의 또 다른 이름 (별칭) |
| 선언 방법 | int* ptr = # | int& ref = num; |
| null 가능 여부 | 가능 (nullptr) | 불가능 (항상 참조 대상이 있어야 함) |
| 재지정 가능 여부 | 가능 (다른 주소를 가리킬 수 있음) | 불가능 (처음 참조한 대상만 계속 참조) |
| 역참조 방식 | *ptr | 그냥 ref |
| 메모리 접근 | 간접 접근 (주소를 통해) | 직접 접근 (이름으로 접근) |
| 안전성 | 실수로 null 접근 등 위험 | 상대적으로 안전 |
| 상황 | 추천 |
|---|---|
| null 허용, 존재 여부 체크 필요 | 포인터 |
| 값이 항상 존재해야 하고, 간단하게 다루고 싶을 때 | 레퍼런스 |
| 다형성, 배열 접근 등 주소가 필요할 때 | 포인터 |
| 함수 매개변수로 "원본 수정"을 원할 때 | 보통 레퍼런스 |
7️⃣ FORCEINLINE
인라인 함수
// 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()
4️⃣ Super = 부모클래스의
✔️ virtual / override / super
| 키워드 | 역할 | 예시 | 설명 |
|---|---|---|---|
virtual | "자식이 오버라이드 가능하게" | virtual void Speak(); | 부모가 말함: "이 함수는 자식이 바꿔 써도 돼" |
override | "부모의 virtual을 내가 재정의한다" | void Speak() override; | 자식이 말함: "내가 새로 쓸게요!" |
super (C++에선 Super::) | "부모 함수도 같이 실행해줘" | Super::BeginPlay(); | 자식이 말함: "부모 함수도 실행할게요" |
5️⃣ cast
GetController()는 부모 타입인 AController* 를 돌려줌if (APlayerController* PlayerController = Cast<APlayerController>(GetController()))
{
// 형변환 성공! 이제 PlayerController를 안전하게 사용 가능
PlayerController->SetShowMouseCursor(true);
}
| 부분 | 의미 |
|---|---|
GetController() | 현재 액터(예: 캐릭터)의 컨트롤러를 가져옴 → AController* 타입 |
Cast<APlayerController>(...) | 자식 타입으로 안전하게 변환 |
if (APlayerController* PlayerController = ...) | 형변환 결과를 변수에 담으면서 널인지 체크 |
{ ... } | 형변환이 성공했을 때만 실행되는 영역 |
🧩 예시
| 실제 행동 | 프로그래밍에서의 의미 |
|---|---|
| 공룡 장난감을 장난감 상자에 넣음 | 자식 객체(공룡)를 부모 타입(장난감)으로 저장 |
| 상자에서 꺼냄 | 부모 타입으로 받아옴 (장난감*) |
| "얘 원래 공룡이었지!" 하며 다시 공룡으로 인식 | 형변환(Cast) 해서 자식 타입(공룡*)으로 다시 변환 |
6️⃣ UE_LOG