icon안동민 개발노트

UMG 위젯 생성 및 조작


 C++에서 UMG 위젯을 생성하고 조작하는 것은 프로그래밍 방식으로 동적인 UI를 구현할 때 매우 유용합니다.

 이 절에서는 위젯 클래스 생성부터 복잡한 UI 레이아웃 구현, 성능 최적화까지 다양한 측면을 다룹니다.

위젯 클래스 생성

 UMG 위젯을 C++에서 사용하기 위해서는 먼저 UUserWidget을 상속받는 클래스를 생성해야 합니다. 이 클래스에서 위젯의 기본 구조와 동작을 정의할 수 있습니다.

UCLASS()
class MYPROJECT_API UMyUserWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeConstruct() override;
 
    UPROPERTY(meta = (BindWidget))
    class UButton* MyButton;
 
    UPROPERTY(meta = (BindWidget))
    class UTextBlock* MyTextBlock;
 
    UFUNCTION()
    void OnButtonClicked();
};

 여기서 UPROPERTY(meta = (BindWidget))는 블루프린트에서 생성된 위젯 컴포넌트와 C++ 코드를 자동으로 연결해줍니다. NativeConstruct 함수는 위젯이 생성될 때 호출되며, 여기서 초기화 작업을 수행할 수 있습니다.

위젯 인스턴스화

 위젯 클래스를 정의한 후, 이를 게임 내에서 실제로 생성하고 사용해야 합니다. 주로 APlayerControllerAHUD 클래스에서 이 작업을 수행합니다.

UCLASS()
class MYPROJECT_API AMyHUD : public AHUD
{
    GENERATED_BODY()
 
public:
    void CreateAndShowWidget();
 
private:
    UPROPERTY()
    UMyUserWidget* MyWidget;
};
 
void AMyHUD::CreateAndShowWidget()
{
    if (!MyWidget)
    {
        MyWidget = CreateWidget<UMyUserWidget>(GetOwningPlayerController(), UMyUserWidget::StaticClass());
    }
 
    if (MyWidget && !MyWidget->IsInViewport())
    {
        MyWidget->AddToViewport();
    }
}

 CreateWidget 함수를 사용하여 위젯을 동적으로 생성하고, AddToViewport 함수를 통해 화면에 표시합니다.

위젯 속성 동적 변경 및 이벤트 바인딩

 생성된 위젯의 속성을 동적으로 변경하거나 이벤트를 처리하는 것은 인터랙티브한 UI를 만드는 데 중요합니다.

void UMyUserWidget::NativeConstruct()
{
    Super::NativeConstruct();
 
    if (MyButton)
    {
        MyButton->OnClicked.AddDynamic(this, &UMyUserWidget::OnButtonClicked);
    }
}
 
void UMyUserWidget::OnButtonClicked()
{
    if (MyTextBlock)
    {
        MyTextBlock->SetText(FText::FromString("Button Clicked!"));
    }
}

 여기서는 버튼 클릭 이벤트를 처리하고, 텍스트 블록의 내용을 동적으로 변경하는 예를 보여줍니다.

복잡한 UI 레이아웃 구현

 그리드, 스크롤 박스, 오버레이 등을 사용하여 더 복잡한 UI 구조를 만들 수 있습니다.

UCLASS()
class MYPROJECT_API UComplexLayoutWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeConstruct() override;
 
    UPROPERTY(meta = (BindWidget))
    class UGridPanel* MainGrid;
 
    UPROPERTY(meta = (BindWidget))
    class UScrollBox* ItemList;
 
    UPROPERTY(meta = (BindWidget))
    class UOverlay* NotificationOverlay;
 
    void PopulateGrid();
    void AddItemToList(const FString& ItemName);
    void ShowNotification(const FString& Message);
};

 이 클래스는 그리드 패널, 스크롤 박스, 오버레이를 조합하여 복잡한 레이아웃을 구성합니다. PopulateGrid, AddItemToList, ShowNotification 등의 함수를 통해 각 컴포넌트를 동적으로 조작할 수 있습니다.

데이터 바인딩을 통한 UI 업데이트

 UI를 게임 데이터와 효과적으로 연동하기 위해 데이터 바인딩을 사용할 수 있습니다.

UCLASS()
class MYPROJECT_API UInventoryWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    void UpdateInventory(const TArray<FItemData>& Items);
 
private:
    UPROPERTY(meta = (BindWidget))
    class UListView* ItemListView;
};
 
void UInventoryWidget::UpdateInventory(const TArray<FItemData>& Items)
{
    ItemListView->ClearListItems();
    for (const FItemData& Item : Items)
    {
        UInventoryItemData* ItemData = NewObject<UInventoryItemData>();
        ItemData->ItemName = Item.Name;
        ItemData->ItemCount = Item.Count;
        ItemListView->AddItem(ItemData);
    }
}

 이 예제에서는 UListView를 사용하여 인벤토리 아이템 목록을 동적으로 업데이트합니다.

UI 시스템 성능 최적화

 대규모 UI 시스템에서는 성능 최적화가 중요합니다. 타이머를 사용하여 무거운 업데이트 작업을 제어하는 것이 한 방법입니다.

UCLASS()
class MYPROJECT_API UOptimizedUserWidget : public UUserWidget
{
    GENERATED_BODY()
 
protected:
    virtual void NativeConstruct() override;
    virtual void NativeDestruct() override;
 
private:
    FTimerHandle UpdateTimerHandle;
    void PerformHeavyUpdate();
};
 
void UOptimizedUserWidget::NativeConstruct()
{
    Super::NativeConstruct();
    GetWorld()->GetTimerManager().SetTimer(UpdateTimerHandle, this, &UOptimizedUserWidget::PerformHeavyUpdate, 0.5f, true);
}
 
void UOptimizedUserWidget::NativeDestruct()
{
    GetWorld()->GetTimerManager().ClearTimer(UpdateTimerHandle);
    Super::NativeDestruct();
}

 이 예제에서는 타이머를 사용하여 주기적으로 무거운 업데이트 작업을 수행합니다. 이를 통해 매 프레임마다 불필요한 연산을 하는 것을 방지할 수 있습니다.

메모리 관리 전략

 효율적인 메모리 관리를 위해 위젯 캐싱 시스템을 구현할 수 있습니다.

UCLASS()
class MYPROJECT_API UUIManager : public UObject
{
    GENERATED_BODY()
 
public:
    template<class T>
    T* GetOrCreateWidget(TSubclassOf<UUserWidget> WidgetClass)
    {
        UUserWidget** FoundWidget = WidgetCache.Find(WidgetClass);
        if (FoundWidget && *FoundWidget)
        {
            return Cast<T>(*FoundWidget);
        }
 
        T* NewWidget = CreateWidget<T>(GetWorld(), WidgetClass);
        WidgetCache.Add(WidgetClass, NewWidget);
        return NewWidget;
    }
 
private:
    UPROPERTY()
    TMap<TSubclassOf<UUserWidget>, UUserWidget*> WidgetCache;
};

 UIManager 클래스는 위젯을 캐시하여 불필요한 재생성을 방지합니다. 이는 메모리 사용을 최적화하고 위젯 생성에 드는 시간을 줄일 수 있습니다.

 이러한 기법들을 적절히 조합하여 사용하면, C++에서 효율적이고 유지보수가 용이한 UMG 기반의 UI 시스템을 구축할 수 있습니다. 각 게임의 특성과 요구사항에 맞게 이 기법들을 조정하고 확장하여 사용하는 것이 중요합니다.