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

김여울·2025년 7월 21일
0

내일배움캠프

목록 보기
48/114

📍 4주차 2강

11. 게임 흐름에 맞춘 메뉴 UI 구현하기

11.1 게임 메뉴 UI 디자인

a. 메뉴 위젯 생성 & 버튼 추가

WBP_MainMenu

1️⃣ Canvas Panel (PANEL)

2️⃣ Border (COMMON)

  • 뒤에 살짝 투명한 검정 배경
    • Slot의 Size X / Y 로 크기 맞추기
    • Brush Color로 검정색, A값으로 투명도 설정하기

3️⃣ Button (COMMON)

  • Position X / Y - 1920 * 1080 의 반값
  • Alignment - 0.5로 하면 중앙에 정렬
  • Size X / Y 조절

4️⃣ TEXT (COMMON)

  • 계층 구조 주의 (Button 아래)

b. 메뉴 레벨 생성 & 설정

메뉴 UI를 띄울 때는 메뉴 전용 Map을 만드는게 좋음

  • 📍 File > NewLevel
    📍 File > Save Current Level AS > MenuLevel

  • MenuLevel을 Default Map으로 설정

11.2 게임 흐름 내에 메뉴 UI 배치

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

PlayerController가 UI 담당

// PlayerController.h
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "HUD")	// ✅ 수정
UUserWidget* HUDWidgetInstance;

// 메뉴 UI
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Menu")
TSubclassOf<UUserWidget> MainMenuWidgetClass;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Menu")
UUserWidget* MainMenuWidgetInstance;

// ...

// HUD창 가져오는 함수
UFUNCTION(BlueprintCallable, Category = "HUD")
void ShowGameHUD();
// 메인 메뉴창 가져오는 함수
UFUNCTION(BlueprintCallable, Category = "Menu")
void ShowMainMenu(bool bIsRestart);	// 처음: Start, GameOver: Restart - bool 변수
// MainMenu의 StartButton - 게임 시작 함수
// Start & Restart - 거의 똑같음 -> StartGame으로 묶기
UFUNCTION(BlueprintCallable, Category = "Menu")
void StartGame();

// PlayerController.cpp
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"	// MainMenu Text 관련

ASpartaPlayerController::ASpartaPlayerController()	// 생성자 정의
	: //...
      MainMenuWidgetClass(nullptr),
	  MainMenuWidgetInstance(nullptr)
{	
}

// ...

// 메뉴 UI 표시
void ASpartaPlayerController::ShowMainMenu(bool bIsRestart)
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();	// 뷰포트에서 떼어내고
		HUDWidgetInstance = nullptr;	// nullptr로 처리
	}

	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}

	// 메뉴 UI 생성
	if (MainMenuWidgetClass)
	{
		MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
		if (MainMenuWidgetInstance)
		{
			MainMenuWidgetInstance->AddToViewport();	// 메인메뉴 생기면 뷰포트에 연결

			// 뒤에 게임(레벨)이 떠있으면 마우스 움직일 때 캐릭터 시점 조작될 수 있음
			// 막기 위해서 마우스 포커스가 UI로만 가게
			bShowMouseCursor = true;	// 게임에선 마우스 커서 안 보이지만 UI에선 보이게
			SetInputMode(FInputModeUIOnly());	// UI만 사용 가능하게 InputMode 처리
		}

		// 메인메뉴가 Start? Restart?
		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()
{
	// GameInstance에 있는 정보들 초기화
	if (USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(UGameplayStatics::GetGameInstance(this)))
	{
		SpartaGameInstance->CurrentLevelIndex = 0;
		SpartaGameInstance->TotalScore = 0;
	}

	// 지금은 UI 전용 Level에 있음
	// BasicLevel 오픈해라~
	UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
}

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

// GameState.cpp
void ASpartaGameState::StartLevel()
{
	// 레벨 오픈 -> 메뉴UI (x) / HUD UI (o)
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
		{
			SpartaPlayerController->ShowGameHUD();	
		}
	}
}

void ASpartaGameState::OnGameOver()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
		{
			SpartaPlayerController->ShowMainMenu(true);	// Restart bool : true
		}
	}
}

c. Menu UI - PlayerController 연결 확인

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


☑ Is Variable → 이벤트 활성화

// PlayerController.cpp
void ASpartaPlayerController::BeginPlay()	
{
	// ...

	// 현재 맵이 mainmap일 경우만 mainmenu 띄우기
	FString CurrentMapName = GetWorld()->GetMapName();
	if (CurrentMapName.Contains("MenuLevel"))
	{
		ShowMainMenu(false);
	}

	/*
	if (HUDWidgetClass)
	{
		// HUDWidgetClassf라는 instance 만들어서 HUDWidget에 할당해라!
		HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
		if (HUDWidgetInstance)	// 안전코드
		{
			HUDWidgetInstance->AddToViewport();	// 생성된 위젯을 뷰포트에 추가해줘
		}
	}

	// 위젯을 뷰포트에 부른 직후 초기화
	ASpartaGameState* SpartaGameState = GetWorld() ? GetWorld()->GetGameState<ASpartaGameState>() : nullptr;
	if (SpartaGameState)
	{
		SpartaGameState->UpdateHUD();
	}
	*/
}

// GameState.cpp
void ASpartaGameState::BeginPlay()
{
	Super::BeginPlay();
	// UpdateHUD();	✅ 지우기
	StartLevel();

	GetWorldTimerManager().SetTimer(
		HUDUpdateTimerHandle,
		this,	// 이 객체에서
		&ASpartaGameState::UpdateHUD,	// 시간이 끝나면 이 함수 불러~
		0.1f,	// 0.1 초마다 UpdateHUD 불러
		true	// 반복
	);
}


Static 함수

static 함수 vs 일반 함수

구분일반 함수static 함수
호출 방식객체 생성 후 객체->함수()객체 없이 클래스::함수()
this 사용가능불가능
멤버 변수 접근가능불가능
주 용도객체의 상태를 다룰 때공용 기능, 유틸리티 함수 등
  • 일반 함수는 객체를 만들어야 호출 가능
  • static 함수는 객체없이 클래스 이름으로 바로 호출 가능
    → 객체 상태랑 상관없는 기능이면 static 함수로 만드는 게 좋아!
// 일반 함수
class UMyActor : public AActor
{
public:
	void SayHello()
	{
		UE_LOG(LogTemp, Warning, TEXT("Hello from object!"));
	}
};

// 일반 함수 - 사용
UMyActor* Actor = GetWorld()->SpawnActor<UMyActor>();
Actor->SayHello();  // ✅ 객체를 만들어야 호출 가능


// static 함수
class UMyHelper
{
public:
	static void SayHello()
	{
		UE_LOG(LogTemp, Warning, TEXT("Hello from static!"));
	}
};

// static 함수 - 사용
// 사용법
UMyHelper::PrintMessage();  // ✅ 클래스 이름으로 바로 호출!

0개의 댓글