언리얼 엔진에서 GameMode는 게임의 규칙과 흐름을 정의하는 핵심 클래스다. 프로젝트가 복잡해지면 게임의 각기 다른 상태나 모드(메인 메뉴, 캐릭터 선택, 인게임 플레이 등)에 맞는 전용 GameMode를 구현하는 게 효율적이다. 이 포스트에서는 공통 기반 클래스(ACSGameModeBase)를 상속받아서 싱글 플레이어 게임의 '캐릭터 선택'과 '튜토리얼 플레이'를 위한 각각의 GameMode(CSSingleGameMode, CSTutorialGameMode)를 구현하는 과정을 다룬다.
또한 게임 개발 초기 단계에서 GameState, PlayerController 등 아직 완전히 구현되지 않은 다른 클래스들에 대한 의존성을 관리하면서 GameMode의 기본 구조를 먼저 개발하는 실용적인 접근 방식을 소개한다.
ACSGameModeBase) 정의모든 인게임 레벨 GameMode의 기반이 될 ACSGameModeBase 클래스를 먼저 정의한다. 이 클래스는 게임의 매치 타입(EMatchType: 싱글, 협동, 대전)과 매치 단계(EMatchPhase: 대기, 진행 중, 종료)를 관리하고, 각 매치 타입에 따른 초기화 로직(InitMatchLogic, InitSinglePlayLogic 등)을 호출하는 기본 구조를 제공한다.
// CSGameModeBase.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "CSTypes/CSGameTypes.h" // EMatchType, EMatchPhase enum 포함
#include "CSGameModeBase.generated.h"
UCLASS()
class CS_API ACSGameModeBase : public AGameMode
{
GENERATED_BODY()
public:
ACSGameModeBase();
virtual void BeginPlay() override;
virtual void PostLogin(APlayerController* NewPlayer) override; // 플레이어 로그인 시 처리
virtual void HandleStartGame(); // 게임 시작 처리 (MatchPhase 변경)
virtual void HandleEndGame(); // 게임 종료 처리 (MatchPhase 변경)
protected:
UPROPERTY(BlueprintReadOnly, Category="GameMode")
EMatchType MatchType; // 게임 인스턴스로부터 설정됨
UPROPERTY(BlueprintReadOnly, Category="GameMode")
EMatchPhase MatchPhase; // 현재 게임 단계
virtual void InitMatchLogic(); // MatchType에 따라 분기
// 각 MatchType별 초기화 (파생 클래스에서 오버라이드)
virtual void InitSinglePlayLogic() {}
virtual void InitVersusLogic() {}
virtual void InitCoopLogic() {}
void SetMatchPhase(EMatchPhase NewPhase); // MatchPhase 변경 및 GameState에 전파
};
// CSGameModeBase.cpp 일부
#include "GameModes/CSGameModeBase.h"
#include "GameStates/CSGameStateBase.h" // 기본 GameState 클래스
#include "GameInstance/CSGameInstance.h" // MatchType 가져오기
#include "Kismet/GameplayStatics.h"
ACSGameModeBase::ACSGameModeBase()
{
// 기본 GameState 설정
GameStateClass = ACSGameStateBase::StaticClass();
}
void ACSGameModeBase::BeginPlay()
{
Super::BeginPlay();
// GameInstance에서 MatchType 가져오기
if (const UCSGameInstance* GI = GetGameInstance<UCSGameInstance>())
{
MatchType = GI->MatchType;
}
InitMatchLogic(); // MatchType에 맞는 초기화 실행
SetMatchPhase(EMatchPhase::EMP_Waiting); // 초기 상태는 Waiting
}
// ... 기타 함수 구현 ...
CSSingleGameMode) 구현싱글 플레이어 모드의 캐릭터 선택 레벨을 위한 GameMode다. 이 레벨의 주요 역할은 플레이어에게 캐릭터 선택 UI를 제공하고, 선택이 완료되면 실제 게임 플레이 레벨(튜토리얼 등)로 이동시키는 것이다.
주요 설계:
PlayerController의 책임으로 분리한다. GameMode는 서버에만 존재하고 UI는 클라이언트 요소이므로, PlayerController가 BeginPlay 등에서 자신의 UI를 직접 생성하고 관리하는 구조가 더 적합하다. 그래서 CSSingleGameMode의 PostLogin에서는 UI 관련 직접 호출을 하지 않는다.PlayerController가 서버 RPC를 통해 GameMode의 RequestStartGameplayLevel 함수를 호출하도록 설계한다. 이 함수는 선택된 캐릭터 정보를 저장(아직 미구현)하고 다음 레벨을 로드한다.// CSSingleGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameModes/CSGameModeBase.h"
#include "CSSingleGameMode.generated.h"
UCLASS()
class CS_API ACSSingleGameMode : public ACSGameModeBase
{
GENERATED_BODY()
public:
ACSSingleGameMode();
// PlayerController에서 호출되어 다음 게임 레벨 시작을 요청한다.
UFUNCTION(BlueprintCallable, Category = "Game Flow")
void RequestStartGameplayLevel(APlayerController* SelectingPlayer);
protected:
virtual void InitSinglePlayLogic() override;
virtual void PostLogin(APlayerController* NewPlayer) override;
// 다음으로 이동할 레벨 이름 (에디터에서 설정 가능)
UPROPERTY(EditDefaultsOnly, Category = "Game Flow")
FName NextLevelName = FName("TutorialLevel");
};
// CSSingleGameMode.cpp (주요 부분)
#include "GameModes/CSSingleGameMode.h"
#include "Controller/CSPlayerController.h"
#include "Kismet/GameplayStatics.h"
ACSSingleGameMode::ACSSingleGameMode()
{
DefaultPawnClass = nullptr; // 캐릭터 선택 레벨에서는 Pawn이 필요 없을 수도 있다.
PlayerControllerClass = ACSPlayerController::StaticClass();
// GameStateClass = ACSSingleGameState::StaticClass(); // 필요하면 설정한다.
}
// ... InitSinglePlayLogic, PostLogin 구현 ...
void ACSSingleGameMode::RequestStartGameplayLevel(APlayerController* SelectingPlayer)
{
// ... PlayerController 유효성 검사 ...
UE_LOG(LogTemp, Log, TEXT("Player %s requested level: %s"), *SelectingPlayer->GetName(), *NextLevelName.ToString());
// --- 캐릭터 선택 정보 저장 로직 (현재는 주석 처리) ---
// TODO: 이 부분은 GameInstance 또는 PlayerState에 선택된 캐릭터 정보를 저장하는 로직이 필요하다.
// PlayerState나 GameInstance에 'SelectedCharacterClass' 같은 변수가 정의되어야 한다.
/*
UCSGameInstance* GI = GetGameInstance<UCSGameInstance>(); // 예시: GameInstance 사용
if (GI) { GI->SelectedCharacterClass = ...; }
ACSPlayerState* PS = SelectingPlayer->GetPlayerState<ACSPlayerState>(); // 예시: PlayerState 사용
if (PS) { PS->SetSelectedCharacter(...); }
*/
// --- 캐릭터 선택 정보 저장 로직 끝 ---
UGameplayStatics::OpenLevel(this, NextLevelName); // 다음 레벨 로드
}
CSTutorialGameMode) 구현실제 게임 플레이가 이루어지는 튜토리얼 레벨을 위한 GameMode다. 이 GameMode는 캐릭터 선택 레벨에서 플레이어가 선택한 캐릭터를 스폰하고, 튜토리얼 진행 규칙을 관리한다.
주요 설계:
HandleStartingNewPlayer 함수를 오버라이드해서 플레이어가 레벨에 진입(또는 리스폰)할 때, GameInstance나 PlayerState에 저장된 캐릭터 선택 정보를 바탕으로 올바른 Pawn 클래스를 스폰한다. 이를 위해 GetDefaultPawnClassForController 함수도 오버라이드해서 선택된 클래스를 반환하도록 한다. (선택 정보 로드 부분은 현재 주석 처리)HandleMatchIsWaitingToStart 함수를 오버라이드해서 게임 시작 준비가 되었을 때 기반 클래스의 HandleStartGame 함수를 호출함으로써 MatchPhase를 EMP_Playing으로 변경한다.FallbackDefaultPawnClass)를 지정할 수 있도록 한다.// CSTutorialGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameModes/CSGameModeBase.h"
#include "CSTutorialGameMode.generated.h"
UCLASS()
class CS_API ACSTutorialGameMode : public ACSGameModeBase
{
GENERATED_BODY()
public:
ACSTutorialGameMode();
virtual void HandleMatchIsWaitingToStart() override; // 게임 시작 준비 완료 시
virtual UClass* GetDefaultPawnClassForController_Implementation(AController* InController) override; // 스폰할 Pawn 클래스 결정
protected:
virtual void InitSinglePlayLogic() override;
virtual void PostLogin(APlayerController* NewPlayer) override;
virtual void RestartPlayer(AController* NewPlayer) override; // 플레이어 리스폰 처리
virtual void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) override; // 최초 스폰 처리
// 캐릭터 선택 정보 로드 실패 시 사용할 기본 Pawn 클래스
UPROPERTY(EditDefaultsOnly, Category = "Defaults")
TSubclassOf<APawn> FallbackDefaultPawnClass;
};
// CSTutorialGameMode.cpp (주요 부분)
#include "GameModes/CSTutorialGameMode.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerStart.h"
#include "Controller/CSPlayerController.h"
// #include "GameStates/CSTutorialGameState.h" // 필요하면 포함한다.
ACSTutorialGameMode::ACSTutorialGameMode()
{
// ... 기본 설정 ...
// GameStateClass = ACSTutorialGameState::StaticClass(); // 필요하면 설정한다.
}
// ... InitSinglePlayLogic, PostLogin, RestartPlayer 구현 ...
void ACSTutorialGameMode::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
{
UClass* PawnClass = GetDefaultPawnClassForController(NewPlayer); // 선택 정보 로드 시도
// ... PawnClass 유효성 검사 및 Fallback 처리 ...
if (!PawnClass) return;
AActor* StartSpot = FindPlayerStart(NewPlayer);
// ... 스폰 위치/회전 계산 ...
APawn* NewPawn = GetWorld()->SpawnActor<APawn>(PawnClass, ...); // Pawn 스폰
if (NewPawn)
{
NewPlayer->Possess(NewPawn); // Pawn에 빙의
}
}
void ACSTutorialGameMode::HandleMatchIsWaitingToStart()
{
HandleStartGame(); // MatchPhase를 Playing으로 변경
}
UClass* ACSTutorialGameMode::GetDefaultPawnClassForController_Implementation(AController* InController)
{
// --- 캐릭터 선택 정보 가져오는 로직 (현재는 주석 처리) ---
// TODO: GameInstance 또는 PlayerState에서 캐릭터 선택 정보를 가져와 반환해야 한다.
/*
UCSGameInstance* GI = GetGameInstance<UCSGameInstance>(); // 예시: GameInstance
if (GI && GI->SelectedCharacterClass) { return GI->SelectedCharacterClass; }
ACSPlayerState* PS = InController ? InController->GetPlayerState<ACSPlayerState>() : nullptr; // 예시: PlayerState
if (PS && PS->SelectedCharacterClass) { return PS->SelectedCharacterClass; }
*/
// --- 캐릭터 선택 정보 가져오는 로직 끝 ---
return FallbackDefaultPawnClass; // 정보 없으면 Fallback 반환
}
게임 개발 초기에는 모든 클래스가 한 번에 구현되지 않는다. 위 코드 예시처럼 GameState, PlayerController, PlayerState 등의 기능이 아직 구현되지 않았더라도 GameMode의 기본 구조 개발을 진행할 수 있다.
접근 방식:
PlayerState의 SelectedCharacterClass 변수 접근)// TODO: 와 같은 주석과 함께 임시로 주석 처리한다. 이렇게 하면 코드가 컴파일되고 GameMode의 다른 부분을 테스트할 수 있다.향후 필요한 정의 (요약):
ACSPlayerController:Server_RequestStartGameplayLevel).BeginPlay에서 위젯 생성, GameState 변경 시 UI 업데이트).ACSGameStateBase 및 파생 클래스 (ACSSingleGameState, ACSTutorialGameState 등):MatchPhase - Base에 이미 있음, CurrentTutorialObjectiveID).AvailableCharacterClasses).LoadAvailableCharacters, SetupTutorialObjectives).ACSPlayerState (사용한다면):SelectedCharacterClass, TutorialProgressIndex). 리플리케이션 설정이 중요하다.UCSGameInstance (사용한다면):SelectedCharacterClass). 싱글플레이어에서는 간편하지만 상태 유지 및 전달 방식에 주의가 필요하다.모드별로 특화된 GameMode를 구현하고 개발 초기 단계에서 의존성을 체계적으로 관리하면 복잡한 게임 개발 과정을 효율적으로 만들 수 있다. 기반 클래스를 활용해서 공통 로직을 재사용하고, 아직 구현되지 않은 부분은 주석과 TODO 리스트로 관리하면서 점진적으로 개발을 진행하는 방식으로 안정적인 프로젝트 구조를 구축할 수 있다.