📌 WBP 생성하기
부모 클래스인 User Widget을 선택해 WBP를 생성한다.
🔎 Widget Blueprint 란?
언리얼에서 UI를 시각적으로 설계할 수 있도록 제공되는 에디터용 블루프린트이다.
이 WBP에서 TextBlock, Button, Image 등 다양한 UI요소를 드래그 앤 드롭으로 간편하게 배치할 수 있다.
여기서 만든 WBP를 PlayerController에서 ViewPort에 표시하도록 할 수 있게한다.
🔎 UI 요소란?
WBP 내부에서 좌측을 살펴보면 Palette 창이 보인다.
여기서 다양한 UI 요소를 드래그해서 드롭하면 아주 간편하게 Viewport에 배치할 수 있다.
- Text: TextBlock을 의미하고, 캐릭터 체력이나 점수, 남은시간, 킬로그 등 많은 곳에 사용된다.
- Button: "게임 시작", "게임 종료", "옵션" 등 사용자가 클릭할 수 있는 이벤트 버튼으로 사용된다.
- Progress Bar: 체력 게이지나 로딩 게이지 등 시각적 표현이 필요할 때 사용된다.
📌 WBP 에서 점수, 시간, 레벨 표시를 위한 UI Widget 디자인
- 좌측 Palette 창에서 Canvers Panel를 배치한다.
- 그 다음 Text block를 배치해, 우측 Details 창에서 위치와 크기 등을 조절하며 UI의 위치를 잡아준다.
- 필요한 경우 폰트도 Import하여 사용할 수 있다.
- 우측 Details 창의 바로 아래를 살펴보면 Text Block의 이름을 지정해줄 수 있다.
- 추후 C++ 코드에서 UUserWidget::GetWidgetFormName(const FName& Name) 메서드를 사용해
해당 Text Block의 이름으로 검색해 위젯을 가져오기 때문에 각 텍스트 기능에 맞춰 이름을 설정해두자.
- 우측 상단의 Screen Size를 조절해 각 모니터 사이즈에서 어떻게 보이게 될지 확인할 수 있다.
📌 PlayerController에서 HUD 생성 로직 추가
✅ 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();
// 에디터에서 UMG 위젯 클래스를 할당 받을 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HUD")
TSubclassOf<UUserWidget> HUDWidgetClass;
// UUserWidget 인스턴스 저장 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HUD")
UUserWidget* HUDWidgetInstance;
// GameState에서 HUD Widget Instance에 접근하기 위한 getter 함수
UFUNCTION(BlueprintCallable, Category = "HUD")
UUserWidget* GetHUDWidget() const { return HUDWidgetInstance; }
};
✅ PlayerController.cpp
#include "StrikeZonePlayerController.h"
#include "SZ_GameState.h"
#include "Blueprint/UserWidget.h"
AStrikeZonePlayerController::AStrikeZonePlayerController() : HUDWidgetInstance(nullptr) {}
void AStrikeZonePlayerController::BeginPlay()
{
Super::BeginPlay();
if (HUDWidgetClass)
{
// 위젯 생성
HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
if (HUDWidgetInstance)
{
// 위젯 viewport에 표시
HUDWidgetInstance->AddToViewport();
}
}
// 게임이 시작되고 월드가 있다면 GameState를 가져온다.
// GameState의 UpdateHUD() 함수를 호출해 게임이 시작되면 바로 HUD 갱신
ASZ_GameState* GameState = GetWorld() ? GetWorld()->GetGameState<ASZ_GameState>() : nullptr;
if (GameState)
{
GameState->UpdateHUD();
}
}
🚫 빌드 오류
PlayerController에서 코드를 작성하고 빌드하게 되면, 아래와 같은 오류가 발생하게 된다.
PlayerController에서 CreateWidget이 작동하려면, UMG 모듈이 빌드 설정에 추가 되어있어야한다.
ProjectName.Build.cs 라는 코드를 열어 아래와 같이 "UMG" 을 추가해주면 오류가 해결된다.
📌 HUD Widget과 Game State 데이터 연동
🔎WBP에서 바인딩 방식
- Text Block 클릭 후 우측 Details 창에서 체인 아이콘을 눌러 "Create Binding" 클릭
- Event Graph로 넘어오게되면 GameInstance 가져오기
- GameInstance에 있는 Total Score를 Text로 변환해서 Return Node에 값 전달
🔴 단점:
이 바인딩 방식은 초심자에게 조금 더 간편하게 데이터를 연동할 수 있게 한다는 장점이 있다.
하지만 Tick과 같이 매 프레임마다 데이터를 연동해 갱신하기 때문에, UI가 많고 복잡해질수록 성능에 이슈가 생길 수 있다.
따라서 C++에서 UTextBlock::SetText(FText Intext) 함수를 데이터 갱신이 필요한 시점에 직점 호출하는 방식으로 사용한다면,
필요할 때만 UI를 업데이트할 수 있어 퍼포먼스에 유리할 수 있다.
🔎 SetText 방식으로 데이터 반영해 위젯 갱신하기
✅ GameState.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "SZ_GameState.generated.h"
UCLASS()
class STRIKEZONE_API ASZ_GameState : public AGameState
{
GENERATED_BODY()
public:
ASZ_GameState();
virtual void BeginPlay() override;
FTimerHandle LevelTimerHandle;
FTimerHandle HUDUpdateTimerHandle;
void StartLevel();
void EndLevel();
void UpdateHUD();
};
✅ GameState.cpp
#include "SZ_GameState.h"
#include "SZ_GameInstance.h"
#include "StrikeZonePlayerController.h"
#include "PlayerCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "ItemSpawnVolume.h"
#include "CoinItem.h"
#include "Components/TextBlock.h"
#include "Components/ProgressBar.h"
#include "Blueprint/UserWidget.h"
ASZ_GameState::ASZ_GameState()
{
Score = 0;
SpawnedCoinCount = 0;
CollectedCoinCount = 0;
LevelDuration = 30.0f;
CurrentLevelIndex = 0;
MaxLevels = 3;
}
void ASZ_GameState::BeginPlay()
{
Super::BeginPlay();
StartLevel();
GetWorldTimerManager().SetTimer(
HUDUpdateTimerHandle,
this,
&ASZ_GameState::UpdateHUD,
0.1f,
true
);
}
void ASZ_GameState::UpdateHUD()
{
if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
{
if (AStrikeZonePlayerController* SZ_PlayerController = Cast<AStrikeZonePlayerController>(PlayerController))
{
if (UUserWidget* HUDWidget = SZ_PlayerController->GetHUDWidget())
{
// Time TextBlock을 가져와서 남은 시간 데이터를 연동해 SetText로 문자열 설정
if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
{
float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time: %.1f"), RemainingTime)));
}
// Score TextBlock을 가져와서 남은 시간 데이터를 연동해 SetText로 문자열 설정
if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
{
if (UGameInstance* GameInstance = GetGameInstance())
{
USZ_GameInstance* SZ_GameInstance = Cast<USZ_GameInstance>(GameInstance);
if (SZ_GameInstance)
{
ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score: %d"), SZ_GameInstance->TotalScore)));
}
}
}
// Level TextBlock을 가져와서 남은 시간 데이터를 연동해 SetText로 문자열 설정
if (UTextBlock* LevelIndexText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
{
LevelIndexText->SetText(FText::FromString(FString::Printf(TEXT("Level: %d"), CurrentLevelIndex + 1)));
}
// HP_Bar ProgressBar를 가져와서 체력 게이지 연동
if (UProgressBar* HP_Bar = Cast<UProgressBar>(HUDWidget->GetWidgetFromName(TEXT("HP_Bar"))))
{
if (APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(SZ_PlayerController->GetPawn()))
{
float CurrentHP = PlayerCharacter->GetHealth();
float MaxHP = PlayerCharacter->GetMaxHealth();
float HP_Percent = FMath::Clamp(CurrentHP / MaxHP, 0.0f, 1.0f);
HP_Bar->SetPercent(HP_Percent);
}
}
}
}
}
}
📍UpdateHUD( ):
- 이전에 PlayerController에 UUserWidget Instance를 반환하는 Getter 함수인 GetHUDWidget( ) 를 호출해 위젯 인스턴스를 가져온다.
- GetWidgetFormName( ) 함수를 통해 에디터에서 설정했던 TextBlock의 이름으로 해당 TextBlock을 가져온다.
- UTextBlock::SetText( ) 함수로 데이터의 값을 연동해 텍스트를 설정한다.
- UProgressBar::SetPercent( ) 함수로 게이지를 연동
📍BeginPlay( ) 에서 UpdateHUD 함수 타이머로 반복 호출:
- HUDUpdateTimerHandle을 헤더 파일에 선언
- 0.1초 마다 UpdateHUD 함수를 반복적으로 호출하도록 SetTimer 설정
BeginPlay 함수 이외에도 레벨이 시작될 때, 캐릭터의 데미지를 입었을 때, 레벨이 끝날 때 등의 상황에
추가적으로 UpdateHUD 함수를 호출해서 데이터의 변동사항을 UI에 갱신되도록 할 수 있다.
🔔 데이터 반영한 UI 업데이트
코인을 획득할 때마다 Score가 갱신되고, Time도 실시간으로 갱신되는 모습을 볼 수 있고,
체력 게이지도 포션을 먹었을 때 정상적으로 회복되는 것을 확인할 수 있다.
'내배캠 > Unreal Engine' 카테고리의 다른 글
UI 애니메이션 효과 만들기 (0) | 2025.02.07 |
---|---|
게임 메뉴 UI 디자인 / 게임 흐름에 맞게 UI 전환 (0) | 2025.02.06 |
Game State와 Game Mode (0) | 2025.02.04 |
캐릭터 데미지 적용 (0) | 2025.02.04 |
가비지 컬렉션과 메모리 관리 (0) | 2025.01.31 |