C++에서 위젯 생성 및 제어
이전 절에서 우리는 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>
를 사용하여 수행하는 것이 가장 일반적입니다.
// 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를 구축하는 데 중요한 역할을 합니다.