icon안동민 개발노트

UMG (Unreal Motion Graphics) 소개


 UMG (Unreal Motion Graphics)는 언리얼 엔진의 강력한 UI 시스템으로, 게임 내 사용자 인터페이스를 쉽고 효율적으로 제작할 수 있게 해줍니다.

 이 절에서는 UMG의 기본 개념과 C++에서의 활용 방법을 살펴보겠습니다.

UMG의 주요 컴포넌트

  1. 위젯 (Widget) : UI의 기본 구성 요소
  2. 캔버스 패널 (Canvas Panel) : 위젯을 자유롭게 배치할 수 있는 컨테이너
  3. 앵커 (Anchor) : 위젯의 위치와 크기를 상대적으로 지정하는 기준점

C++에서 기본적인 UI 요소 생성 및 조작

 위젯 생성

UCLASS()
class MYPROJECT_API UMyUserWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeConstruct() override;
 
private:
    UPROPERTY(meta = (BindWidget))
    class UButton* MyButton;
 
    UPROPERTY(meta = (BindWidget))
    class UTextBlock* MyTextBlock;
 
    UFUNCTION()
    void OnButtonClicked();
};
 
void UMyUserWidget::NativeConstruct()
{
    Super::NativeConstruct();
 
    if (MyButton)
    {
        MyButton->OnClicked.AddDynamic(this, &UMyUserWidget::OnButtonClicked);
    }
}
 
void UMyUserWidget::OnButtonClicked()
{
    if (MyTextBlock)
    {
        MyTextBlock->SetText(FText::FromString("Button Clicked!"));
    }
}

 위젯 생성 및 화면에 추가

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

위젯 블루프린트와 C++ 코드 연동

 위젯 블루프린트에서 C++ 함수를 호출하거나 C++에서 위젯 블루프린트의 속성에 접근할 수 있습니다.

UCLASS()
class MYPROJECT_API UMyWidgetBlueprintBase : public UUserWidget
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "UI")
    void UpdateHealthBar(float Health);
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UI")
    float MaxHealth = 100.0f;
};
 
void UMyWidgetBlueprintBase::UpdateHealthBar(float Health)
{
    // 헬스바 업데이트 로직
}

동적 UI 생성 및 관리

 동적으로 UI 요소를 생성하고 관리하는 방법

UCLASS()
class MYPROJECT_API UDynamicUIManager : public UObject
{
    GENERATED_BODY()
 
public:
    void CreateDynamicUI(TSubclassOf<UUserWidget> WidgetClass);
    void RemoveDynamicUI(UUserWidget* WidgetToRemove);
 
private:
    UPROPERTY()
    TArray<UUserWidget*> ActiveWidgets;
};
 
void UDynamicUIManager::CreateDynamicUI(TSubclassOf<UUserWidget> WidgetClass)
{
    APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    if (PlayerController)
    {
        UUserWidget* NewWidget = CreateWidget<UUserWidget>(PlayerController, WidgetClass);
        if (NewWidget)
        {
            NewWidget->AddToViewport();
            ActiveWidgets.Add(NewWidget);
        }
    }
}
 
void UDynamicUIManager::RemoveDynamicUI(UUserWidget* WidgetToRemove)
{
    if (WidgetToRemove)
    {
        WidgetToRemove->RemoveFromParent();
        ActiveWidgets.Remove(WidgetToRemove);
    }
}

UI 애니메이션 구현

 UMG에서 C++를 통해 UI 애니메이션을 구현하는 방법

UCLASS()
class MYPROJECT_API UAnimatedWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeConstruct() override;
    void PlayFadeAnimation();
 
private:
    UPROPERTY(Transient, meta = (BindWidgetAnim))
    UWidgetAnimation* FadeAnimation;
};
 
void UAnimatedWidget::NativeConstruct()
{
    Super::NativeConstruct();
 
    if (FadeAnimation)
    {
        PlayAnimation(FadeAnimation);
    }
}
 
void UAnimatedWidget::PlayFadeAnimation()
{
    PlayAnimation(FadeAnimation);
}

UMG 성능 최적화 전략

  1. 위젯 풀링
UCLASS()
class MYPROJECT_API UWidgetPool : public UObject
{
    GENERATED_BODY()
 
public:
    UUserWidget* GetWidget(TSubclassOf<UUserWidget> WidgetClass);
    void ReturnWidget(UUserWidget* Widget);
 
private:
    TMap<TSubclassOf<UUserWidget>, TArray<UUserWidget*>> PooledWidgets;
};
 
UUserWidget* UWidgetPool::GetWidget(TSubclassOf<UUserWidget> WidgetClass)
{
    if (PooledWidgets.Contains(WidgetClass) && PooledWidgets[WidgetClass].Num() > 0)
    {
        return PooledWidgets[WidgetClass].Pop();
    }
    else
    {
        return CreateWidget<UUserWidget>(GetWorld(), WidgetClass);
    }
}
 
void UWidgetPool::ReturnWidget(UUserWidget* Widget)
{
    if (Widget)
    {
        Widget->RemoveFromParent();
        PooledWidgets.FindOrAdd(Widget->GetClass()).Add(Widget);
    }
}
  1. 레이아웃 캐싱
UCLASS()
class MYPROJECT_API UOptimizedUserWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeConstruct() override;
 
protected:
    virtual void NativePreConstruct() override;
 
private:
    UPROPERTY()
    FGeometry CachedGeometry;
 
    void UpdateCachedGeometry();
};
 
void UOptimizedUserWidget::NativePreConstruct()
{
    Super::NativePreConstruct();
    UpdateCachedGeometry();
}
 
void UOptimizedUserWidget::UpdateCachedGeometry()
{
    // 레이아웃 계산 및 CachedGeometry 업데이트
}

반응형 UI 디자인 구현

 다양한 해상도와 화면 비율에 대응하는 반응형 UI 설계

UCLASS()
class MYPROJECT_API UResponsiveWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeConstruct() override;
 
protected:
    virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
 
private:
    void UpdateLayout();
};
 
void UResponsiveWidget::NativeConstruct()
{
    Super::NativeConstruct();
    UpdateLayout();
}
 
void UResponsiveWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
    Super::NativeTick(MyGeometry, InDeltaTime);
    
    if (MyGeometry.GetLocalSize() != LastSize)
    {
        LastSize = MyGeometry.GetLocalSize();
        UpdateLayout();
    }
}
 
void UResponsiveWidget::UpdateLayout()
{
    // 화면 크기에 따라 위젯 레이아웃 조정
}

고급 UI 기능 구현

 인벤토리 시스템

UCLASS()
class MYPROJECT_API UInventoryWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    void UpdateInventory(const TArray<FItemData>& Items);
 
private:
    UPROPERTY(meta = (BindWidget))
    class UUniformGridPanel* ItemGrid;
 
    UPROPERTY(EditDefaultsOnly, Category = "UI")
    TSubclassOf<UUserWidget> ItemWidgetClass;
};
 
void UInventoryWidget::UpdateInventory(const TArray<FItemData>& Items)
{
    ItemGrid->ClearChildren();
 
    for (const FItemData& Item : Items)
    {
        UUserWidget* ItemWidget = CreateWidget<UUserWidget>(this, ItemWidgetClass);
        // ItemWidget 설정
        ItemGrid->AddChildToUniformGrid(ItemWidget);
    }
}

 미니맵

UCLASS()
class MYPROJECT_API UMinimapWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
 
private:
    UPROPERTY(meta = (BindWidget))
    class UImage* MinimapImage;
 
    void UpdateMinimapPosition();
};
 
void UMinimapWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
    Super::NativeTick(MyGeometry, InDeltaTime);
    UpdateMinimapPosition();
}
 
void UMinimapWidget::UpdateMinimapPosition()
{
    // 플레이어 위치에 따라 미니맵 이미지 업데이트
}

 대화 시스템

UCLASS()
class MYPROJECT_API UDialogueWidget : public UUserWidget
{
    GENERATED_BODY()
 
public:
    void StartDialogue(const TArray<FString>& DialogueLines);
 
private:
    UPROPERTY(meta = (BindWidget))
    class UTextBlock* DialogueText;
 
    UPROPERTY(meta = (BindWidget))
    class UButton* NextButton;
 
    UFUNCTION()
    void OnNextButtonClicked();
 
    TArray<FString> CurrentDialogue;
    int32 CurrentLineIndex;
};
 
void UDialogueWidget::StartDialogue(const TArray<FString>& DialogueLines)
{
    CurrentDialogue = DialogueLines;
    CurrentLineIndex = 0;
    if (DialogueText && CurrentDialogue.Num() > 0)
    {
        DialogueText->SetText(FText::FromString(CurrentDialogue[CurrentLineIndex]));
    }
}
 
void UDialogueWidget::OnNextButtonClicked()
{
    CurrentLineIndex++;
    if (CurrentLineIndex < CurrentDialogue.Num())
    {
        DialogueText->SetText(FText::FromString(CurrentDialogue[CurrentLineIndex]));
    }
    else
    {
        RemoveFromParent();
    }
}

 UMG는 언리얼 엔진에서 강력하고 유연한 UI 시스템을 구축할 수 있게 해주는 도구입니다.

 C++를 사용하여 UMG의 기능을 확장하고 커스터마이즈함으로써, 게임의 요구사항에 맞는 복잡하고 인터랙티브한 UI를 구현할 수 있습니다.

 위젯 블루프린트와 C++ 코드를 효과적으로 연동하여 사용하면, 디자이너와 프로그래머 간의 협업을 원활히 하고 개발 효율성을 높일 수 있습니다.

 동적 UI 생성 및 관리 기법을 통해 게임 상황에 따라 유동적으로 변화하는 UI를 구현할 수 있으며, UI 애니메이션을 활용하여 더욱 생동감 있는 인터페이스를 만들 수 있습니다.