1. GameMode 세팅
![]() |
먼저, 게임의 총괄 관리자 역할을 하는 GameMode를 C++ 클래스로 생성해준다.
GameMode에는 대표적으로 GameModeBase와 GameMode가 존재한다. GameModeBase는 멀티 플레이 로직이 없는 단순화된 상태이므로 GameModeBase는 간단한 게임을 만들 때에 적합하다.
GameMode는 멀티플레이 기능도 제공하고 GameState, PlayerState도 연동이 되어있어서 조금 무겁지만 다양한 기능을 활용해볼 수 있기에 GameMode를 추천한다.
GameMode.h / GameMode.cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "SpartaGameMode.generated.h"
UCLASS()
class SPARTAPROJECT_API ASpartaGameMode : public AGameMode
{
GENERATED_BODY()
public:
ASpartaGameMode();
};
#include "SpartaGameMode.h"
#include "SpartaCharacter.h"
#include "SpartaPlayerController.h"
ASpartaGameMode::ASpartaGameMode()
{
DefaultPawnClass = ASpartaCharacter::StaticClass();
PlayerControllerClass = ASpartaPlayerController::StaticClass();
}
생성자에서 DefaultPawnClass와 PlayerControllerClass를 등록해준다.
2. 플레이어 컨트롤러 세팅
![]() |
플레이어 컨트롤러는 사용자의 입력을 인식하는 역할을 한다.
방향키로 이동, 스페이스 바로 점프와 같은 기능을 수행하려면 플레이어 컨트롤러는 필수적이다.
PlayerController.h / PlayerController.cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"
class UInputMappingContext;
class UInputAction;
UCLASS()
class SPARTAPROJECT_API ASpartaPlayerController : public APlayerController
{
GENERATED_BODY()
public:
ASpartaPlayerController();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputMappingContext* InputMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* SprintAction;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
UInputAction* LookAction;
protected:
virtual void BeginPlay() override;
};
#include "SpartaPlayerController.h"
#include "EnhancedInputSubsystems.h"
ASpartaPlayerController::ASpartaPlayerController()
: InputMappingContext(nullptr),
MoveAction(nullptr),
JumpAction(nullptr),
LookAction(nullptr),
SprintAction(nullptr)
{
}
void ASpartaPlayerController::BeginPlay()
{
Super::BeginPlay();
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (InputMappingContext)
{
Subsystem->AddMappingContext(InputMappingContext, 0);
}
}
}
}
InputMappingContext를 플레이어 컨트롤러에 연결한다.
3. 캐릭터 세팅
![]() |
캐릭터를 C++ 클래스로 만들어서, 실질적인 행동과 비쥬얼을 담당하는 객체를 생성한다.
Character.h / Character.cpp
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "SpartaCharacter.generated.h"
struct FInputActionValue;
class USpringArmComponent;
class UCameraComponent;
UCLASS()
class SPARTAPROJECT_API ASpartaCharacter : public ACharacter
{
GENERATED_BODY()
public:
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 StartSprint(const FInputActionValue& Value);
UFUNCTION()
void StopSprint(const FInputActionValue& Value);
UFUNCTION()
void Look(const FInputActionValue& Value);
private:
float NormalSpeed;
float SprintSpeedMultiplier;
float SprintSpeed;
};
#include "SpartaCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SpartaPlayerController.h"
#include "EnhancedInputComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
ASpartaCharacter::ASpartaCharacter()
{
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 = 1.5f;
SprintSpeed = NormalSpeed * SprintSpeedMultiplier;
// 일반 속도 적용
GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
}
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)
{
const 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;
}
}
InputSystem이 다양하기때문에 우리가 지금 사용하는 EnhancedInputComponent로 캐스팅하는 과정이 필요하다.
이후 이동, 점프, 카메라 이동, 달리기 Action을 EnhancedInput에 등록해주면 된다.
각각 행동에 대한 함수도 따로 구현해야 한다.
느낀 점
Blueprint 노드로 간단하게 구현했던 캐릭터가 C++로 구현하니 이렇게 복잡하네요 ..
근데 제가 개발자여서 그런지 Blueprint보다 C++로 구현하는게 마음이 편하게 느껴집니다 !
솔직히 다시 작성해보라고 하면 지금은 못 할 것 같지만, 반복적인 연습과 학습으로 언젠가 캐릭터를 뚝딱 만들어내는 개발자가 되고싶네요 !
'언리얼 엔진 > 스파르타코딩클럽' 카테고리의 다른 글
[언리얼엔진] 4-1. UI 위젯 설계와 실시간 데이터 연동 (0) | 2025.02.10 |
---|---|
[언리얼엔진] 3-5. 게임 루프 설계 (0) | 2025.02.07 |
[언리얼엔진] 3-4. 캐릭터 체력 및 점수 시스템 구현 (0) | 2025.02.06 |
[언리얼엔진] 3-3. 아이템 스폰 및 데이터테이블 관리 (1) | 2025.02.05 |
[언리얼엔진] 3-2. 충돌 이벤트로 획득하는 아이템 구현 (0) | 2025.02.04 |