C++과 Unreal Engine으로 3D 게임 개발 11

김여울·2025년 7월 20일
0

내일배움캠프

목록 보기
47/114
post-thumbnail

📍 4주차 1강

10. UI 위젯 설계와 실시간 데이터 연동하기

10.1 UMG (Unreal Motion Graphics)

a. HUD (Head-Up Display)

플레이어에게 체력, 미니맵, 퀘스트 등 게임 정보를 화면에 보여주는 시스템
언리얼 엔진에서는 두 가지 방식이 있음:

  • Canvas 기반 HUD

    • AHUD 클래스 상속
    • 텍스트, 이미지 등 2D 요소 직접 그리기
    • 레거시 방식이라 요즘은 잘 안 씀
  • UMG (Unreal Motion Graphics)

    • Widget Blueprint를 사용하는 UI 시스템
    • 버튼, 텍스트, 이미지 등 다양한 위젯 제공

전자는 예전 방식이니까 UMG 방식으로 만듦!

b. Widget Blueprint

  • 언리얼에서 UI를 시각적으로 만드는 블루프린트
  • Text, Button, Image 같은 위젯을 드래그 앤 드롭으로 배치
  • 이렇게 만든 UI가 게임 화면에 표시됨

UI > WDP_HUD

c. UI 요소

d. UI Widget Design (점수, 시간, 레벨 표시)

1️⃣ Canvas Panel(PANEL) 을 가장 먼저 위치 (도화지)

2️⃣ Screen Size 설정

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

3️⃣ Text(COMMON) 배치

  • Text 변경
  • Size
  • Justification 정렬
  • Font Family → Font import (.ttf / .otf 등)

4️⃣ Slot Position / Size 맞추기

5️⃣ 우측 상단에서 UI 요소 이름 설정

10.2 HUD Widget 화면에 표시

a. PlayerController에서 HUD 생성 로직 추가

Widget(HUD & UI 요소)
: 플레이어의 입력과 상호작용하며 화면에 표시되는 요소
→ PlayerController가 입력 받고 캐릭터 조종하니까 거기에서 구현

HUD 배치 변수 생성

// 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();	// 생성된 위젯을 뷰포트에 추가해줘
		}
	}
}

UMG 추가

  • CreateWidget을 쓰려면 UMG 모듈이 필요
  • SpartaProject.Build.cs 에 "UMG"를 추가해야 빌드가 됨
  • 프로젝트가 compile 할 때 어떤 모듈을 포함할지 설정하는 파일
// SpartaProject.Build.cs

// 이 프로젝트에서 필수적으로 사용하는 Engine 기능들
PublicDependencyModuleNames.AddRange(new string[] { 
	"Core",            // 엔진의 핵심 기능
	"CoreUObject",     // 리플렉션, 가비지 컬렉터 (UObject 기반 시스템)
	"Engine",          // 게임 엔진의 주요 기능들
	"InputCore",       // 기본 입력 처리 시스템
	"EnhancedInput",   // 향상된 입력 시스템 (신규 입력 방식)
	"UMG"              // UI 제작용 UMG (Widget Blueprint) ✅
});

b. HUD Widget 테스트

HUD Widget - PlayerController

PlayerController에서 구현했으니까
BP_SpartaPlayerController에서 설정하기

HUD Widget - 게임 화면


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

10.3 HUD Widget과 GameState 연동

a. GameState에서 점수 데이터 바인딩


직관적이고 편리하지만 바인딩 방식은 Tick 함수처럼 매 프레임마다 점수 갱신 중
UI가 복잡하면 부하가 있음
→ SetText로 갱신이 될 때만 점수 올리는 방식

b. SetText 방식으로 위젯 갱신

1️⃣ HUDWidget을 GameState로 넘겨주기

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;
}

2️⃣-1️⃣ GameState에서 위젯 갱신

// 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)));
				}
			}
		}
	}
}

2️⃣-2️⃣ Timer ← 갱신

  • 남은 시간 표시용 HUD를 SetText로 계속 갱신해야 함
  • Tick() 함수에서 매 프레임 갱신하면 비효율적 (바인딩이랑 다를 게 없음)
    → 그래서 Timer 함수를 써서 주기적으로만 갱신하는 방식이 더 효율적
// 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 갱신됨!

0개의 댓글