본문 바로가기

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의 복잡성을 관리하고, 레이아웃을 유연하게 조정하며, 특정 위젯에만 영향을 미치는 애니메이션이나 상호작용을 구현하는 데 필수적입니다.

계층에서 Crosshair는 CanvasPanel의 직접 자식이지만 HealthBar는 VerticalBox의 자식입니다. 따라서 Crosshair의 Canvas 슬롯 앵커를 조절하는 일과 HealthBar의 VerticalBox 슬롯 정렬을 조절하는 일은 같은 설정이 아닙니다. 아래 그림은 위 계층의 일부를 발췌한 소속 관계이며, 버튼의 실행 순서를 뜻하지 않습니다.

UNREAL C++ · CH6-1

위젯의 직접 부모가 배치 슬롯을 결정한다

CanvasPanel의 자식과 VerticalBox의 자식을 구별합니다. 화살표는 소속이며 각 위젯의 직접 부모에 맞는 슬롯 설정을 사용합니다.

위젯의 직접 부모가 배치 슬롯을 결정한다위본문예시계층의일부를발췌한다. UMG_HUD아래CanvasPanel이있고CanvasPanel의직접자식은VerticalBox와Crosshair이다. VerticalBox의직접자식으로PlayerName과HealthBar를표시한다. 화살표는부모에서자식으로의소속이며이벤트실행이나NativeConstruct순서가아니다. HorizontalBox와두버튼의계층은원코드에유지된다.UMG_HUDUserWidgetCanvasPanel루트 패널VerticalBox세로 배치 패널PlayerNameTextBlockHealthBarProgressBarCrosshairImage · Canvas 슬롯
위젯의 직접 부모가 배치 슬롯을 결정한다위본문예시계층의일부를발췌한다. UMG_HUD아래CanvasPanel이있고CanvasPanel의직접자식은VerticalBox와Crosshair이다. VerticalBox의직접자식으로PlayerName과HealthBar를표시한다. 화살표는부모에서자식으로의소속이며이벤트실행이나NativeConstruct순서가아니다. HorizontalBox와두버튼의계층은원코드에유지된다.UMG_HUDUserWidgetCanvasPanel루트 패널VerticalBox세로 배치 패널PlayerNameTextBlockHealthBarProgressBarCrosshairImage · Canvas 슬롯

원 전체 계층에서 일부를 발췌했습니다. Crosshair는 Canvas 슬롯, PlayerName과 HealthBar는 VerticalBox 슬롯에 속합니다. C++ 바인딩 예제의 텍스트 이름은 PlayerNameText이며 이 일반 계층 예시와 구별합니다.


위젯 블루프린트 (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 의존성을 추가해야 합니다.

예시: 위젯 이름과 표시값을 연결하기

이 C++ 예를 따라 만들 때는 UMyUserWidget을 부모로 하는 Widget Blueprint에 ProgressBar인 HealthBar와 TextBlock인 PlayerNameText를 같은 이름으로 배치하고 Is Variable을 켭니다. 앞의 일반 계층 예시의 PlayerName을 이 C++ 바인딩에 그대로 쓰면 이름 계약이 맞지 않습니다. 화면 생성·부착은 다음 절에서 이어집니다.

현재 체력 75, 최대 체력 120을 UpdateHealth에 전달하면 비율은 75/120 = 0.625로 62.5%이고 텍스트는 PlayerOne (75/120)입니다. 바의 표시 비율은 0~1로 제한하지만, 텍스트는 넘긴 체력값을 내림해 표시합니다. 이 함수는 게임 캐릭터의 체력을 수정하는 함수가 아닙니다.

표시와 원래 상태 구별하기

유한한 현재 체력 150, 최대 체력 100을 전달하면 바와 텍스트는 각각 무엇을 표시할까요? 표시가 100%라는 이유만으로 캐릭터의 현재 체력도 100으로 바뀐 것인가요? 최대 체력이 0이면 어느 분기를 택하는지도 판단하세요.

표시 함수의 실제 계산을 따른 풀이

바는 비율 1.5를 1로 제한해 100%이고 텍스트는 PlayerOne (150/100)입니다. 입력 값을 수정하는 코드는 없으므로 캐릭터 상태까지 100으로 바뀌지 않습니다. 최대 체력이 0이면 나눗셈을 하지 않고 바를 0으로 표시합니다. 이 분기가 잘못된 게임 설정 자체를 복구하는 것은 아니므로 게임 상태의 유효성은 별도로 처리합니다.


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

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

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