이번에는 게임에서 가장 첫번째로 마주하게 될 Title UI와 볼륨과 게임을 종료할 수 있는 메뉴 UI를 만들어 보도록 하자.
Title UI
타이틀 UI 같은 경우 최대한 심플하게 만들자는 것이 목표였다.
로고와 화면을 터치 시 게임에 접속하게 하는 버튼, 메뉴를 틀 수 있는 버튼만 넣자라는 생각이었다.
그리고 이러한 단조로운 구성을 애니메이션으로 넘어가자 라고 생각했다.
따라서 다음과 같은 구조를 형성했다.
게임의 로고 이미지 같은 경우는 ChatGPT에게 부탁해 제작했다.
이러한 로고가 2초 정도 페이드 인 아웃이 된 다음. 다음과 같은 화면이 6초에 걸쳐 페이드 인 된다.
애니메이션은 이렇게 구성하면 된다.
타이틀 화면이 심심하지 않게 타이틀 레벨을 만들고 이 레벨에서 시네마틱을 재생해 화면을 채워보겠다.
타이틀 시네마틱
타이틀 시네마틱 같은 경우
메인 맵에서 플레이어가 뒤 돌아 서 있고 그 플레이어 에게 포커스가 날려져 있다가 화면이 다가가면서 포커스가 맞춰지게 할 것이다.
타이틀 레벨에 액터를 배치해준다.
간단하게 타이틀 시네마틱을 만들어 준다.
TitleHUD Code
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "RASTitleWidget.generated.h"
/**
*
*/
UCLASS()
class PROJECTRAS_API URASTitleWidget : public UUserWidget
{
GENERATED_BODY()
public:
URASTitleWidget(const FObjectInitializer& ObjectInitializer);
virtual void NativeConstruct() override;
UFUNCTION(BlueprintCallable, Category = "UI")
void StartGame();
UFUNCTION()
void OnStartGameEnd();
UFUNCTION(BlueprintCallable, Category = "UI")
void OnMenuWidget();
protected:
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> StartButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> MenuButton;
UPROPERTY(Meta = (BindWidgetAnim), Transient)
TObjectPtr<class UWidgetAnimation> UI_StartGame;
UPROPERTY(Meta = (BindWidgetAnim), Transient)
TObjectPtr<class UWidgetAnimation> UI_TextAnim;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/RASTitleWidget.h"
#include "UI/RASUISubsystem.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Components/Button.h"
#include "Kismet/GameplayStatics.h"
URASTitleWidget::URASTitleWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
void URASTitleWidget::NativeConstruct()
{
Super::NativeConstruct();
if (UI_StartGame)
{
FWidgetAnimationDynamicEvent AnimationFinishedEvent;
AnimationFinishedEvent.BindDynamic(this, &URASTitleWidget::OnStartGameEnd);
BindToAnimationFinished(UI_StartGame, AnimationFinishedEvent);
PlayAnimation(UI_StartGame);
}
if (StartButton)
{
StartButton->SetIsEnabled(false);
StartButton->OnClicked.AddDynamic(this, &URASTitleWidget::StartGame);
}
if (MenuButton)
{
MenuButton->OnClicked.AddDynamic(this, &URASTitleWidget::OnMenuWidget);
}
}
void URASTitleWidget::StartGame()
{
UGameplayStatics::OpenLevel(GetWorld(), FName("L_Dungeons"));
}
void URASTitleWidget::OnStartGameEnd()
{
if (StartButton)
{
StartButton->SetIsEnabled(true);
}
if (UI_TextAnim)
{
PlayAnimation(UI_TextAnim, 0.f, 0);
}
}
void URASTitleWidget::OnMenuWidget()
{
URASUISubsystem* UISubsystem = GetGameInstance()->GetSubsystem<URASUISubsystem>();
if (UISubsystem)
{
UISubsystem->ShowMenu();
}
}
타이틀 UI에서는 별거 없이 버튼에 대한 바인딩 역할만 하면된다.
다만 SubSystem이 등장하게 되는데 이는 추후 Menu에서 설명하겠다.
Menu UI
메뉴 UI는 볼륨의 사이즈를 줄이는 슬라이더와 게임 종료 버튼을 포함하고 있다.
다만 생각해보면 메뉴 UI는 이 레벨에만 사용할 것이 아니라 계속 사용해야할 것이기 때문에 GameInstance가 가지고 있는 것이 바람직하다.
문제는 현재 GameInstace는 각종 몬스터와 플레이어의 Data를 저장하고 불러오는 역할을 하기에 적합하지 않다.
따라서 GameInstace가 생성된 후 생성되는 Subsystem을 이용해 UI를 가지고 있게끔 하는 것이 포인트이다.
Subsystem
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "RASUISubsystem.generated.h"
/**
*
*/
UCLASS()
class PROJECTRAS_API URASUISubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
URASUISubsystem();
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
UFUNCTION(BlueprintCallable)
void ShowMenu();
UFUNCTION(BlueprintCallable)
void HideMenu();
protected:
UPROPERTY(EditDefaultsOnly)
TSubclassOf<class URASMenuWidget> MenuWidgetClass;
UPROPERTY()
TObjectPtr<class URASMenuWidget> MenuWidget;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/RASUISubsystem.h"
#include "UI/RASMenuWidget.h"
#include "Kismet/GameplayStatics.h"
#include "Controller/Player/RASTitlePlayerController.h"
URASUISubsystem::URASUISubsystem()
{
static ConstructorHelpers::FClassFinder<URASMenuWidget> MenuWidgetBPClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/1_ProjectRAS/UI/WBP_Menu.WBP_Menu_C'"));
if (MenuWidgetBPClass.Class)
{
MenuWidgetClass = MenuWidgetBPClass.Class;
}
}
void URASUISubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
void URASUISubsystem::Deinitialize()
{
Super::Deinitialize();
}
void URASUISubsystem::ShowMenu()
{
if (!MenuWidget && MenuWidgetClass)
{
UWorld* World = GetWorld();
if (!World) return;
MenuWidget = CreateWidget<URASMenuWidget>(
/*Outer=*/GetGameInstance(),
MenuWidgetClass);
MenuWidget->AddToViewport(100);
}
if (MenuWidget)
{
MenuWidget->SetVisibility(ESlateVisibility::Visible);
UGameplayStatics::SetGamePaused(GetWorld(), true);
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (PC)
{
FInputModeUIOnly InputMode;
InputMode.SetWidgetToFocus(MenuWidget->GetCachedWidget());
PC->SetInputMode(InputMode);
PC->bShowMouseCursor = true;
}
}
}
void URASUISubsystem::HideMenu()
{
if (MenuWidget)
{
MenuWidget->SetVisibility(ESlateVisibility::Collapsed);
UGameplayStatics::SetGamePaused(GetWorld(), false);
APlayerController* PC = GetWorld()->GetFirstPlayerController();
ARASTitlePlayerController* TitlePC = Cast<ARASTitlePlayerController>(PC);
if (TitlePC)
{
TitlePC->SetTitleUI();
}
else
{
FInputModeGameOnly InputMode;
PC->SetInputMode(InputMode);
PC->bShowMouseCursor = false;
}
}
}
이 서브 시스템에서 Menu 클래스를 가지고 있다 Show or Hide 요청이 들어오면 UI의 포커스를 변경하고 게임을 멈추거나 재생할 수 있다.
Menu UI Code
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "RASMenuWidget.generated.h"
/**
*
*/
UCLASS()
class PROJECTRAS_API URASMenuWidget : public UUserWidget
{
GENERATED_BODY()
public:
URASMenuWidget(const FObjectInitializer& ObjectInitializer);
virtual void NativeConstruct() override;
protected:
UFUNCTION(BlueprintCallable, Category = "UI")
void OffMenuWidget();
UFUNCTION(BlueprintCallable, Category = "UI")
void ExitGame();
UFUNCTION(BlueprintCallable, Category = "UI")
void SetBGMVolume(float Value);
UFUNCTION(BlueprintCallable, Category = "UI")
void SetSFXVolume(float Value);
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> ExitButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> OffButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class USlider> BGMSlider;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class USlider> SFXSlider;
UPROPERTY(EditAnywhere)
TObjectPtr<class USoundMix> SoundMix;
UPROPERTY(EditAnywhere)
TObjectPtr<class USoundClass> BGMClass;
UPROPERTY(EditAnywhere)
TObjectPtr<class USoundClass> SFXClass;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/RASMenuWidget.h"
#include "UI/RASUISubsystem.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Components/Button.h"
#include "Components/Slider.h"
#include "Kismet/GameplayStatics.h"
URASMenuWidget::URASMenuWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
void URASMenuWidget::NativeConstruct()
{
Super::NativeConstruct();
if (ExitButton)
{
ExitButton->OnClicked.AddDynamic(this, &URASMenuWidget::ExitGame);
}
if (OffButton)
{
OffButton->OnClicked.AddDynamic(this, &URASMenuWidget::OffMenuWidget);
}
if (BGMSlider)
{
BGMSlider->OnValueChanged.AddDynamic(this, &URASMenuWidget::SetBGMVolume);
}
if (SFXSlider)
{
SFXSlider->OnValueChanged.AddDynamic(this, &URASMenuWidget::SetSFXVolume);
}
}
void URASMenuWidget::OffMenuWidget()
{
URASUISubsystem* UISubsystem = GetGameInstance()->GetSubsystem<URASUISubsystem>();
if (UISubsystem)
{
UISubsystem->HideMenu();
}
}
void URASMenuWidget::ExitGame()
{
UWorld* World = GetWorld();
if (!World) return;
APlayerController* PC = World->GetFirstPlayerController();
UKismetSystemLibrary::QuitGame(
this,
PC,
EQuitPreference::Quit,
false
);
}
void URASMenuWidget::SetBGMVolume(float Value)
{
Value = FMath::Clamp(Value, 0.0f, 1.0f);
UGameplayStatics::SetSoundMixClassOverride(
GetWorld(),
SoundMix,
BGMClass,
Value,
1.f,
0.5f
);
UGameplayStatics::PushSoundMixModifier(GetWorld(), SoundMix);
}
void URASMenuWidget::SetSFXVolume(float Value)
{
Value = FMath::Clamp(Value, 0.0f, 1.0f);
UGameplayStatics::SetSoundMixClassOverride(
GetWorld(),
SoundMix,
SFXClass,
Value,
1.f,
0.5f
);
UGameplayStatics::PushSoundMixModifier(GetWorld(), SoundMix);
}
결과
'Unreal Engine 5 > ProjectRAS' 카테고리의 다른 글
[UE5] ProjectRAS - Npc 만들기 (3) | 2025.07.01 |
---|---|
[UE5] ProjectRAS - 미니맵, 월드맵 만들기 (1) | 2025.06.23 |
[UE5] ProjectRAS - 몬스터 스폰하기, Clothes 적용하기 (0) | 2025.05.28 |
[UE5] ProjectRAS - UI 스킬 쿨타임 과 포션 마시기 (0) | 2025.05.21 |
[UE5] ProjectRAS - 보스 패턴 제작하기 (0) | 2025.05.16 |