icon
6장 : UI 개발 (UMG)

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


이전 절에서 우리는 UMG 시스템의 개요와 위젯의 계층적 구조에 대해 알아보았습니다. UMG는 시각적 디자인을 위해 블루프린트를 주로 사용하지만, 실제 게임 개발에서는 C++ 코드에서 위젯을 생성하고 제어해야 하는 경우가 많습니다. 특히 게임의 핵심 UI 관리 로직, 성능이 중요한 업데이트, 또는 복잡한 데이터 바인딩 등은 C++로 구현하는 것이 효율적입니다.

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


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

대부분의 UI는 위젯 블루프린트(.uasset)로 존재합니다. C++에서 이러한 위젯 블루프린트 인스턴스를 생성하고 화면에 띄우기 위해서는 다음 단계를 거쳐야 합니다.

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

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

C++에서 위젯 블루프린트 인스턴스를 생성하려면, 먼저 해당 위젯 블루프린트의 클래스(Class) 를 참조해야 합니다. 이는 UPROPERTY(EditDefaultsOnly)TSubclassOf<UUserWidget>를 사용하여 수행하는 것이 가장 일반적입니다.

// AMyPlayerController.h (혹은 AMyHUD.h, AMyGameModeBase.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();
};
  • TSubclassOf<UUserWidget> HUDWidgetClass;: 이 변수는 에디터에서 UMG 위젯 블루프린트 에셋을 할당할 수 있도록 해줍니다. TSubclassOf는 특정 C++ 클래스(여기서는 UUserWidget)를 상속받는 모든 블루프린트 클래스를 드롭다운 목록으로 보여줍니다.

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

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

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

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

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

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

void AMyPlayerController::ShowHUD()
{
    // 1. HUDWidgetClass가 유효한지 확인
    if (HUDWidgetClass)
    {
        // 2. 이미 HUD가 생성되어 있다면 다시 생성하지 않음
        if (CurrentHUDWidget) 
        {
            // 이미 존재한다면, 다시 보이게만 할 수도 있습니다.
            CurrentHUDWidget->SetVisibility(ESlateVisibility::Visible);
            return;
        }

        // 3. 위젯 인스턴스 생성
        // CreateWidget(WorldContextObject, 위젯클래스, 플레이어컨트롤러)
        // 월드 컨텍스트는 현재 액터(PlayerController) 자신을 사용
        // 이 위젯을 소유할 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 (InventoryWidgetClass)
    {
        if (CurrentInventoryWidget && CurrentInventoryWidget->IsInViewport())
        {
            // 인벤토리 위젯이 이미 표시되어 있다면 제거
            CurrentInventoryWidget->RemoveFromParent();
            UE_LOG(LogTemp, Warning, TEXT("Inventory Widget removed."));

            // 게임플레이 입력 활성화 (UI 열기 전 상태로 복귀)
            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>(WorldContextObject, WidgetClass, WidgetName):
    • T: 생성할 위젯의 C++ 타입 (UUserWidget 또는 이를 상속받는 커스텀 C++ 위젯 클래스).
    • WorldContextObject: 월드 컨텍스트를 제공하는 UObject (예: APlayerController, APawn, AGameModeBase 등). 주로 위젯을 소유할 APlayerController를 사용합니다.
    • WidgetClass: 생성할 위젯 블루프린트의 TSubclassOf 변수.
    • WidgetName: (선택 사항) 생성된 위젯 인스턴스에 부여할 이름.
  • UUserWidget::AddToViewport(ZOrder = 0): 위젯을 현재 플레이어의 뷰포트에 추가하여 화면에 표시합니다. ZOrder는 위젯이 겹칠 때의 순서를 결정하며, 숫자가 높을수록 위에 표시됩니다.
  • UUserWidget::AddToPlayerScreen(ZOrder = 0): AddToViewport와 유사하지만, 특정 로컬 플레이어 스크린에 추가하는 데 사용됩니다.
  • UUserWidget::RemoveFromParent(): 위젯을 화면에서 제거하고 부모 위젯(뷰포트 또는 다른 패널 위젯)으로부터 분리합니다. 이 함수를 호출하면 위젯은 더 이상 렌더링되지 않지만, 메모리에서는 즉시 해제되지 않습니다. 언리얼 엔진의 가비지 컬렉터가 이후에 이를 처리합니다.
  • APlayerController::SetInputMode(): 플레이어의 입력 모드를 전환합니다.
    • FInputModeGameOnly(): 오직 게임 월드 입력만 받습니다 (마우스 커서 숨김).
    • FInputModeUIOnly(): 오직 UI 입력만 받습니다 (마우스 커서 표시, 게임 월드 입력 비활성화).
    • FInputModeGameAndUI(): 게임과 UI 입력 모두 받습니다 (마우스 커서 표시, UI 클릭 시 게임 입력 비활성화).
  • bShowMouseCursor: 마우스 커서의 가시성을 제어합니다.

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

생성된 UUserWidget* CurrentHUDWidget 포인터를 통해 해당 위젯 인스턴스의 함수를 호출하거나 속성에 접근할 수 있습니다. 특히 C++ 기반의 커스텀 UUserWidget 클래스를 사용하고 meta=(BindWidget)으로 자식 위젯을 노출했다면, C++에서 직접 자식 위젯의 속성을 제어할 수 있습니다.

// AMyPlayerController.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(): C++에서 위젯이 생성되고 제거될 때 호출되는 가상 함수입니다. 블루프린트의 Event Construct / Event Destruct와 유사하며, C++에서 초기화 및 정리 로직을 구현할 때 오버라이드합니다.
// UMyUserWidget.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!"));
}

마치며

C++에서 UMG 위젯을 생성하고 제어하는 방법을 이해하는 것은 언리얼 엔진 UI 개발에 있어 필수적인 역량입니다. TSubclassOf를 통한 클래스 참조, CreateWidget()을 통한 인스턴스 생성, AddToViewport()/RemoveFromParent()를 통한 화면 관리, 그리고 SetInputMode()를 통한 입력 처리까지, 이 모든 기능들은 견고하고 효율적인 게임 UI를 구축하는 데 중요한 역할을 합니다.