C++와 Unreal Engine으로 3D 게임 개발 2-1~3
1. WHAT
- GameMode 기능
- 플레이어 Pawn/Character 스폰
- Pawn - PlayerController 연동
- PlayerController 지정
- 게임 규칙 관리
- 게임 전반의 규칙 정의 및 유지
- 승리/패배 결정 조건 관리 > 승/패 후속 처리
- GameState / PlayerState 사용
GameMode 클래스 적용
1. 프로젝트 전역 설정: Edit → Project Settings → Maps & Modes → Default GameMode → 클래스 지정
2. 레벨 별 설정: World Settings → GameMode Override → 클래스 지정
Character 클래스 생성 및 스켈레탈 메시 설정
언리얼에서 일반적으로 캐릭터의 전방 방향은 X축
SpringArm 및 CameraComponent 추가
// SpartaCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SpartaCharacter.generated.h"
class USpringArmComponent; // 스프링 암 관련 클래스 헤더
class UCameraComponent; // 카메라 관련 클래스 전방 선언
UCLASS()
class SPARTAPROJECT_API ASpartaCharacter : public ACharacter
{
GENERATED_BODY()
public:
ASpartaCharacter();
protected:
// 스프링 암 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
USpringArmComponent* SpringArmComp;
// 카메라 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* CameraComp;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // 이 함수는 이후에 다루게 되니, 우선 삭제하기 않고 둡니다.
};
// SpartaCharacter.cpp
#include "SpartaCharacter.h"
// 카메라, 스프링 암 실제 구현이 필요한 경우라서 include
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
ASpartaCharacter::ASpartaCharacter()
{
// Tick 함수는 우선 꺼둡니다.
PrimaryActorTick.bCanEverTick = false;
// (1) 스프링 암 생성
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
// 스프링 암을 루트 컴포넌트 (CapsuleComponent)에 부착
SpringArmComp->SetupAttachment(RootComponent);
// 캐릭터와 카메라 사이의 거리 기본값 300으로 설정
SpringArmComp->TargetArmLength = 300.0f;
// 컨트롤러 회전에 따라 스프링 암도 회전하도록 설정
SpringArmComp->bUsePawnControlRotation = true;
// (2) 카메라 컴포넌트 생성
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
// 스프링 암의 소켓 위치에 카메라를 부착
CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
// 카메라는 스프링 암의 회전을 따르므로 PawnControlRotation은 꺼둠
CameraComp->bUsePawnControlRotation = false;
}
void ASpartaCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
GameMode에서 DefaultPawnClass 설정
#include "SpartaGameMode.h"
#include "SpartaCharacter.h"
ASpartaGameMode::ASpartaGameMode()
{
DefaultPawnClass = ASpartaCharacter::StaticClass();
}
- 입력 매핑 구현
IA 설정 > IMC 설정 > PlayerController에서 IMC 활성화하기
// SpartaPlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"
class UInputMappingContext; // IMC 관련 전방 선언
class UInputAction; // IA 관련 전방 선언
UCLASS()
class SPARTAPROJECT_API ASpartaPlayerController : public APlayerController
{
GENERATED_BODY()
public:
ASpartaPlayerController();
// 에디터에서 세팅할 IMC
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
UInputMappingContext* InputMappingContext;
// IA_Move를 지정할 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
UInputAction* MoveAction;
// IA_Jump를 지정할 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
UInputAction* JumpAction;
// IA_Look를 지정할 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
UInputAction* LookAction;
// IA_Sprint를 지정할 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* SprintAction;
virtual void BeginPlay() override;
};
// SpartaPlayerController.cpp
#include "SpartaPlayerController.h"
#include "EnhancedInputSubsystems.h" // Enhanced Input System의 Local Player Subsystem을 사용하기 위해 포함
ASpartaPlayerController::ASpartaPlayerController()
: InputMappingContext(nullptr),
MoveAction(nullptr),
JumpAction(nullptr),
LookAction(nullptr),
SprintAction(nullptr)
{
}
void ASpartaPlayerController::BeginPlay()
{
Super::BeginPlay();
// 현재 PlayerController에 연결된 Local Player 객체를 가져옴
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
// Local Player에서 EnhancedInputLocalPlayerSubsystem을 획득
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (InputMappingContext)
{
// Subsystem을 통해 우리가 할당한 IMC를 활성화
// 우선순위(Priority)는 0이 가장 높은 우선순위
Subsystem->AddMappingContext(InputMappingContext, 0);
}
}
}
}
- Character 클래스에 액션 바인딩 추가 및 이동 함수 구현
// SpartaCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SpartaCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
struct FInputActionValue;
UCLASS()
class SPARTAPJ_API ASpartaCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ASpartaCharacter();
UPROPERTY(visibleAnywhere, BlueprintReadOnly, Category = "Camera")
USpringArmComponent* SpringArmComp;
UPROPERTY(visibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* CameraComp;
protected:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UFUNCTION()
void Move(const FInputActionValue& value);
UFUNCTION()
void StartJump(const FInputActionValue& value);
UFUNCTION()
void StopJump(const FInputActionValue& value);
UFUNCTION()
void Look(const FInputActionValue& value);
UFUNCTION()
void StartSprint(const FInputActionValue& value);
UFUNCTION()
void StopSprint(const FInputActionValue& value);
private:
float NormalSpeed;
float SprintSpeedMultiplier;
float SprintSpeed;
};
// SpartaCharacter.cpp
#include "SpartaCharacter.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "SpartaPlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
// Sets default values
ASpartaCharacter::ASpartaCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArmComp->SetupAttachment(RootComponent);
SpringArmComp->TargetArmLength = 300.0f;
SpringArmComp->bUsePawnControlRotation = true;
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
CameraComp->bUsePawnControlRotation = false;
NormalSpeed = 600.0f;
SprintSpeedMultiplier = 2.0;
SprintSpeed=NormalSpeed*SprintSpeedMultiplier;
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
// Called to bind functionality to input
void ASpartaCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if(UEnhancedInputComponent* EnhancedInput=Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
if (ASpartaPlayerController* PlayerController = Cast<ASpartaPlayerController>(GetController()))
{
if (PlayerController->MoveAction)
{
EnhancedInput->BindAction(
PlayerController->MoveAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::Move
);
}
if (PlayerController->JumpAction)
{
EnhancedInput->BindAction(
PlayerController->JumpAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::StartJump
);
EnhancedInput->BindAction(
PlayerController->JumpAction,
ETriggerEvent::Completed,
this,
&ASpartaCharacter::StopJump
);
}
if (PlayerController->LookAction)
{
EnhancedInput->BindAction(
PlayerController->LookAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::Look
);
}
if (PlayerController->SprintAction)
{
EnhancedInput->BindAction(
PlayerController->SprintAction,
ETriggerEvent::Triggered,
this,
&ASpartaCharacter::StartSprint
);
EnhancedInput->BindAction(
PlayerController->SprintAction,
ETriggerEvent::Completed,
this,
&ASpartaCharacter::StopSprint
);
}
}
}
}
void ASpartaCharacter::Move(const FInputActionValue& value)
{
if (!Controller) return;
const FVector2D MoveInput = value.Get<FVector2D>();
if (!FMath::IsNearlyZero(MoveInput.X))
{
AddMovementInput(GetActorForwardVector(),MoveInput.X);
}
if (!FMath::IsNearlyZero(MoveInput.Y))
{
AddMovementInput(GetActorRightVector(), MoveInput.Y);
}
}
void ASpartaCharacter::StartJump(const FInputActionValue& value)
{
if (value.Get<bool>())
{
Jump();
}
}
void ASpartaCharacter::StopJump(const FInputActionValue& value)
{
if (!value.Get<bool>())
{
StopJumping();
}
}
void ASpartaCharacter::Look(const FInputActionValue& value)
{
FVector2D LookInput = value.Get<FVector2D>();
AddControllerYawInput(LookInput.X);
AddControllerPitchInput(LookInput.Y);
}
void ASpartaCharacter::StartSprint(const FInputActionValue& value)
{
if (GetCharacterMovement())
{
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
}
}
void ASpartaCharacter::StopSprint(const FInputActionValue& value)
{
if (GetCharacterMovement())
{
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
}
2. RESULT
점프 횟수를 세는 간단한 변수를 C++에 추가 (JumpCount++),
점프할 때마다 콘솔에 "Jump Count: x" 형태로 출력하도록 해보세요.
void ASpartaCharacter::StartJump(const FInputActionValue& value)
{
if (value.Get<bool>() and !JumpCheck)
{
Jump();
JumpCount += 1;
JumpCheck = true;
GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, FString::Printf(TEXT("Jump Count: %d"),JumpCount));
}
}
void ASpartaCharacter::StopJump(const FInputActionValue& value)
{
if (!value.Get<bool>())
{
StopJumping();
JumpCheck = false;
}
}

'내배캠Unreal_TIL > UE' 카테고리의 다른 글
| [TIL] 2026-01-14 | UE Pawn 클래스로 3D 캐릭터 만들기 (2) | 2026.01.14 |
|---|---|
| [TIL] 2026-01-13 | State Machine 설계를 통한 캐릭터 애니메이션 적용 (0) | 2026.01.13 |
| [TIL] 2026-01-09 | C++ 문자 종류 판단, Actor 라이프 사이클, Transform, 리플렉션 시스템 (0) | 2026.01.09 |
| [TIL] 2026-01-08 | 언리얼 엔진 기본 구조, 빌드 문제 해결, Actor 클래스에 컴포넌트 추가 (0) | 2026.01.08 |
| [TIL] 2025-12-24 | 과제 5 구현, 레벨 테스트 후기 (1) | 2025.12.24 |