● 클래스 선택 레벨

타이틀 화면 레벨은 이정도로 하고 클래스 선택 레벨에서 기능을 구현할 차례다. 레벨 자체는 타이틀 화면의 기능 구현을 위해 이미 생성해뒀지만 구현한 것은 아무것도 없으니 그냥 빈 깡통 수준이다.

플레이어 클래스를 선택하는 기능이라서 들어가는 기능은 다르겠지만 여기도도 UI 외에는 출력할 것이 없기 때문에 대략적인 C++ 클래스 구조는 타이틀 레벨과 다르지 않다. 게임 인스턴스는 이미 만든 상태라서 이를 제외한 나머지를 만들면 된다.
● 게임 모드
UCLASS()
class DESECRATION_API AT3SelectClassGameMode : public AGameModeBase
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
public:
//튜토리얼 시작
void TutorialStart();
//타이틀 레벨로 돌아가기
void ReturnToTitleLevel();
private:
//게임 인스턴스
UPROPERTY()
TObjectPtr<UT3GameInstance> T3GameInstance;
};
지금 게임 모드에서는 튜토리얼 시작 기능과 타이틀로 돌아가는 기능만 있으면 된다.
void AT3SelectClassGameMode::BeginPlay()
{
Super::BeginPlay();
//게임 인스턴스
T3GameInstance = Cast<UT3GameInstance>(GetGameInstance());
if (!T3GameInstance)
{
UE_LOG(LogTemp, Error, TEXT("%s : T3GameInstance is NULL"), *GetNameSafe(this));
return;
}
}
void AT3SelectClassGameMode::TutorialStart()
{
UGameplayStatics::OpenLevel(CurrentWorld, TEXT("T3TutorialLevel"));
}
void AT3SelectClassGameMode::ReturnToTitleLevel()
{
UGameplayStatics::OpenLevel(CurrentWorld, TEXT("T3TitleLevel"));
}
타이틀 화면의 게임모드 처럼 시작시 게임 인스턴스를 캐싱한다. 튜토리얼 시작은 튜토리얼 레벨을 열고, 타이틀로 돌아가기는 타이틀 레벨을 연다. 튜토리얼 레벨은 다른 분이 만들고 있으니 임시로 예상하여 이름을 넣었다.
● 플레이어 컨트롤러
UCLASS()
class DESECRATION_API AT3SelectClassPlayerController : public APlayerController
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
public:
//튜토리얼 시작
void TutorialStart(const FString& PlayerName, const int32 ClassNum);
//타이틀 화면으로
void ReturnToTitleLevel();
private:
//게임 모드
UPROPERTY()
TObjectPtr<AT3SelectClassGameMode> SelectClassGameMode;
};
타이틀 화면의 플레이어 컨트롤러처럼 여기서도 위젯과 게임모드를 중계하는 역할이다. 게임 모드에서 선언한 함수에 대응되도록 함수를 만든다. 다만 기획상 선택한 클래스와 이름이 필요하기에 튜토리얼 시작 함수에 매개변수를 넣었다.
void AT3SelectClassPlayerController::BeginPlay()
{
Super::BeginPlay();
//마우스 사용
bShowMouseCursor = true;
const FInputModeUIOnly InputModeUIOnly;
SetInputMode(InputModeUIOnly);
//게임 모드
SelectClassGameMode = Cast<AT3SelectClassGameMode>(GetWorld()->GetAuthGameMode());
if (!SelectClassGameMode)
{
UE_LOG(LogTemp, Error, TEXT("%s : TitleGameMode is NULL"), *GetNameSafe(this));
return;
}
}
UI만 존재하는 레벨이니 여기서도 마우스 커서를 보이게 하고 입력 모드를 UI 전용으로 한다.
사실 이미 타이틀 화면에서 입력 모드를 UI 전용으로 했으니 여기서는 할 필요가 없는데, 테스트를 위해 이 레벨에서 바로 시작할 때 마우스가 간혹 사라지는 현상이 있어서 이렇게 했다.
void AT3SelectClassPlayerController::TutorialStart(const FString& PlayerName, const int32 ClassNum)
{
UE_LOG(LogTemp, Warning, TEXT("Name : %s, Class : %d"), *PlayerName, ClassNum);
SelectClassGameMode->TutorialStart();
}
void AT3SelectClassPlayerController::ReturnToTitleLevel()
{
SelectClassGameMode->ReturnToTitleLevel();
}
아직 이름이나 선택 클래스를 처리할 방법이 없으니 로그로 띄우는 용도로만 사용한다. 그 외에는 각 함수는 게임 모드에 대응되는 함수를 호출하도록 만든다.
● UI
UCLASS()
class DESECRATION_API UT3SelectClassWidget : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
private:
//클래스 선택 버튼1
UFUNCTION()
void OnClickSelectClassButton1();
//클래스 선택 버튼2
UFUNCTION()
void OnClickSelectClassButton2();
//클래스 선택 버튼3
UFUNCTION()
void OnClickSelectClassButton3();
//클래스 선택
void SelectClass(int32 ClassNum);
//선택 버튼
UFUNCTION()
void OnClickSelectButton();
//타이틀로 돌아가기
UFUNCTION()
void ReturnToTitle();
//클래스 버튼1
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectClassButton1;
//클래스 버튼2
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectClassButton2;
//클래스 버튼3
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectClassButton3;
//선택 버튼
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectButton;
//돌아가기 버튼
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> ReturnButton;
//플레이어 컨트롤러
UPROPERTY()
TObjectPtr<AT3SelectClassPlayerController> SelectClassPlayerController;
//마지막으로 선택한 클래스
int32 SelectedPlayerClass;
};
미리 캐싱할 플레이어 컨트롤러와 각 버튼마다 연결할 변수 및 대응되는 함수를 선언하느라 헤더가 다소 길어졌다. 내가 짠 코드지만 영 마음에 들지 않는다. 이걸 최적화할 방법을 생각해볼 필요가 있다. 하지만 당장 떠오르는 해결책이 없으니 일단 넘어가야겠다.
void UT3SelectClassWidget::NativeConstruct()
{
Super::NativeConstruct();
//플레이어 컨트롤러
SelectClassPlayerController = Cast<AT3SelectClassPlayerController>(GetOwningPlayer());
if (!SelectClassPlayerController)
{
UE_LOG(LogTemp, Error, TEXT("%s : SelectClassPlayerController가 NULL"), *GetNameSafe(this));
return;
}
//선택 버튼은 비활성화 상태로 시작
SelectButton->SetIsEnabled(false);
//버튼 바인딩
SelectClassButton1->OnClicked.AddDynamic(this, &ThisClass::OnClickSelectClassButton1);
SelectClassButton2->OnClicked.AddDynamic(this, &ThisClass::OnClickSelectClassButton2);
SelectClassButton3->OnClicked.AddDynamic(this, &ThisClass::OnClickSelectClassButton3);
SelectButton->OnClicked.AddDynamic(this, &ThisClass::OnClickSelectButton);
ReturnButton->OnClicked.AddDynamic(this, &ThisClass::ReturnToTitle);
//그외 나머지 초기화
SelectedPlayerClass = -1;
}
NativeConstruct에서는 플레이어 컨트롤러 캐싱 및 버튼 바인딩 작업을 한다.
void UT3TitleLevelWidget::OnClickSelectClassButton1()
{
SelectClass(1);
}
void UT3TitleLevelWidget::OnClickSelectClassButton2()
{
SelectClass(2);
}
void UT3TitleLevelWidget::OnClickSelectClassButton3()
{
SelectClass(3);
}
void UT3TitleLevelWidget::SelectClass(int32 ClassNum)
{
//이전에 누른 버튼과 동일하면 선택 버튼을 누른 것으로 간주
if (ClassNum == SelectedPlayerClass)
{
OnClickSelectButton();
return;
}
//선택 버튼 활성화
SelectButton->SetIsEnabled(true);
//누른 버튼 기억
SelectedPlayerClass = ClassNum;
}
각 클래스 버튼의 함수는 하나의 클래스 선택 함수로 이어진다. 이 함수에서는 이전에 누른 버튼의 번호를 기억한 다음 다시 버튼을 누를 때 그게 동일한 버튼이면 선택 버튼의 함수를 호출하도록 했다.
그리고 클래스 버튼 중 아무거나 눌렀다면 선택 버튼을 활성화한다.
void UT3SelectClassWidget::OnClickSelectButton()
{
SelectClassPlayerController->TutorialStart(TEXT(""), SelectedPlayerClass);
}
void UT3SelectClassWidget::ReturnToTitle()
{
SelectClassPlayerController->ReturnToTitleLevel()
}
선택 버튼과 타이틀로 돌아가기 버튼의 함수는 플레리어 컨트롤러에서 대응되는 함수를 호출한다. 이름 입력은 아직 구현하지 않았으니 튜토리얼 시작 함수의 첫 번째 매개변수는 임시로 빈 문자열을 넘긴다.
//T3SelectClassPlayerController.h
private:
//클래스 선택 화면의 위젯
UPROPERTY(EditDefaultsOnly, Category = "Widget", meta = (AllowPrivateAccess = true))
TSubclassOf<UT3SelectClassWidget> SelectClassWidgetClass;
UPROPERTY()
TObjectPtr<UT3SelectClassWidget> SelectClassWidgetInstance;
//T3SelectClassPlayerController.cpp
void AT3SelectClassPlayerController::BeginPlay()
{
Super::BeginPlay();
...
//위젯 생성
SelectClassWidgetInstance = CreateWidget<UT3SelectClassWidget>(this, SelectClassWidgetClass);
if (!SelectClassWidgetInstance)
{
UE_LOG(LogTemp, Error, TEXT("%s : Failed to create TitleLevelWidget"), *GetNameSafe(this));
return;
}
SelectClassWidgetInstance->AddToViewport();
}
위젯 클래스를 만들었으니 플레이어 컨트롤러에서는 해당 위젯을 화면에 출력하도록 한다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (6) (0) | 2026.03.25 |
|---|---|
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (5) (0) | 2026.03.24 |
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (3) (0) | 2026.03.20 |
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (2) (0) | 2026.03.19 |
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (1) (0) | 2026.03.18 |