📌 Game Menu 창에 대한 WBP 만들기
- Canvas Pannel를 배치하고, 그 안에 Border를 배치한다.
- Border를 클릭해, Details 창을 살펴보면 Brush Color 항목이 나온다.
- 검은색 바탕에 투명도를 0.7로 조정하여 어둡고 약간은 투명한 배경을 만들었다.
- Button을 배치해 StartButton 이름으로 변경
- 위치를 화면의 중앙으로 잡아주고 크기를 적절하게 조절
- Is Variable 에 체크가 되어있는데 이 버튼에 클릭 이벤트를 생성하고 함수를 바인딩 하는 과정은 아래에서 다시 설명하겠다.
- StartButton 안에 TextBlock을 넣고 StartButtonText 이름으로 변경
- 게임의 상태에 따라 안의 텍스트 문구는 "Start" 혹은 "Restart"로 변경되도록 C++에서 구현
📌 Menu Level 생성하고 설정
흔히 게임 개발 시 메뉴 전용 맵을 만들어, 그 맵에서는 메뉴만 띄우도록하고, 실제 게임이 시작되면 게임 레벨로 넘어가도록 한다.
이 방식을 사용하면 "Menu UI"와 "Game Level"이 완전히 분리되어, 구조가 더 명확해진다.
- nu Level을 만든뒤 Project Settings에서 Editor Startup Map, Game Default Map으로 설정
📌 게임 흐름에 맞게 WBP 전환
메뉴 위젯을 게임이 시작될 때, 게임이 종료 될 때 등 상황에 맞게 표시하고, 유저가 플레이 한다면 숨기고 HUD 위젯을 띄우는 등 자연스러운 전환이 필요하다.
1. 플레이 버튼을 누르면 Menu UI가 가장 먼저 띄워진다.
2. 게임 시작시 자동으로 HUD를 띄운다.
3. Menu가 나타날 때마다, UI 입력모드로 전환하여 버튼 클릭에 집중하게 만드는 방식으로 수정한다.
4. 게임이 종료되면 Menu UI가 다시 뜨도록 한다.
✅ PlayerController.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "StrikeZonePlayerController.generated.h"
UCLASS()
class STRIKEZONE_API AStrikeZonePlayerController : public APlayerController
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
public:
AStrikeZonePlayerController();
// HUD
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HUD")
TSubclassOf<UUserWidget> HUDWidgetClass;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HUD")
UUserWidget* HUDWidgetInstance;
// Menu
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Menu")
TSubclassOf<UUserWidget> MainMenuWidgetClass;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Menu")
UUserWidget* MainMenuWidgetInstance;
// HUD Instance Getter
UFUNCTION(BlueprintPure, Category = "HUD")
UUserWidget* GetHUDWidget() const { return HUDWidgetInstance; }
// HUD 위젯 전환
UFUNCTION(BlueprintCallable, Category = "HUD")
void ShowGameHUD();
// Menu 위젯 전환
UFUNCTION(BlueprintCallable, Category = "Menu")
void ShowMainMenu(bool bIsReStart);
// 게임 시작
UFUNCTION(BlueprintCallable, Category = "Menu")
void StartGame();
};
✅ PlayerController.cpp
#include "StrikeZonePlayerController.h"
#include "SZ_GameState.h"
#include "SZ_GameInstance.h"
#include "EnhancedInputSubsystems.h"
#include "Blueprint/UserWidget.h"
#include "PlayerCharacter.h"
#include "Drone.h"
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"
AStrikeZonePlayerController::AStrikeZonePlayerController()
: HUDWidgetClass(nullptr),
HUDWidgetInstance(nullptr),
MainMenuWidgetClass(nullptr),
MainMenuWidgetInstance(nullptr)
{
}
void AStrikeZonePlayerController::BeginPlay()
{
Super::BeginPlay();
// 현재 레벨이 Menu Level이라면, Main Menu 위젯 송출
FString CurrentMapName = GetWorld()->GetMapName();
if (CurrentMapName.Contains("MenuLevel"))
{
ShowMainMenu(false);
}
}
void AStrikeZonePlayerController::ShowGameHUD()
{
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());
}
}
ASZ_GameState* GameState = GetWorld() ? GetWorld()->GetGameState<ASZ_GameState>() : nullptr;
if (GameState)
{
GameState->UpdateHUD();
}
}
void AStrikeZonePlayerController::ShowMainMenu(bool bIsReStart)
{
// HUD가 사용중이라면 제거
if (HUDWidgetInstance)
{
HUDWidgetInstance->RemoveFromParent();
HUDWidgetInstance = nullptr;
}
// 메인 메뉴가 사용중이라면 제거
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->RemoveFromParent();
MainMenuWidgetInstance = nullptr;
}
// 1. 메인 메뉴 인스턴스 생성
// 2. 메인 메뉴 뷰포트에 송출
// 3. bShowMouseCursor = true, 메뉴에서 마우스 클릭 가능하도록 보여지게 설정
// 4. FInputModeUIOnly 구조체 생성, UI전용 입력모드로 설정
if (MainMenuWidgetClass)
{
MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->AddToViewport();
bShowMouseCursor = true;
SetInputMode(FInputModeUIOnly());
}
}
// bool 값에 따라 Button Text를 변경
if (UTextBlock* ButtonText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
{
if (bIsReStart)
{
ButtonText->SetText(FText::FromString(TEXT("Restart")));
}
else
{
ButtonText->SetText(FText::FromString(TEXT("Start")));
}
}
}
void AStrikeZonePlayerController::StartGame()
{
// 게임 시작 시, GameInstance의 데이터 초기화
if (USZ_GameInstance* GameInstance = Cast<USZ_GameInstance>(UGameplayStatics::GetGameInstance(this)))
{
GameInstance->CurrentLevelIndex = 0;
GameInstance->TotalScore = 0;
}
// 초기 맵 오픈
UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
// 게임 진행
SetPause(false)
}
📍 ShowMainMenu( ) / ShowGameHUD( ):
- 현재 사용중인 위젯이 있다면 UWidget::RemoveFromParent( ) 함수를 사용해 제거.
이 함수는 위젯이 부모로부터 제거될 때 호출되는 가상 함수로, 위젯이 관리되고 있는 경우 게임 뷰포트 서브시스템에서 제거하거나, 현재 부모 위젯으로 부터 제거하는 함수이다. - 각 함수에 맞는 위젯을 생성.
- bShowMouseCursor 를 통해 마우스를 숨길지 여부를 결정
- MainMenu의 경우 마우스를 통해 UI와 상호작용을 해야한다.
이때 UI에 포커스가 가도록 하려면 SetInputMode 계열 함수를 사용해 PlayerController가 어느 입력을 우선으로 처리할지 결정한다. - FInputModeUIOnly 구조체를 생성해 이것으로 SetInputMode를 설정하면, 플레이어의 마우스 입력과 키 입력이 먼저 UI로 전달된다.
이때 캐릭터 이동이나 시야 회전 등 게임 월드 입력은 잠시 비활성화되고, UI와의 상호작용에 집중할 수 있다. - ShowMainMenu( ) 함수의 경우 매개변수 bool 값에 따라 Button Text를 "Start" 혹은 "Restart" 로 변경
📍 StartGame( ):
- GameInstance의 데이터를 초기화
- UGameplayStatics::OpenLevel( ) 함수로 초기 맵 "BasicLevel"을 오픈
📌 GameState에서 게임 흐름에 따라 UI 호출
✅ GameState.cpp
void ASZ_GameState::BeginPlay()
{
Super::BeginPlay();
StartLevel();
GetWorldTimerManager().SetTimer(
HUDUpdateTimerHandle,
this,
&ASZ_GameState::UpdateHUD,
0.1f,
true
);
}
void ASZ_GameState::StartLevel()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (AStrikeZonePlayerController* SZ_PlayerController = Cast<AStrikeZonePlayerController>(PlayerController))
{
SZ_PlayerController->ShowGameHUD();
}
}
if (UGameInstance* GameInstance = GetGameInstance())
{
USZ_GameInstance* SZ_GameInstance = Cast<USZ_GameInstance>(GameInstance);
if (SZ_GameInstance)
{
CurrentLevelIndex = SZ_GameInstance->CurrentLevelIndex;
}
}
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
TArray<AActor*> FoundVolumes;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AItemSpawnVolume::StaticClass(), FoundVolumes);
const int32 ItemToSpawn = 40;
for (int32 i = 0; i < ItemToSpawn; ++i)
{
if (FoundVolumes.Num() > 0)
{
AItemSpawnVolume* SpawnVolume = Cast<AItemSpawnVolume>(FoundVolumes[0]);
if (SpawnVolume)
{
AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
{
SpawnedCoinCount++;
}
}
}
}
GetWorldTimerManager().SetTimer
(
LevelTimerHandle,
this,
&ASZ_GameState::OnLevelTimeUp,
LevelDuration,
false
);
}
void ASZ_GameState::OnGameOver()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (AStrikeZonePlayerController* SZ_PlayerController = Cast<AStrikeZonePlayerController>(PlayerController))
{
SZ_PlayerController->SetPause(true);
SZ_PlayerController->ShowMainMenu(true);
}
}
}
📌 Start 버튼 클릭 이벤트에 함수 바인딩
- MainMenu WBP에서 StartButton을 클릭해 Details 창을 확인
- Details창의 상단에 위치한 "is variable" 체크
- Details창 하단에 위치한 Events 탭에 "On Clicked" 추가
- Graph 화면으로 전환되는데 거기서 PlayerController에 정의한 Start Game() 함수 바인딩
UFUNCTION(BlueprintCallable, Category = "Menu")
void StartGame();
void AStrikeZonePlayerController::StartGame()
{
if (USZ_GameInstance* GameInstance = Cast<USZ_GameInstance>(UGameplayStatics::GetGameInstance(this)))
{
GameInstance->CurrentLevelIndex = 0;
GameInstance->TotalScore = 0;
}
UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
}
🔔 UI 전환 흐름
- 게임이 시작되면 Default Map으로 설정했던 Menu Level 오픈
- Start 버튼 클릭하면 PlayerController의 StartGame() 함수 호출
- GameInstance의 데이터 리셋되며, BasicLevel로 전환
- GameState의 BeginPlay() 호출 → StartLevel() 호출 → PlayerController::ShowGameHUD() 호출
'내배캠 > Unreal Engine' 카테고리의 다른 글
Widget Component로 월드에 UI 배치하기 (0) | 2025.02.07 |
---|---|
UI 애니메이션 효과 만들기 (0) | 2025.02.07 |
UI 위젯 설계와 실시간 데이터 연동 (0) | 2025.02.06 |
Game State와 Game Mode (0) | 2025.02.04 |
캐릭터 데미지 적용 (0) | 2025.02.04 |