안동민 개발노트

본문 시작

UMG 시스템 소개와 위젯 구조

UMG의 Widget Blueprint 계층과 UserWidget 수명 주기를 이해하고 UI 요소의 속성과 이벤트를 구성합니다.

이전 장에서 우리는 게임 월드 내의 오브젝트들이 서로 어떻게 상호작용하는지(충돌, 물리, 트레이스)에 대해 깊이 있게 알아보았습니다.

이제 게임플레이의 또 다른 핵심 요소인 사용자 인터페이스(UI)에 대해 다룰 시간입니다.

UI는 플레이어에게 정보를 전달하고, 입력을 받으며, 게임 세계와 상호작용할 수 있도록 돕는 시각적 요소들의 집합입니다.

언리얼 엔진은 강력하고 유연한 UI 시스템인 UMG (Unreal Motion Graphics) UI Designer를 제공합니다.

이번 절에서는 UMG 시스템이 무엇인지 소개하고, UMG UI를 구성하는 기본 단위인 위젯(Widget)의 구조와 계층 관계에 대해 살펴보겠습니다.


UMG (Unreal Motion Graphics)?

UMG는 언리얼 엔진 4부터 도입된 선언적(Declarative) UI 디자인 시스템으로, 개발자가 코드를 직접 작성하는 대신 시각적인 도구(UMG UI Designer)를 사용하여 UI를 직관적으로 제작할 수 있도록 돕습니다.

웹 개발의 HTML/CSS와 유사하게, UMG는 미리 정의된 위젯들을 조합하여 복잡한 UI를 빠르고 효율적으로 만들 수 있게 합니다.

UMG의 주요 특징

  • 시각적 디자인 도구: 언리얼 에디터 내에서 드래그 앤 드롭 방식으로 위젯을 배치하고 크기를 조절하며, 실시간으로 UI 레이아웃을 확인할 수 있습니다.
  • 데이터 바인딩(Data Binding): 게임 내 변수(C++ 또는 블루프린트)와 UI 위젯의 속성(텍스트, 진행률 바 등)을 쉽게 연결하여 UI가 값을 읽거나 변경 통지를 받아 표시하도록 구성합니다. 일반 속성 바인딩은 값이 변할 때만 평가되는 방식이 아닙니다.
  • 애니메이션 시스템 통합: UI 요소에 부드러운 전환 효과나 시각적 피드백을 주기 위한 애니메이션을 내장된 애니메이션 시스템으로 제작할 수 있습니다.
  • 블루프린트 지원: UI 로직을 블루프린트 비주얼 스크립팅으로 쉽게 구현할 수 있으며, C++와의 연동도 유연하게 지원합니다.
  • 확장성: 새로운 커스텀 위젯을 C++로 생성하고 UMG 시스템에 통합할 수 있습니다.
    • 하드웨어 가속: GPU를 활용하여 고성능의 UI 렌더링을 제공합니다.

위젯 (Widget)이란?

위젯은 UMG UI의 가장 기본적인 구성 단위입니다.

버튼, 텍스트 블록, 이미지, 체크박스, 슬라이더 등 UI 화면에 보이는 모든 요소는 위젯입니다.

모든 위젯은 UWidget 클래스를 상속받으며, 이는 UObject의 자식입니다.

위젯의 계층적 구조

UMG의 UI는 마치 웹 페이지의 HTML DOM(문서 객체 모델)이나 윈도우 운영 체제의 창 구조처럼 계층적인 트리 구조를 가집니다.

자식을 담을 수 있는 패널이 위젯들을 배치합니다. 모든 위젯이 여러 자식을 갖는 것은 아니며, Button 같은 콘텐츠 위젯은 단일 자식만 받습니다.

  • 루트 위젯 (Root Widget): 모든 UI의 최상단에 있는 위젯입니다. 일반적으로 UCanvasPanel과 같은 레이아웃 위젯이 루트 위젯이 됩니다.
  • 패널 위젯 (Panel Widget): 자식 위젯들을 특정 규칙에 따라 배치하는 데 사용되는 위젯입니다. (예: UCanvasPanel, UHorizontalBox, UVerticalBox, UGridPanel, UScrollBox 등)
  • 컨트롤 위젯 (Control Widget): 사용자 상호작용을 받거나 정보를 표시하는 개별 UI 요소입니다. (예: UButton, UTextBlock, UImage, UProgressBar, USlider 등)
예시 계층 구조
- UMG_HUD (UserWidget)
  - CanvasPanel (Root Widget, Panel Widget)
    - VerticalBox (Panel Widget)
      - TextBlock (PlayerName, Control Widget)
      - ProgressBar (HealthBar, Control Widget)
      - HorizontalBox (Panel Widget)
        - Button (AttackButton, Control Widget)
          - TextBlock (Button Text, Control Widget)
        - Button (DefendButton, Control Widget)
          - TextBlock (Button Text, Control Widget)
    - Image (Crosshair, Control Widget)

이러한 계층 구조는 UI의 복잡성을 관리하고, 레이아웃을 유연하게 조정하며, 특정 위젯에만 영향을 미치는 애니메이션이나 상호작용을 구현하는 데 필수적입니다.


위젯 블루프린트 (UUserWidget)

언리얼 에디터에서 우리가 직접 생성하고 디자인하는 UI는 실제로는 위젯 블루프린트(Widget Blueprint)에셋입니다.

이 에셋은 C++의 UUserWidget 클래스를 상속받아 구현됩니다.

UUserWidget은 UMG 시스템의 핵심으로, UI를 화면에 표시하고, 상호작용 로직을 처리하며, 내부적으로 C++ 위젯 클래스들을 관리하는 역할을 합니다.

위젯 블루프린트 생성 및 구성

생성: 콘텐츠 브라우저에서 마우스 오른쪽 버튼 클릭 > User Interface > Widget Blueprint를 선택합니다.

이름을 지정합니다 (예: WBP_HUD).

UMG UI Designer: 생성된 위젯 블루프린트를 더블 클릭하여 UMG UI Designer를 엽니다.

주요 작업 영역은 다음과 같습니다.

  • 팔레트(Palette): 사용할 수 있는 모든 기본 위젯 목록입니다. 여기서 원하는 위젯을 드래그 앤 드롭하여 디자인 화면에 추가합니다.
  • 계층 구조(Hierarchy): 현재 UI를 구성하는 위젯들의 트리 구조를 보여줍니다. 부모-자식 관계를 시각적으로 파악하고, 위젯을 재정렬할 수 있습니다.
  • 디자이너(Designer) 탭: 위젯들을 실제로 배치하고 레이아웃을 조절하는 시각적인 작업 공간입니다.
  • 그래프(Graph) 탭: 위젯의 이벤트 처리 로직(클릭 시 동작, 데이터 업데이트 등)을 블루프린트 비주얼 스크립팅으로 구현하는 공간입니다.

위젯의 일반적인 속성 및 이벤트

모든 위젯은 공통적으로 가지는 속성들이 있습니다.

  • 포지션(Position) 및 크기(Size): 화면에서의 위치와 크기를 정의합니다.
  • 앵커(Anchors): Canvas Panel의 자식 슬롯에서 기준점을 정합니다. 모든 패널에 공통인 속성은 아니며, 다른 패널은 각 슬롯의 정렬·크기 규칙을 사용합니다.
  • 패딩(Padding) 및 마진(Margin): 위젯 내부 콘텐츠와 테두리 간의 간격, 또는 위젯 외부의 다른 위젯과의 간격을 조절합니다.
  • 가시성(Visibility): 위젯이 보이는지, 숨겨져 있는지, 아니면 아예 공간을 차지하지 않는지 등을 설정합니다.
  • 상호작용 가능 여부(Is Enabled): 위젯이 사용자 입력을 받을 수 있는지 여부입니다.
  • 이벤트(Events): OnClicked (버튼 클릭), OnHovered (마우스 오버), OnTextChanged (텍스트 변경) 등 사용자의 상호작용에 응답하는 다양한 이벤트들이 있습니다. 이러한 이벤트는 그래프 탭에서 블루프린트 노드로 연결하여 로직을 구현합니다.

C++와 UMG의 연동 기본

비록 UMG가 블루프린트 중심의 시각적 디자인을 지향하지만, 복잡한 로직이나 성능이 중요한 부분, 또는 재사용 가능한 기반 위젯은 C++로 구현하는 것이 효율적입니다.

  • C++ 클래스 생성: UUserWidget을 상속받는 새로운 C++ 클래스를 생성할 수 있습니다 (예: UMyBaseHUDWidget).
  • 블루프린트에서 상속: 이 C++ 클래스를 부모로 하는 위젯 블루프린트를 생성하여 디자이너가 시각적 요소를 추가하고 세부 로직을 구현하도록 합니다.
  • UPROPERTY / UFUNCTION: C++ 위젯 클래스 내에서 UPROPERTY와 UFUNCTION 매크로를 사용하여 변수와 함수를 블루프린트에 노출함으로써, 블루프린트에서 C++ 로직에 접근하거나 C++ 데이터에 바인딩할 수 있게 합니다. 특히 meta=(BindWidget) 지정자는 특정 UMG 위젯을 C++ 코드에서 직접 참조할 때 유용합니다. (다음 절에서 자세히 다룰 예정입니다.)
MyUserWidget.h
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyUserWidget.generated.h"

// UProgressBar를 사용하기 위해 포함
class UProgressBar;
class UTextBlock;

UCLASS()
class MYPROJECT_API UMyUserWidget : public UUserWidget
{
    GENERATED_BODY()

public:
    // 생성자나 BeginPlay에서 위젯들을 가져오지 않고,
    // meta=(BindWidget)을 사용하여 위젯 블루프린트의 이름과 일치하는 UPROPERTY를 선언하면,
    // 자동으로 해당 위젯 인스턴스가 바인딩됩니다.
    // 이 실습에서는 디자이너의 Is Variable도 활성화합니다.
    UPROPERTY(meta = (BindWidget))
    UProgressBar* HealthBar; // 위젯 블루프린트의 Progress Bar 위젯 이름이 'HealthBar'여야 함

    UPROPERTY(meta = (BindWidget))
    UTextBlock* PlayerNameText; // 위젯 블루프린트의 Text Block 위젯 이름이 'PlayerNameText'여야 함

    // 플레이어의 체력을 업데이트하는 함수
    UFUNCTION(BlueprintCallable, Category = "UI")
    void UpdateHealth(float CurrentHealth, float MaxHealth);

protected:
    virtual void NativeConstruct() override; // Slate 위젯 구성 시 호출되며 재구성 때 반복될 수 있음
};
MyUserWidget.cpp
#include "MyUserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"

void UMyUserWidget::NativeConstruct()
{
    Super::NativeConstruct();

    // HealthBar와 PlayerNameText는 이미 meta=(BindWidget)으로 인해 자동으로 바인딩됩니다.
    // 여기서 추가적인 초기화나 이벤트 바인딩을 할 수 있습니다.
    if (HealthBar)
    {
        HealthBar->SetPercent(1.0f); // 초기 체력 100%
    }
    if (PlayerNameText)
    {
        PlayerNameText->SetText(FText::FromString(TEXT("PlayerOne"))); // 초기 플레이어 이름 설정
    }
}

void UMyUserWidget::UpdateHealth(float CurrentHealth, float MaxHealth)
{
    if (HealthBar)
    {
        // 진행률 바 업데이트 (0.0 ~ 1.0 사이의 값)
        const float Percent = MaxHealth > 0.0f ? FMath::Clamp(CurrentHealth / MaxHealth, 0.0f, 1.0f) : 0.0f;
        HealthBar->SetPercent(Percent);
    }
    if (PlayerNameText)
    {
        // 텍스트 업데이트 (예: 체력 수치도 같이 표시)
        PlayerNameText->SetText(FText::Format(FText::FromString(TEXT("{0} ({1}/{2})")),
                                            FText::FromString(TEXT("PlayerOne")),
                                            FText::AsNumber(FMath::FloorToInt(CurrentHealth)),
                                            FText::AsNumber(FMath::FloorToInt(MaxHealth))));
    }
}
meta=(BindWidget) 주의사항
  • 이 매크로를 사용하려면 UPROPERTY 변수 이름이 위젯 블루프린트 내의 위젯 이름과 정확히 일치해야 합니다.
  • 위젯 블루프린트의 부모 C++ 클래스, 이름과 타입을 맞춥니다. 이 실습에서는 Is Variable도 활성화합니다.
  • NativeConstruct()는 Slate 재구성 때 반복될 수 있습니다. 인스턴스당 한 번의 바인딩은 NativeOnInitialized()에서 수행하고, 화면 재구성에 필요한 표시 갱신과 구분합니다.

이 예제는 표시할 값이 유한한 실수라는 전제이며, 최대 체력이 양수가 아니면 진행률을 0으로 표시합니다. 모듈의 .Build.cs에는 공개 헤더의 UUserWidget 타입을 제공하는 UMG 의존성을 추가해야 합니다.


UMG 시스템은 언리얼 엔진에서 사용자 인터페이스를 개발하기 위한 강력하고 직관적인 도구입니다.

위젯의 계층적 구조를 이해하고 UUserWidget 클래스를 기반으로 하는 위젯 블루프린트를 통해 시각적 디자인과 로직을 통합하는 것이 중요합니다.

특히 C++에서 meta=(BindWidget)을 활용하여 위젯을 참조하고 제어하는 방법은 효율적인 UI 개발의 핵심입니다.