오늘은 UMG (Unreal Motion Graphics)를 사용하여 게임 시작을 위한 메인 메뉴, 실시간 정보를 보여주는 인게임 HUD, 그리고 게임 오버 화면까지 전체 UI 흐름을 구축했다. C++의 데이터를 UI 위젯에 연결하는 방법을 배우고, 캐릭터의 머리 위에는 3D 위젯으로 체력 바를 띄웠다. 마지막으로 아이템 획득이나 지뢰 폭발 시 파티클과 사운드 이펙트를 추가하여, 게임을 훨씬 생동감 있고 완성도 높게 만들었다. ✨
SetText 방식)언리얼 엔진에서 UI를 만드는 현대적이고 강력한 시스템이다. 위젯 블루프린트라는 에셋을 통해 UI를 만드는데, 내부적으로 두 개의 큰 탭으로 나뉜다.
UI를 생성하고, 화면에 띄우고, 없애는 모든 관리는 PlayerController에서 하는 것이 정석이다. PlayerController는 플레이어와 게임 세계를 잇는 다리 역할을 하므로, 플레이어가 보게 될 화면(UI)을 제어하기에 가장 적합한 장소다.
CreateWidget: 위젯 블루프린트 클래스로부터 실제 화면에 띄울 위젯 인스턴스를 생성한다.AddToViewport: 생성된 위젯 인스턴스를 화면에 보이게 한다.RemoveFromParent: 화면에 떠 있는 위젯을 제거한다.SetInputMode: 입력 모드를 변경한다. 메뉴를 띄웠을 때는 마우스 커서가 보이고 UI만 조작할 수 있도록 UIOnly 모드로, 게임 중일 때는 캐릭터만 조작할 수 있도록 GameOnly 모드로 설정한다.UI의 텍스트(점수, 시간 등)는 게임 데이터가 변할 때마다 실시간으로 갱신되어야 한다. 방법은 크게 두 가지가 있는데, 매 프레임마다 UI가 데이터를 '가져오는' 바인딩 방식은 비효율적일 수 있다. 우리는 더 효율적인, 이벤트 기반의 SetText 방식을 사용했다.
UpdateScore 같은 함수를 호출한다 → 그 함수 안에서 해당 텍스트 블록의 내용을 SetText 노드로 직접 바꿔준다.일반적인 UI는 화면(스크린) 공간에 2D로 덧씌워지지만, 이 컴포넌트를 사용하면 UI 위젯을 3D 월드 공간 자체에 배치할 수 있다. 캐릭터의 머리 위에 뜨는 체력 바, NPC의 이름표 등이 대표적인 예다. Actor에 컴포넌트로 추가하고, WidgetClass에 보여줄 위젯 블루프린트를 지정해주면 된다.
게임에 생동감을 더하는 시청각 효과는 주로 GameplayStatics 라이브러리의 함수를 통해 구현한다.
SpawnEmitterAtLocation: 지정된 위치에 파티클 이펙트를 생성한다. "Fire and Forget" 방식이라 한번 생성되면 이펙트 스스로의 생명주기에 따라 동작하고 사라지며, 생성한 액터가 사라져도 영향을 받지 않는다.PlaySoundAtLocation: 지정된 위치에 사운드를 재생한다. 이 또한 "Fire and Forget" 방식이다.#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"
UCLASS()
class SPARTAPROJECT_API ASpartaPlayerController : public APlayerController
{
GENERATED_BODY()
protected:
// 1. 블루프린트에서 설정할 위젯 클래스들 (붕어빵 틀)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<class UUserWidget> HUDWidgetClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<class UUserWidget> MainMenuWidgetClass;
// 2. 실제 생성된 위젯 인스턴스들 (붕어빵)
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UI")
UUserWidget* HUDWidgetInstance;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UI")
UUserWidget* MainMenuWidgetInstance;
bool bIsRestart = false; // 재시작 상태인지 확인하는 변수
public:
// 3. UI를 띄우거나 숨기는 함수들
UFUNCTION(BlueprintCallable)
void ShowMainMenu(bool IsRestart);
UFUNCTION(BlueprintCallable)
void ShowGameHUD();
UFUNCTION(BlueprintCallable)
void StartGame();
};
#include "SpartaPlayerController.h"
#include "Blueprint/UserWidget.h"
#include "Kismet/GameplayStatics.h"
#include "SpartaGameInstance.h"
using namespace std;
void ASpartaPlayerController::ShowMainMenu(bool IsRestart)
{
// 1. 기존 HUD가 있다면 제거
if (HUDWidgetInstance)
{
HUDWidgetInstance->RemoveFromParent();
HUDWidgetInstance = nullptr;
}
// 2. 메인 메뉴 위젯 생성 및 화면에 추가
if (MainMenuWidgetClass)
{
MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
MainMenuWidgetInstance->AddToViewport();
}
bIsRestart = IsRestart;
// 3. 입력 모드를 UI 전용으로 바꾸고 마우스 커서 보이기
FInputModeUIOnly InputMode;
SetInputMode(InputMode);
bShowMouseCursor = true;
SetPause(true); // 게임 일시정지
}
void ASpartaPlayerController::ShowGameHUD()
{
// 1. 기존 메인 메뉴가 있다면 제거
if (MainMenuWidgetInstance)
{
MainMenuWidgetInstance->RemoveFromParent();
MainMenuWidgetInstance = nullptr;
}
// 2. HUD 위젯 생성 및 화면에 추가
if (HUDWidgetClass)
{
HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
HUDWidgetInstance->AddToViewport();
}
// 3. 입력 모드를 게임 전용으로 바꾸고 마우스 커서 숨기기
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
bShowMouseCursor = false;
SetPause(false); // 게임 재개
}
void ASpartaPlayerController::StartGame()
{
// 게임 재시작 시 게임 인스턴스의 데이터 초기화
if (USpartaGameInstance* GameInstance = Cast<USpartaGameInstance>(GetGameInstance()))
{
GameInstance->ResetGameData();
}
// 게임 레벨(Basic) 로드
UGameplayStatics::OpenLevel(GetWorld(), FName("Basic"));
}
#include "SpartaCharacter.h"
#include "Components/WidgetComponent.h"
#include "Blueprint/UserWidget.h"
#include "Components/TextBlock.h"
ASpartaCharacter::ASpartaCharacter()
{
// ... (기존 코드 생략) ...
// 1. 3D 위젯 컴포넌트 생성 및 설정
OverheadHPBar = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadHPBar"));
OverheadHPBar->SetupAttachment(GetMesh());
OverheadHPBar->SetWidgetSpace(EWidgetSpace::Screen); // 항상 화면을 바라보도록 설정
OverheadHPBar->SetDrawSize(FVector2D(150, 30));
}
void ASpartaCharacter::BeginPlay()
{
Super::BeginPlay();
// 2. 게임 시작 시 HP UI 초기화
UpdateOverheadHP();
}
// 3. 체력이 변경될 때마다(데미지, 힐링) 호출될 함수
void ASpartaCharacter::UpdateOverheadHP()
{
if (OverheadHPBar)
{
// 4. 위젯 컴포넌트로부터 실제 유저 위젯을 가져와서 텍스트 블록을 찾고 내용 업데이트
UUserWidget* HPWidget = OverheadHPBar->GetUserWidgetObject();
if (HPWidget)
{
UTextBlock* HPText = Cast<UTextBlock>(HPWidget->GetWidgetFromName("OverheadHP"));
if (HPText)
{
FString HPString = FString::Printf(TEXT("%d / %d"), (int32)Health, (int32)MaxHealth);
HPText->SetText(FText::FromString(HPString));
}
}
}
}
float ASpartaCharacter::TakeDamage(...)
{
// ... (데미지 처리 로직) ...
UpdateOverheadHP(); // 5. 데미지 입으면 HP UI 업데이트
return ActualDamage;
}
#include "MineItem.h"
#include "Kismet/GameplayStatics.h"
using namespace std;
void AMineItem::Explode()
{
// 1. 중복 폭발 방지
if (bHasExploded) return;
bHasExploded = true;
// 2. 폭발 파티클 스폰 (위치, 회전, 크기, 자동 소멸)
if (ExplosionParticle)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionParticle, GetActorLocation(), GetActorRotation(), FVector(2.f), true);
}
// 3. 폭발 사운드 재생
if (ExplosionSound)
{
UGameplayStatics::PlaySoundAtLocation(GetWorld(), ExplosionSound, GetActorLocation());
}
// ... (데미지 처리 로직) ...
DestroyItem();
}
[ProjectName].Build.cs 파일에 "UMG" 모듈을 추가하지 않아서였다. UI 기능을 사용하려면 엔진에 알려줘야 한다는 것을 배웠다.PlayerController의 SetPause(true) 함수를 호출하여 게임 월드의 시간을 멈춰야 완벽한 게임오버 상태가 된다는 것을 깨달았다.Destroy했는데, 픽업 효과로 스폰한 파티클이 계속 루프 재생되며 화면에 남아있었다. SpawnEmitterAtLocation으로 생성된 파티클은 독립적인 존재라는 것을 이해하고, 타이머와 람다 함수를 이용해 몇 초 뒤에 파티클 컴포넌트를 직접 파괴하는 로직을 추가하여 해결했다.| 개념 | 설명 | 비고 |
|---|---|---|
| UMG | 언리얼 엔진의 UI 디자인 시스템. | Widget Blueprint를 통해 시각적으로 디자인한다. |
| PlayerController | UI의 생성, 제거, 표시 전환 등 UI 흐름을 관리하는 주체. | CreateWidget, AddToViewport 함수가 핵심. |
SetText 방식 | C++에서 데이터가 변경될 때만 UI를 갱신하는 효율적인 이벤트 기반 방식. | 매 프레임 UI를 업데이트하는 바인딩 방식보다 권장됨. |
| Input Mode | 플레이어의 입력을 UI에 집중시킬지(UIOnly), 게임에 집중시킬지(GameOnly) 결정. | 메뉴 화면과 인게임 화면 전환 시 필수. |
| 3D 위젯 컴포넌트 | UI를 2D 화면이 아닌 3D 월드 공간에 배치하는 컴포넌트. | 캐릭터 체력 바, 이름표 등에 사용. |
SetPause | 게임의 시간을 멈추거나 재개하는 함수. | 게임오버, 일시정지 메뉴 구현에 필수. |
SpawnEmitter/Sound | "Fire and Forget" 방식으로 시청각 효과를 생성하는 함수. | 생성 주체와 독립적으로 동작한다. |