C++와 Unreal Engine으로 3D 게임 개발

C++와 Unreal Engine으로 3D 게임 개발 4-2

jh009 2026. 6. 23. 20:12

4-2. 게임 흐름에 맞춘 메뉴 UI 구현하기

1. 게임 메뉴 UI 디자인하기

 

메뉴 위젯 생성하고 버튼 추가

UI 폴더 우클릭 → User Interface → Widget Blueprint 선택 → 위젯 이름 설정 WBP_MainMenu

 

Canvas Panel을 놓아서 기본적인 바탕 마련

Border :

Details - Size X → 1920, Size Y →  1080 

Brush Color → RGB 색깔 선택 후, 맨 아래 A (투명도) 도 취향에 맞게 설정

 

Common - Button 위젯 → Start 버튼 하나만 Canvas Panel 아래에 두기

Button 이름 → StartButton

 

Alignment를 (0.5, 0.5)로 놓고 X, Y의 위치를 화면 크기의 절반으로 두면 가운데에 딱 배치

 

각 버튼마다 Common - Text를 끌어다 놓기 (왼쪽 하단에 Hierarchy 창에 드래그 앤 드랍을 하면 편함)

Text 이름 → StartButtonText


메뉴 레벨 생성하고 설정하기

  • 흔히 게임 개발 시 메뉴 전용 맵을 만들고, 그 맵에서는 메뉴만 띄우도록 하고, 실제 게임이 시작되면 게임 레벨로 넘어가게 함
  • 이 방식을 쓰면 Menu UI 와 Game Level 이 완전히 분리되어, 구조가 좀 더 명확해짐

File - New Level 선택 → Basic 템플릿 생성, Maps 폴더 → MenuLevel 이름으로 저장

Editor - Project Settings → Maps & Modes → Default Maps 를 MenuLevel 로 지정


2. 게임 흐름 내에 메뉴 UI 배치하기

 

WBP_MainMenu 을 다양한 상황에서 표시하고, 숨기고, 다시 띄우는 로직을 단계별로 구현

  1. 플레이 버튼을 누르면 Menu UI가 가장 먼저 뜨기
  2. 게임 시작 시 자동으로 HUD를 띄우기
  3. 메뉴가 나타날 때마다, UI 입력 모드로 전환하여 버튼 클릭에 집중하게 만드는 방식으로 수정
  4. 게임이 종료되면 메뉴가 다시 뜨도록 만들기

PlayerController에 기본 위젯 오픈 함수 구현

PlayerControllerUI를 다루기 좋음

게임 모드도 가능하지만, 멀티플레이를 고려하면 PlayerController가 UI 담당이 좀 더 자연스러운 편

 

SpartaPlayerController.h

SpartaPlayerController.h

 

SpartaPlayerController.cpp

 

1. 기본 원리

PlayerController의 SetInputMode 함수를 통해 입력을 UI에 집중시킬지, 게임에 집중시킬지 결정

 

2. UI 전용 모드 전환 (UI Only)

메뉴 호출 시 캐릭터 조작을 막고 UI 입력만 받기 위한 단계

  • 구조체 설정: FInputModeUIOnly 생성 및 SetWidgetToFocus 로 포커스 대상(위젯) 지정
  • 모드 적용: SetInputMode (InputMode) 호출
  • 시각화: bShowMouseCursor = true 로 설정

3. 에디터 세팅

  • BP_SpartaPlayerController 의 MainMenuWidgetClass 변수에 WBP_MainMenu 위젯을 할당해야 함

SetInputMode로 입력 대상을 UI로 바꾸고, 마우스를 띄운 뒤, 컨트롤러에 위젯 클래스를 연결

→ 마우스로 UI만 누를 수 있게 됨  

// 메뉴 UI 표시
void ASpartaPlayerController::ShowMainMenu(bool bIsRestart)
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();
		HUDWidgetInstance = nullptr;
	}
	
	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}
	
	// 메뉴 UI 생성
	if (MainMenuWidgetClass)
	{		
		MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
		if (MainMenuWidgetInstance)
		{
			MainMenuWidgetInstance->AddToViewport();
			
			bShowMouseCursor = true;
			SetInputMode(FInputModeUIOnly());
		}
		
		if (UTextBlock* ButtonText = Cast<UTextBlock>
        (MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
		{
			if (bIsRestart)
			{
				ButtonText->SetText(FText::FromString(TEXT("Restart")));
			}
			else
			{
				ButtonText->SetText(FText::FromString(TEXT("Start")));
			}
		}
	}
}

// 게임 HUD 표시
void ASpartaPlayerController::ShowGameHUD()
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();
		HUDWidgetInstance = nullptr;
	}
	
	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}

	if (HUDWidgetClass)
	{
		HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
		if (HUDWidgetInstance)
		{
			HUDWidgetInstance->AddToViewport();
			
			bShowMouseCursor = false;
			SetInputMode(FInputModeGameOnly());
			
			ASpartaGameState* SpartaGameState 
			= GetWorld() ? GetWorld()->GetGameState<ASpartaGameState>() : nullptr;
			if (SpartaGameState)
			{
				SpartaGameState->UpdateHUD();
			}
		}
	}
}

// 게임 시작 - BasicLevel 오픈, GameInstance 데이터 리셋
void ASpartaPlayerController::StartGame()
{
	if (USpartaGameInstance* SpartaGameInstance 
		= Cast<USpartaGameInstance>(UGameplayStatics::GetGameInstance(this)))
	{
		SpartaGameInstance->CurrentLevelIndex = 0;
		SpartaGameInstance->TotalScore = 0;
	}
	
	UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
}

GameState에서 게임 흐름에 따라 UI 호출

 

SpartaGameState.cpp 수정


 

메뉴 UI가 PlayerController에 설정이 되었나 확인하기

BP_SpartaPlayerController → Menu - Main Menu Widget Class → WBP_MainMenu 로 설정 (메인 메뉴 추가하기)


Start 버튼 클릭 이벤트에 함수 바인딩

Designer 탭에서 StartButton 버튼을 선택 → Is Variable 체크

 

맨 하단에 Events 활성화 → On Clicked 옆에 + 클릭

 

OnClicked (StartButton) 이벤트 노드가 자동 생성 이벤트 노드가 자동 생성 되며, 로직을 구현할 수 있음

 

PlayerController에서 Start Game함수를 가져와서 연결


PlayerController 상에서 메뉴를 다 연결했다면 SpartaPlayerController.cpp → HUDWidget 부분 삭제하기

 

BeginPlay → 레벨을 새로 갱신할 때마다 PlayerController가 다시 만들어지게 코드를 수정하였음

 

위젯 생성을 매번 하지 않도록 코드를 정리

현재 맵메인 메뉴 맵인 경우에만 메인 메뉴 위젯을 화면에 띄우도록 조건을 추가

 

메인 메뉴 화면일 때만 메인 메뉴 UI를 띄우는 작업으로 이해하면 됨

// SpartaPlayerController.cpp

// 삭제할 부분
if (HUDWidgetClass)
{
	HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
    if (HUDWidgetInstance)
    {
    	HUDWidgetInstance->AddToViewport();
    }
}