📍 4주차 1강
플레이어에게 체력, 미니맵, 퀘스트 등 게임 정보를 화면에 보여주는 시스템
언리얼 엔진에서는 두 가지 방식이 있음:
Canvas 기반 HUD
UMG (Unreal Motion Graphics)
전자는 예전 방식이니까 UMG 방식으로 만듦!

21.5-24" monitor = 1920 x 1080 (16:9)



Widget(HUD & UI 요소)
: 플레이어의 입력과 상호작용하며 화면에 표시되는 요소
→ PlayerController가 입력 받고 캐릭터 조종하니까 거기에서 구현
// PlayerController.h
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
TSubclassOf<UUserWidget> HUDWidgetClass;
// PlayerController.cpp
#include "Blueprint/UserWidget.h"
void ASpartaPlayerController::BeginPlay()
{
// ...
if (HUDWidgetClass)
{
// HUDWidgetClassf라는 instance 만들어서 HUDWidget에 할당해라!
UUserWidget* HUDWidget = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWiget) // 안전코드
{
HUDWidget->AddToViewport(); // 생성된 위젯을 뷰포트에 추가해줘
}
}
}
SpartaProject.Build.cs 에 "UMG"를 추가해야 빌드가 됨// SpartaProject.Build.cs
// 이 프로젝트에서 필수적으로 사용하는 Engine 기능들
PublicDependencyModuleNames.AddRange(new string[] {
"Core", // 엔진의 핵심 기능
"CoreUObject", // 리플렉션, 가비지 컬렉터 (UObject 기반 시스템)
"Engine", // 게임 엔진의 주요 기능들
"InputCore", // 기본 입력 처리 시스템
"EnhancedInput", // 향상된 입력 시스템 (신규 입력 방식)
"UMG" // UI 제작용 UMG (Widget Blueprint) ✅
});
PlayerController에서 구현했으니까
BP_SpartaPlayerController에서 설정하기


▶ 아까 설정한 모니터랑 뷰포트 크기 맞추기
▶ 새 창으로 보기


직관적이고 편리하지만 바인딩 방식은 Tick 함수처럼 매 프레임마다 점수 갱신 중
UI가 복잡하면 부하가 있음
→ SetText로 갱신이 될 때만 점수 올리는 방식
GameState 갱신
PlayerController Input을 입력 받고 관리하는 쪽
// PlayerController.h
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HUD")
TSubclassOf<UUserWidget> HUDWidgetClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HUD")
UUserWidget* HUDWidgetInstance;
// HUDWidget을 GameState로 넘겨주기
UFUNCTION(BlueprintCallable, Category = "HUD")
UUserWidget* GetHUDWidget() const;
// PlayerController.cpp
ASpartaPlayerController::ASpartaPlayerController() // 생성자 정의
: InputMappingContext(nullptr),
MoveAction(nullptr),
JumpAction(nullptr),
LookAction(nullptr),
SprintAction(nullptr), // BP에서 할당 할 거니까 여기선 nullptr로 초기화
HUDWidgetClass(nullptr)
{
}
void ASpartaPlayerController::BeginPlay() // 플레이 시작 시 호출되는 함수
{
// ...
if (HUDWidgetClass)
{
// HUDWidgetClassf라는 instance 만들어서 HUDWidget에 할당해라!
HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWidgetInstance) // 안전코드
{
HUDWidgetInstance->AddToViewport(); // 생성된 위젯을 뷰포트에 추가해줘
}
}
}
// get 함수 만들기
UUserWidget* ASpartaPlayerController::GetHUDWidget() const
{
return HUDWidgetInstance;
}
// GameState.h
void UpdateHUD();
// GameState.cpp
#include "SpartaPlayerController.h"
#include "Components/TextBlock.h"
#include "Blueprint/UserWidget.h"
void ASpartaGameState::BeginPlay()
{
Super::BeginPlay();
UpdateHUD();
StartLevel();
}
void ASpartaGameState::StartLevel()
{
// ...
UpdateHUD();
}
void ASpartaGameState::OnGameOver()
{
UpdateHUD();
UE_LOG(LogTemp, Warning, TEXT("Game Over!!"));
}
void ASpartaGameState::UpdateHUD()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController);
{
if (UUserWidget* HUDWidget = SpartaPlayerController->GetHUDWidget())
{
// UI 요소들 가져오기
if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
{
float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle); // 타이머가 몇 초 남았나?
TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time: %.1f"), RemainingTime))); // String -> Text 변환해주는 작업
}
if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
{
// GameInstance에 TotalScore 있음
if (UGameInstance* GameInstance = GetGameInstance())
{
USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(GameInstance);
if (SpartaGameInstance)
{
ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score: %d"), SpartaGameInstance->TotalScore)));
}
}
}
if (UTextBlock* LevelIndexText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
{
LevelIndexText->SetText(FText::FromString(FString::Printf(TEXT("Level: %d"), CurrentLevelIndex + 1)));
}
}
}
}
}
// GameState.h
FTimerHandle HUDUpdateTimerHandle;
// GameState.cpp
void ASpartaGameState::BeginPlay()
{
Super::BeginPlay();
UpdateHUD();
StartLevel();
GetWorldTimerManager().SetTimer(
HUDUpdateTimerHandle,
this, // 이 객체에서
&ASpartaGameState::UpdateHUD, // 시간이 끝나면 이 함수 불러~
0.1f, // 0.1 초마다 UpdateHUD 불러
true // 반복
);
}
// PlayerController.cpp
#include "SpartaGameState.h"
ASpartaPlayerController::ASpartaPlayerController() // 생성자 정의
: InputMappingContext(nullptr),
MoveAction(nullptr),
JumpAction(nullptr),
LookAction(nullptr),
SprintAction(nullptr), // BP에서 할당 할 거니까 여기선 nullptr로 초기화
HUDWidgetClass(nullptr),
HUDWidgetInstance(nullptr) // ✅
{
}
void ASpartaPlayerController::BeginPlay()
{
// ...
if (HUDWidgetClass)
{
HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWidgetInstance)
{
HUDWidgetInstance->AddToViewport();
}
}
ASpartaGameState* SpartaGameState = GetWorld() ? GetWorld()->GetGameState<ASpartaGameState>() : nullptr;
if (SpartaGameState)
{
SpartaGameState->UpdateHUD();
}
}
UUserWidget* ASpartaPlayerController::GetHUDWidget() const
{
return HUDWidgetInstance;
}
// PlayerController.h


레벨 넘어가도 Score 유지 / Time 갱신됨!