본문 바로가기

C++에서 위젯 생성 및 제어

C++에서 Widget Blueprint 클래스를 참조하고 CreateWidget로 생성해 뷰포트와 데이터를 제어합니다.

이전 절에서 우리는 UMG 시스템의 개요와 위젯의 계층적 구조에 대해 알아보았습니다.

UMG는 시각적 디자인을 위해 블루프린트를 주로 사용하지만, 실제 게임 개발에서는 C++ 코드에서 위젯을 생성하고 제어해야 하는 경우가 많습니다.

특히 게임의 핵심 UI 관리 로직, 성능이 중요한 업데이트, 또는 복잡한 데이터 바인딩 등은 C++로 구현하는 것이 효율적입니다.

이번 절에서는 C++ 코드에서 UMG 위젯을 생성하고, 화면에 추가하며, 속성을 제어하고, 제거하는 방법에 대해 자세히 살펴보겠습니다.


C++에서 위젯 생성의 필요성

대부분의 UI는 위젯 블루프린트(.uasset)로 존재합니다.

C++에서 이러한 위젯 블루프린트 인스턴스를 생성하고 화면에 띄우는 방식은 다음과 같은 경우에 활용합니다.

  • 동적 생성: 게임 플레이 중 특정 조건(예: 게임 시작 시 HUD 표시, 아이템 획득 시 알림 팝업)에 따라 UI를 동적으로 생성해야 할 때.
  • 중앙 집중식 관리: APlayerController나 AHUD 클래스에서 모든 UI 위젯의 생명 주기를 관리하고 싶을 때.
  • 데이터 주입: C++의 게임플레이 데이터를 UI 위젯에 전달하여 표시해야 할 때.
  • 성능 최적화: 매우 빈번하게 업데이트되거나 복잡한 연산이 필요한 UI 로직을 C++로 구현할 때.

위젯 블루프린트 클래스 참조

C++에서 위젯 블루프린트 인스턴스를 생성하려면, 먼저 해당 위젯 블루프린트의 클래스(Class)를 참조해야 합니다.

이는 UPROPERTY(EditDefaultsOnly)와 TSubclassOf<UUserWidget>를 사용하여 수행하는 것이 가장 일반적입니다.

MyPlayerController.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"

// UUserWidget을 사용하기 위해 포함
class UUserWidget;

UCLASS()
class MYPROJECT_API AMyPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    AMyPlayerController();

protected:
    virtual void BeginPlay() override;

    // 블루프린트에서 할당할 위젯 블루프린트 클래스에 대한 참조
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
    TSubclassOf<UUserWidget> HUDWidgetClass; // 예를 들어, BP_HUD_Main 위젯 블루프린트 클래스 할당

    // 생성된 위젯 인스턴스에 대한 포인터
    UPROPERTY()
    UUserWidget* CurrentHUDWidget;

    // 인벤토리 위젯 클래스
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
    TSubclassOf<UUserWidget> InventoryWidgetClass;

    UPROPERTY()
    UUserWidget* CurrentInventoryWidget;

public:
    // HUD를 화면에 표시하는 함수
    UFUNCTION(BlueprintCallable, Category = "UI")
    void ShowHUD();

    // HUD를 화면에서 제거하는 함수
    UFUNCTION(BlueprintCallable, Category = "UI")
    void HideHUD();

    // 인벤토리 위젯을 토글하는 함수
    UFUNCTION(BlueprintCallable, Category = "UI")
    void ToggleInventory();

    virtual void PlayerTick(float DeltaTime) override;
};
  • TSubclassOf<UUserWidget> HUDWidgetClass;: 이 변수는 에디터에서 UMG 위젯 블루프린트 에셋을 할당할 수 있도록 해줍니다. TSubclassOf는 특정 C++ 클래스(여기서는 UUserWidget)를 상속받는 모든 블루프린트 클래스를 드롭다운 목록으로 보여줍니다.

위젯 인스턴스 생성 및 화면에 추가하기

CreateWidget() 함수를 사용하여 위젯 인스턴스를 생성하고, AddToViewport() 또는 AddToPlayerScreen() 함수를 사용하여 화면에 표시합니다.

MyPlayerController.cpp
#include "MyPlayerController.h"
#include "Blueprint/UserWidget.h" // CreateWidget 함수를 위해 포함

AMyPlayerController::AMyPlayerController()
{
    // ...
}

void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();

    // 게임 시작 시 HUD를 자동으로 표시 (예시)
    ShowHUD();
}

void AMyPlayerController::ShowHUD()
{
    if (!IsLocalController()) return;
    // 1. HUDWidgetClass가 유효한지 확인
    if (HUDWidgetClass)
    {
        // 2. 이미 HUD가 생성되어 있다면 다시 생성하지 않음
        if (CurrentHUDWidget)
        {
            // 화면에서 분리된 인스턴스는 다시 붙인 뒤 표시합니다.
            if (!CurrentHUDWidget->IsInViewport()) CurrentHUDWidget->AddToViewport();
            CurrentHUDWidget->SetVisibility(ESlateVisibility::Visible);
            return;
        }

        // 3. 위젯 인스턴스 생성
        // CreateWidget(소유 객체, 위젯 클래스, 선택적 인스턴스 이름)
        // 여기서는 로컬 PlayerController를 소유 객체로 사용
        CurrentHUDWidget = CreateWidget<UUserWidget>(this, HUDWidgetClass, TEXT("MainHUD")); // 이름 부여 (선택 사항)

        // 4. 위젯이 성공적으로 생성되었는지 확인
        if (CurrentHUDWidget)
        {
            // 5. 위젯을 뷰포트에 추가하여 화면에 표시
            CurrentHUDWidget->AddToViewport();
            UE_LOG(LogTemp, Warning, TEXT("HUD Widget spawned and added to viewport."));
        }
        else
        {
            UE_LOG(LogTemp, Error, TEXT("Failed to create HUD Widget."));
        }
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("HUDWidgetClass is not set in PlayerController."));
    }
}

void AMyPlayerController::HideHUD()
{
    if (CurrentHUDWidget)
    {
        // 뷰포트에서 위젯 제거
        CurrentHUDWidget->RemoveFromParent();
        // 포인터를 nullptr로 초기화하여 다시 생성할 수 있도록 함
        CurrentHUDWidget = nullptr;
        UE_LOG(LogTemp, Warning, TEXT("HUD Widget removed from viewport."));
    }
}

void AMyPlayerController::ToggleInventory()
{
    if (!IsLocalController()) return;
    if (InventoryWidgetClass)
    {
        if (CurrentInventoryWidget && CurrentInventoryWidget->IsInViewport())
        {
            // 인벤토리 위젯이 이미 표시되어 있다면 제거
            CurrentInventoryWidget->RemoveFromParent();
            UE_LOG(LogTemp, Warning, TEXT("Inventory Widget removed."));

            // 이 예제의 기본 게임 입력 모드로 복귀 (임의의 이전 상태 복원은 아님)
            SetInputMode(FInputModeGameOnly());
            bShowMouseCursor = false;
        }
        else
        {
            // 인벤토리 위젯이 없다면 생성하고 추가
            if (!CurrentInventoryWidget)
            {
                CurrentInventoryWidget = CreateWidget<UUserWidget>(this, InventoryWidgetClass, TEXT("PlayerInventory"));
            }

            if (CurrentInventoryWidget)
            {
                CurrentInventoryWidget->AddToViewport();
                UE_LOG(LogTemp, Warning, TEXT("Inventory Widget added."));

                // UI와 상호작용할 수 있도록 입력 모드 변경
                SetInputMode(FInputModeUIOnly()); // UI 전용 입력 모드
                // SetInputMode(FInputModeGameAndUI()); // 게임과 UI 모두 입력 가능
                bShowMouseCursor = true; // 마우스 커서 표시
            }
        }
    }
    else
    {
        UE_LOG(LogTemp, Warning, TEXT("InventoryWidgetClass is not set in PlayerController."));
    }
}
핵심 함수
  • CreateWidget<T>(OwningObject, WidgetClass, WidgetName)
  • T: 생성할 위젯의 C++ 타입 (UUserWidget 또는 이를 상속받는 커스텀 C++ 위젯 클래스).
  • OwningObject: C++ 템플릿이 지원하는 소유 타입을 사용합니다. 이 예제는 로컬 APlayerController를 넘기며, 임의의 APawn이나 AGameModeBase를 그대로 넘기는 호출은 지원되지 않습니다.
  • WidgetClass: 생성할 위젯 블루프린트의 TSubclassOf 변수.
  • WidgetName: (선택 사항) 생성된 위젯 인스턴스에 부여할 이름.
  • UUserWidget::AddToViewport(ZOrder = 0): 게임 뷰포트에 추가하여 화면에 표시합니다. 분할 화면의 특정 플레이어 영역은 AddToPlayerScreen을 사용합니다. ZOrder는 위젯이 겹칠 때의 순서를 결정하며, 숫자가 높을수록 위에 표시됩니다.
  • UUserWidget::AddToPlayerScreen(ZOrder = 0): AddToViewport와 유사하지만, 특정 로컬 플레이어 스크린에 추가하는 데 사용됩니다.
  • UUserWidget::RemoveFromParent(): 위젯을 화면에서 제거하고 부모 위젯(뷰포트 또는 다른 패널 위젯)으로부터 분리합니다. 이 함수를 호출하면 위젯은 더 이상 렌더링되지 않지만, 메모리에서는 즉시 해제되지 않습니다. 이 예제의 UPROPERTY 포인터처럼 강한 참조가 남으면 인스턴스는 유지됩니다. 도달 가능한 참조가 없어져야 GC 대상이 됩니다.
  • APlayerController::SetInputMode(): 플레이어의 입력 모드를 전환합니다.
  • FInputModeGameOnly(): 플레이어 입력/컨트롤러가 입력을 받습니다.
  • FInputModeUIOnly(): UI만 입력을 받습니다. 닫기 버튼이나 포커스를 받은 위젯의 키 처리로 복귀 경로를 마련합니다.
  • FInputModeGameAndUI(): UI가 먼저 처리하고, 처리하지 않은 입력은 플레이어 입력/컨트롤러에 전달될 수 있습니다.
  • bShowMouseCursor: 마우스 커서의 가시성을 제어합니다.

생성된 위젯 인스턴스 제어 및 데이터 바인딩

생성된 UUserWidget* CurrentHUDWidget 포인터를 통해 해당 위젯 인스턴스의 함수를 호출하거나 속성에 접근할 수 있습니다.

특히 C++ 기반의 커스텀 UUserWidget 클래스를 사용하고 meta=(BindWidget)으로 자식 위젯을 노출했다면, C++에서 직접 자식 위젯의 속성을 제어할 수 있습니다.

MyPlayerController.cpp (HUD 위젯의 체력 업데이트)
#include "MyUserWidget.h" // 커스텀 HUD 위젯 클래스 포함

// ... 기존 코드 ...

void AMyPlayerController::PlayerTick(float DeltaTime)
{
    Super::PlayerTick(DeltaTime);

    // 예시: 매 틱마다 HUD의 체력을 업데이트 (실제로는 체력 변동 시에만 호출하는 것이 효율적)
    if (CurrentHUDWidget)
    {
        UMyUserWidget* MyHUD = Cast<UMyUserWidget>(CurrentHUDWidget);
        if (MyHUD)
        {
            // 플레이어 캐릭터의 현재 체력과 최대 체력을 가져와서 HUD에 전달 (예시)
            // AMyCharacter* MyCharacter = Cast<AMyCharacter>(GetPawn());
            // if (MyCharacter)
            // {
            //     MyHUD->UpdateHealth(MyCharacter->GetCurrentHealth(), MyCharacter->GetMaxHealth());
            // }
        }
    }
}

이처럼 Cast<UMyUserWidget>(CurrentHUDWidget)를 통해 위젯 인스턴스를 해당 C++ 위젯 클래스로 캐스팅한 후, 그 클래스에 정의된 UFUNCTION(BlueprintCallable) 함수나 UPROPERTY(BlueprintReadWrite) 변수에 접근하여 UI를 동적으로 업데이트할 수 있습니다.


위젯 생명 주기 관리

위젯의 생명 주기는 일반적으로 CreateWidget (생성) -> AddToViewport (활성화/표시) -> RemoveFromParent (비활성화/숨김/제거) 순으로 관리됩니다.

  • NativeConstruct() / NativeDestruct(): Slate 자원의 구성·해제에 대응하며 같은 UUserWidget 인스턴스에서 반복될 수 있습니다. 한 번의 UObject 생성·GC와 동일한 시점은 아닙니다. 아래 정의를 추가할 때 MyUserWidget.h에도 virtual void NativeDestruct() override;를 선언합니다.
MyUserWidget.cpp
void UMyUserWidget::NativeConstruct()
{
    Super::NativeConstruct();
    // 위젯 초기화 로직 (예: 초기 텍스트 설정, 애니메이션 시작)
    UE_LOG(LogTemp, Warning, TEXT("MyUserWidget NativeConstruct called!"));
}

void UMyUserWidget::NativeDestruct()
{
    Super::NativeDestruct();
    // 위젯 정리 로직 (예: 타이머 해제, 이벤트 바인딩 해제)
    UE_LOG(LogTemp, Warning, TEXT("MyUserWidget NativeDestruct called!"));
}

화면에서 분리해도 인스턴스 보관 정책은 다릅니다

독립 HUD H1은 분리/참조 비움 후 H2 생성, 인벤토리 I1은 분리/참조 유지 후 같은 I1 재부착이라는 수명/표시 상태 변화이다.

UNREAL C++ · CH6-2

화면에서 분리해도 인스턴스 보관 정책은 다릅니다

같은 RemoveFromParent 뒤에도 HUD는 참조를 비우고 인벤토리는 참조를 유지합니다. 다음 열기에 새로 생성하는지, 다시 부착하는지를 코드별로 비교합니다.

분리 뒤 HUD와 인벤토리의 보관 정책이 다르다HUD와인벤토리의 두 비교 경로는 독립이다. HUD를붙여둔상태에서HideHUD는분리하고이컨트롤러참조를비워다음ShowHUD가새인스턴스를만든다. 인벤토리닫기는분리하되참조를보관하여다음열기에같은인스턴스를다시붙인다. 화면분리는즉시객체소멸이아니다.HUD 부착현재 H1 표시분리 · 참조 비움이 참조는 nullptr새 HUD 생성다음 ShowHUD에서 H2인벤토리 부착현재 I1 표시분리 · 참조 유지I1 객체 보관같은 객체 재부착다음 열기에도 I1
분리 뒤 HUD와 인벤토리의 보관 정책이 다르다HUD와인벤토리의 두 비교 경로는 독립이다. HUD를붙여둔상태에서HideHUD는분리하고이컨트롤러참조를비워다음ShowHUD가새인스턴스를만든다. 인벤토리닫기는분리하되참조를보관하여다음열기에같은인스턴스를다시붙인다. 화면분리는즉시객체소멸이아니다.HUD 부착현재 H1 표시분리 · 참조 비움이 참조는 nullptr새 HUD 생성다음 ShowHUD에서 H2인벤토리 부착현재 I1 표시분리 · 참조 유지I1 객체 보관같은 객체 재부착다음 열기에도 I1다른 보관 정책의 비교

H1/H2와 I1은 생성·재사용을 구별하는 예시 인스턴스입니다. 새 HUD 생성이 이전 객체의 즉시 GC를 뜻하지 않으며, 인벤토리 재부착 뒤에도 실제 데이터 갱신은 별도입니다.

화면 제거와 인스턴스 재사용
코드 경로화면과 참조다음 열기
HideHUD부모에서 분리하고 CurrentHUDWidget을 비움새 HUD 인스턴스를 생성
인벤토리 닫기부모에서 분리하되 CurrentInventoryWidget 유지같은 인스턴스를 다시 AddToViewport
표시 속성 변경Visible로 바꾸기만 하면 분리된 부모 관계는 복구되지 않음기존 HUD가 분리되었다면 부착 여부를 먼저 확인
HideHUD
화면과 참조: 부모에서 분리하고 CurrentHUDWidget을 비움
다음 열기: 새 HUD 인스턴스를 생성
인벤토리 닫기
화면과 참조: 부모에서 분리하되 CurrentInventoryWidget 유지
다음 열기: 같은 인스턴스를 다시 AddToViewport
표시 속성 변경
화면과 참조: Visible로 바꾸기만 하면 분리된 부모 관계는 복구되지 않음
다음 열기: 기존 HUD가 분리되었다면 부착 여부를 먼저 확인

RemoveFromParent는 화면 부모에서의 분리이며 즉시 객체 소멸을 뜻하지 않습니다. 이 예제의 인벤토리 닫기는 GameOnly·커서 숨김으로 정하고, 열기 전 임의 상태를 저장·복원하지 않습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명HUD와 인벤토리의 두 비교 경로는 독립이다. HUD를 붙여 둔 상태에서 HideHUD는 HUD를 분리하고 이 컨트롤러의 참조를 비워 다음 ShowHUD가 새 인스턴스를 만들도록 한다. 인벤토리 닫기는 화면에서 분리하되 참조를 보관하여 다음 열기에 같은 인스턴스를 다시 붙인다. 화면 분리는 즉시 객체 소멸을 뜻하지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
부착 상태

독립 HUD H1과 인벤토리 I1이 표시된다.

  • HUD: H1
  • 인벤토리: I1
  • 뷰포트 부착과 객체의 보관 참조는 다른 관계다.
분리 정책

H1은 참조를 비우고 I1은 참조를 유지한다.

  • HUD 참조: 비움
  • 인벤토리 참조: I1 유지
  • RemoveFromParent 자체가 모든 객체를 즉시 없애지는 않는다.
다시 표시

HUD는 H2를 만들고 인벤토리는 같은 I1을 붙인다.

  • HUD: 새 H2
  • 인벤토리: 같은 I1
  • 보관 정책에 따라 재사용과 새 생성의 identity가 다르다.

화면에서 분리해도 인스턴스 보관 정책은 다릅니다

독립 HUD H1은 분리/참조 비움 후 H2 생성, 인벤토리 I1은 분리/참조 유지 후 같은 I1 재부착이라는 수명/표시 상태 변화이다.

UNREAL C++ · CH6-2

화면에서 분리해도 인스턴스 보관 정책은 다릅니다

같은 RemoveFromParent 뒤에도 HUD는 참조를 비우고 인벤토리는 참조를 유지합니다. 다음 열기에 새로 생성하는지, 다시 부착하는지를 코드별로 비교합니다.

분리 뒤 HUD와 인벤토리의 보관 정책이 다르다HUD와인벤토리의 두 비교 경로는 독립이다. HUD를붙여둔상태에서HideHUD는분리하고이컨트롤러참조를비워다음ShowHUD가새인스턴스를만든다. 인벤토리닫기는분리하되참조를보관하여다음열기에같은인스턴스를다시붙인다. 화면분리는즉시객체소멸이아니다.HUD 부착현재 H1 표시분리 · 참조 비움이 참조는 nullptr새 HUD 생성다음 ShowHUD에서 H2인벤토리 부착현재 I1 표시분리 · 참조 유지I1 객체 보관같은 객체 재부착다음 열기에도 I1
분리 뒤 HUD와 인벤토리의 보관 정책이 다르다HUD와인벤토리의 두 비교 경로는 독립이다. HUD를붙여둔상태에서HideHUD는분리하고이컨트롤러참조를비워다음ShowHUD가새인스턴스를만든다. 인벤토리닫기는분리하되참조를보관하여다음열기에같은인스턴스를다시붙인다. 화면분리는즉시객체소멸이아니다.HUD 부착현재 H1 표시분리 · 참조 비움이 참조는 nullptr새 HUD 생성다음 ShowHUD에서 H2인벤토리 부착현재 I1 표시분리 · 참조 유지I1 객체 보관같은 객체 재부착다음 열기에도 I1다른 보관 정책의 비교

H1/H2와 I1은 생성·재사용을 구별하는 예시 인스턴스입니다. 새 HUD 생성이 이전 객체의 즉시 GC를 뜻하지 않으며, 인벤토리 재부착 뒤에도 실제 데이터 갱신은 별도입니다.

화면 제거와 인스턴스 재사용
코드 경로화면과 참조다음 열기
HideHUD부모에서 분리하고 CurrentHUDWidget을 비움새 HUD 인스턴스를 생성
인벤토리 닫기부모에서 분리하되 CurrentInventoryWidget 유지같은 인스턴스를 다시 AddToViewport
표시 속성 변경Visible로 바꾸기만 하면 분리된 부모 관계는 복구되지 않음기존 HUD가 분리되었다면 부착 여부를 먼저 확인
HideHUD
화면과 참조: 부모에서 분리하고 CurrentHUDWidget을 비움
다음 열기: 새 HUD 인스턴스를 생성
인벤토리 닫기
화면과 참조: 부모에서 분리하되 CurrentInventoryWidget 유지
다음 열기: 같은 인스턴스를 다시 AddToViewport
표시 속성 변경
화면과 참조: Visible로 바꾸기만 하면 분리된 부모 관계는 복구되지 않음
다음 열기: 기존 HUD가 분리되었다면 부착 여부를 먼저 확인

RemoveFromParent는 화면 부모에서의 분리이며 즉시 객체 소멸을 뜻하지 않습니다. 이 예제의 인벤토리 닫기는 GameOnly·커서 숨김으로 정하고, 열기 전 임의 상태를 저장·복원하지 않습니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명HUD와 인벤토리의 두 비교 경로는 독립이다. HUD를 붙여 둔 상태에서 HideHUD는 HUD를 분리하고 이 컨트롤러의 참조를 비워 다음 ShowHUD가 새 인스턴스를 만들도록 한다. 인벤토리 닫기는 화면에서 분리하되 참조를 보관하여 다음 열기에 같은 인스턴스를 다시 붙인다. 화면 분리는 즉시 객체 소멸을 뜻하지 않는다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
부착 상태

독립 HUD H1과 인벤토리 I1이 표시된다.

  • HUD: H1
  • 인벤토리: I1
  • 뷰포트 부착과 객체의 보관 참조는 다른 관계다.
분리 정책

H1은 참조를 비우고 I1은 참조를 유지한다.

  • HUD 참조: 비움
  • 인벤토리 참조: I1 유지
  • RemoveFromParent 자체가 모든 객체를 즉시 없애지는 않는다.
다시 표시

HUD는 H2를 만들고 인벤토리는 같은 I1을 붙인다.

  • HUD: 새 H2
  • 인벤토리: 같은 I1
  • 보관 정책에 따라 재사용과 새 생성의 identity가 다르다.

따라 할 때는 이 컨트롤러를 부모로 한 BP 클래스의 HUDWidgetClass와 InventoryWidgetClass 기본값에 각 Widget Blueprint 클래스를 지정하고, 현재 맵의 GameMode가 그 PlayerController 클래스를 사용하도록 준비합니다. 체력 갱신을 이어 보려면 HUD 위젯의 부모가 앞 절의 UMyUserWidget인지도 확인합니다. 같은 클래스의 생성자·BeginPlay와 NativeConstruct 예제를 여러 번 정의하지 않고 기존 정의에 필요한 동작을 합칩니다.

예시: 생성 횟수와 화면 부착 횟수 비교하기

시작 시 두 참조가 비어 있고 클래스와 생성 결과가 유효하다고 가정합니다. ShowHUD를 두 번 호출해도 두 번째는 기존 인스턴스를 사용합니다. HideHUD는 화면에서 분리한 뒤 이 컨트롤러의 참조를 비우므로, 다시 ShowHUD를 호출하면 새 인스턴스를 생성합니다. 이전 객체가 그 줄에서 즉시 메모리에서 사라졌다는 뜻은 아닙니다.

인벤토리는 ToggleInventory로 열기·닫기·다시 열기를 해도 참조를 비우지 않아 같은 인스턴스를 다시 붙입니다. 객체 재사용과 표시값 유지가 항상 같은 것은 아닙니다. Slate 재구성 때 NativeConstruct가 임시 표시를 다시 대입할 수 있어, 실제 게임 값은 다시 전달할 시점을 준비합니다.

원 코드의 두 경로 따라가기

위의 유효 조건에서 ShowHUD, ShowHUD, HideHUD, ShowHUD를 차례로 호출했습니다. 새 HUD 생성은 몇 번일까요? 별도로 ToggleInventory를 세 번 호출했을 때 인벤토리 생성 횟수와 마지막 부착 상태를 구하세요. 화면만 보이면 체력도 실제 데이터로 갱신되었다고 판단해도 되는지 설명하세요.

참조와 부착을 나누어 풀이하기

HUD는 처음과 마지막 호출에서 두 번 생성됩니다. 인벤토리는 한 번 생성하고 마지막에는 부착되어 있으며 이 예제에서는 UIOnly와 커서 표시 상태가 됩니다. 원 PlayerTick의 데이터 전달은 주석이므로, 표시된 HUD에 실제 체력이 자동으로 들어왔다는 판정은 할 수 없습니다. 조회·전달 경로를 구현하고 부착 뒤 초기 현재값도 동기화해야 합니다.

이 코드는 모듈의 UMG 의존성과 로컬 컨트롤러의 위젯 클래스 설정이 필요합니다. 인벤토리 닫기 버튼에서 ToggleInventory를 호출하도록 연결하고, UIOnly에서 게임 입력 바인딩만으로 닫으려 하지 않습니다. 위 PlayerTick 예시는 데이터 조회·전달 부분이 주석이라 실제 체력을 갱신하지 않습니다.


C++에서 UMG 위젯을 생성하고 제어하는 방법을 이해하는 것은 언리얼 엔진 UI 개발에 있어 필수적인 역량입니다.

TSubclassOf를 통한 클래스 참조, CreateWidget()을 통한 인스턴스 생성, AddToViewport()/RemoveFromParent()를 통한 화면 관리, 그리고 SetInputMode()를 통한 입력 처리까지, 이 모든 기능들은 견고하고 효율적인 게임 UI를 구축하는 데 중요한 역할을 합니다.