PlayerController를 여러 레벨에서 재사용 가능// PlayerController.h
#include "Blueprint/UserWidget.h"
class UUserWidget;
class PPP_API APppPlayerController : public APlayerController
{
GENERATED_BODY()
public:
// ====== UI 위젯 클래스 ======
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<UUserWidget> MainMenuWidgetClass;
// ====== UI 위젯 인스턴스 ======
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UI")
UUserWidget* MainMenuWidgetInstance;
// ====== 함수들 ======
virtual void SetupInputComponent() override;
UFUNCTION(BlueprintCallable, Category = "UI")
void ShowMainMenu(bool bIsRestart);
// PlayerController.cpp
#include "PppPlayerController.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetSystemLibrary.h"
APppPlayerController::APppPlayerController()
: MainMenuWidgetClass(nullptr)
, MainMenuWidgetInstance(nullptr)
{
}
void APppPlayerController::BeginPlay()
{
Super::BeginPlay();
FString CurrentMapName = GetWorld()->GetMapName();
if (CurrentMapName.Contains("MainMenuLevel"))
{
ShowMainMenu(false);
}
}
void APppPlayerController::ShowMainMenu(bool bIsRestart)
{
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->RemoveFromParent();
MainMenuWidgetInstance = nullptr;
}
if (MainMenuWidgetClass)
{
MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->AddToViewport();
bShowMouseCursor = true;
SetInputMode(FInputModeUIOnly());
if (UButton* StartBtn = Cast<UButton>(MainMenuWidgetInstance->GetWidgetFromName(TEXT("Start_BTN"))))
{
StartBtn->OnClicked.AddDynamic(this, &APppPlayerController::StartGame);
}
if (UButton* QuitBtn = Cast<UButton>(MainMenuWidgetInstance->GetWidgetFromName(TEXT("Quit_BTN"))))
{
QuitBtn->OnClicked.AddDynamic(this, &APppPlayerController::QuitGame);
}
}
}
}
void APppPlayerController::StartGame()
{
//인풋 모드를 GameOnly로 바꿔서 키보드 입력 (WASD 등) 가능하게
SetInputMode(FInputModeGameOnly());
bShowMouseCursor = false;
// 0.3초 후에 맵 로딩하도록 타이머 설정
FTimerHandle StartHandle;
GetWorld()->GetTimerManager().SetTimer(
StartHandle,
[this]() {
UGameplayStatics::OpenLevel(GetWorld(), FName("BasicMap"), true);
},
0.3f, false);
UE_LOG(LogTemp, Warning, TEXT("StartGame() called - 0.3초 후 BasicMap으로 이동"));
}
void APppPlayerController::QuitGame()
{
UWorld* World = GetWorld();
if (!World) return;
if (QuitSound)
{
UGameplayStatics::PlaySound2D(this, QuitSound);
}
FTimerHandle QuitHandle;
World->GetTimerManager().SetTimer(
QuitHandle,
[this, World]() {
APlayerController* PC = UGameplayStatics::GetPlayerController(World, 0);
UKismetSystemLibrary::QuitGame(World, PC, EQuitPreference::Quit, false);
},
1.0f, false);
}
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
"AIModule",
"NavigationSystem",
"GameplayTasks",
"UMG",

OutGamePlayerController 블루프린트에서MainMenuWidgetClass에 MainMenuWidget 연결하기
| 체크포인트 | 체크해! |
|---|---|
| 레벨 → GameMode Override → GM_MainMenu로 설정 | ☑ |
| GM_MainMenu → Player Controller Class → OutGamePlayerController | ☑ |
| BeginPlay 로그 찍어서 실행 확인 | ☑ |
| MainMenuWidgetClass에 위젯 연결 | ☑ |
| ShowMainMenu에서 AddToViewport() 호출 | ☑ |
여러 번 생성 방지
AddToViewport() 할 때마다 새로 생김 (겹쳐짐, 메모리 낭비)제거(RemoveFromParent)를 쉽게
RemoveFromParent() 같은 제어 가능위젯 내부 값을 동적으로 제어
상태 유지 및 추적
블루프린트는 그냥 그때그때 팝업 창 열기 방식
C++에서는 "열린 창을 변수로 보관해두고 나중에 닫거나 수정할 수 있게 함
UI를 직접 관리하고 제어하려면 인스턴스화는 필수 !!
// 이미 열려 있으면 다시 안 만들고 return
if (MainMenuWidgetInstance)
return;
// 나중에 닫아야 하니까 기억해둠
MainMenuWidgetInstance = CreateWidget<UUserWidget>(...);
MainMenuWidgetInstance->AddToViewport();