[CH3-03] MainMenu와 PlayerController 연결하기

김여울·2025년 8월 5일

내일배움캠프

목록 보기
54/139

📌 PlayerController에서 위젯을 다루는 이유

  • 위젯의 역할:
    • 화면에 표시되는 UI (WidgetComponent 아님!)
    • 마우스/키보드 입력을 받음
    • 메뉴 버튼 클릭, 설정 변경, 시작 버튼 등등 입력 처리
      → **이런 입력의 중심은 PlayerController가 담당

🔨 MainMenu와 PlayerController 연결하기

1️⃣ C++로 PlayerController 생성


2️⃣ 위젯을 띄우는 코드 추가

  • 같은 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);
}


3️⃣ Build.cs에 UMG 추가하기

PublicDependencyModuleNames.AddRange(new string[]
        {
            "Core",
            "CoreUObject",
            "Engine",
            "InputCore",
            "EnhancedInput",
            "AIModule",
            "NavigationSystem",
            "GameplayTasks",
            "UMG",

4️⃣ 블루프린트에서 MainMenuWidgetClass에 MainMenuWidget(BP) 연결


OutGamePlayerController 블루프린트에서MainMenuWidgetClassMainMenuWidget 연결하기

5️⃣ 레벨의 GameMode에서 PlayerController로 설정


✅ 문제 생기면 이거 체크하기

체크포인트체크해!
레벨 → GameMode Override → GM_MainMenu로 설정
GM_MainMenu → Player Controller Class → OutGamePlayerController
BeginPlay 로그 찍어서 실행 확인
MainMenuWidgetClass에 위젯 연결
ShowMainMenu에서 AddToViewport() 호출

📌 인스턴스(MainMenuWidgetInstance)를 쓰는 이유

  • 여러 번 생성 방지

    • 위젯 인스턴스를 멤버 변수로 저장하면 이미 생성된 위젯이 있으면 재사용할 수 있음
      → 안 그러면 AddToViewport() 할 때마다 새로 생김 (겹쳐짐, 메모리 낭비)
  • 제거(RemoveFromParent)를 쉽게

    • 나중에 PauseMenu, GameOver 등 여러 위젯을 켜고 끌 때 인스턴스를 잡아놔야 RemoveFromParent() 같은 제어 가능
  • 위젯 내부 값을 동적으로 제어

    • (예) 텍스트 바꾸기, 버튼 잠그기, 이미지 교체 등 UI 조작 시 MainMenuWidgetInstance->GetWidgetFromName() 같은 접근 필요
  • 상태 유지 및 추적

    • 어떤 위젯이 켜져 있는지 판단하거나, 버튼 상태를 기억하는 등 상태 추적하려면 인스턴스를 잡고 있어야 함

블루프린트는 그냥 그때그때 팝업 창 열기 방식
C++에서는 "열린 창을 변수로 보관해두고 나중에 닫거나 수정할 수 있게 함
UI를 직접 관리하고 제어하려면 인스턴스화는 필수 !!

// 이미 열려 있으면 다시 안 만들고 return
if (MainMenuWidgetInstance)
    return;

// 나중에 닫아야 하니까 기억해둠
MainMenuWidgetInstance = CreateWidget<UUserWidget>(...);
MainMenuWidgetInstance->AddToViewport();

0개의 댓글