[Unreal Engine] UMG (Unreal Motion Graphics)

Imeamangryang·2025년 6월 29일

Unreal UMG & Slate UI

목록 보기
5/13
post-thumbnail

UMG (Unreal Motion Graphics)

UMG 프레임워크는 UObject 기반으로 동작하며, 반드시 특정 Player Controller(분할 화면을 고려해 Owning Player라고 부름)에 연결되어야 합니다.
Owning Player를 지정하지 않으면 자동으로 레벨의 첫 번째 로컬 플레이어에 연결됩니다.

UMG의 객체 계층 구조는 다음과 같습니다:

  • UVisual: UMG 슬롯과 위젯의 모든 요소의 기본 클래스입니다.
    • UWidget: 모든 위젯의 기본 클래스이며, Slate 위젯을 생성하고 Blueprint/UObject 기반 클래스에서 Slate로 기능을 전달합니다.
      (예: TextBlock, ScrollBox, Button 등)
      • UUserWidget: UI를 디자인하고, 애니메이션을 만들며, 게임 코드와 연결하는 데 사용하는 기본 클래스입니다.

User Widget은 여러 위젯으로 구성되지만, 반드시 루트 위젯이 필요하지는 않습니다.
(참고: Actor가 여러 Actor Component로 구성되지만 반드시 루트 컴포넌트가 필요한 것과는 다름)

User Widget은 Actor가 컴포넌트 계층 구조를 상속받는 것과 달리 Widget Hierarchy(위젯 계층 구조)를 상속받을 수는 없습니다.
하지만 클래스 기능은 상속할 수 있으므로, User Widget을 추상 클래스로 만들어 다른 클래스가 상속받게 하거나, C++로 클래스를 만들어 상속 구조를 설계할 수 있습니다.

User Widget Hierarchy

User Widget은 설계상 루트 UWidget입니다. 즉, User Widget은 내부에 위젯이 하나도 없을 수도 있으며, 기본적으로 Compound Widget(복합 위젯)으로 1개의 자식만 가질 수 있습니다.
하지만 그 자식 위젯은 또 다른 자식들을 가질 수 있으므로, 각 User Widget의 트리 계층 구조(Tree Hierarchy) 내에서 자식 위젯들이 연쇄적으로 포함되는 구조가 만들어집니다.

디자이너/계층 구조 에디터 뷰런타임 결과

Health_BarHealth_Text굵게 표시되어 있는데, 이는 Is Variable 플래그가 활성화되어 있기 때문입니다

해당 계층 구조를 다이어그램으로 나타낸 예시

User Widget Animation

모든 User Widget은 Sequencer를 통해 해당 User Widget 내부의 위젯들을 활용한 커스텀 애니메이션을 만들 수 있습니다.
Widget Designer(위젯 디자이너)에서 애니메이션을 생성할 수 있으며, 각 위젯의 렌더 트랜스폼, 위젯 가시성 등 다양한 속성을 애니메이션으로 제어할 수 있습니다.
또한, 위젯의 머티리얼 파라미터나 런타임 값 등도 애니메이션에서 수정할 수 있습니다.

UMG의 애니메이션 디자이너 예시

User Widget의 Tick Frequency(틱 빈도)가 클래스 기본값에서 Auto가 아닌 Never로 설정되어 있으면
애니메이션이 전혀 실행되지 않습니다.
애니메이션이 재생되려면 User Widget이 애니메이션을 틱할 수 있어야 하며,
Tick Frequency를 Never로 설정하면 애니메이션 객체도 틱되지 않아 애니메이션이 동작하지 않습니다.

User Widget Events

모든 User Widget에는 직접 구현하여 원하는 기능을 추가할 수 있는 내장 이벤트들이 있습니다.

  • Pre Construct: 이 이벤트는 에디터의 디자이너에서와 런타임에서 위젯이 실제로 생성되기 전에 모두 호출됩니다. 액터의 Construction Script와 유사한 역할을 합니다.
virtual void UUserWidget::NativePreConstruct()
{
  // 블루프린트(BP) 버전 호출
  PreConstruct(IsDesignTime());
}

  • On Initialized: 이 이벤트는 런타임에서 실제 인스턴스가 생성될 때(즉, User Widget을 스폰할 때) 단 한 번만 호출됩니다.
virtual void UUserWidget::NativeOnInitialized()
{
  
  // 이 위젯에 바인딩된 입력 델리게이트가 있다면 소유 플레이어 컨트롤러에 바인딩합니다.
  if(APlayerController* PC = GetOwningPlayer())
  {
    UInputDelegateBinding::BindInputDelegates(GetClass(), PC->InputComponent, this);		
  }

  OnInitialized();
  
}

  • Construct: 이 이벤트는 위젯이 화면에 Construct(예: Add to Viewport, Add to Player Screen)될 때마다 여러 번 호출될 수 있습니다.
    따라서 위젯을 부모에서 제거했다가 다시 추가하는 경우, 최초 1회만 필요한 초기화 코드는 “On Initialized”에 넣는 것이 좋습니다.
virtual void UUserWidget::NativeConstruct()
{
    // 블루프린트(BP) 버전 호출
    Construct();
    UpdateCanTick();
}

  • Destruct: 이 위젯이 더 이상 화면에 표시되지 않을 때 호출됩니다. Construct의 반대 개념으로, 부모에서 제거될 때마다 여러 번 호출될 수 있습니다.
virtual void UUserWidget::NativeDestruct()
{
    StopListeningForAllInputActions();
    // BP(블루프린트) 버전 호출
    Destruct();
}

  • On Paint: 이 위젯이 매 프레임마다 그려질 때 호출됩니다. Tick과는 다르며, Paint Context 정보를 처리하는 용도입니다.
virtual int32 UUserWidget::NativePaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
    // BP에서 함수가 구현되어 있다면
    if ( bHasScriptImplementedPaint )
    {
      FPaintContext Context(AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
      // BP 버전 호출
      OnPaint( Context );

      return FMath::Max(LayerId, Context.MaxLayer);
    }

    return LayerId;
}

  • Tick: 이 위젯이 화면에 표시되는 동안 매 프레임마다 호출됩니다. 화면에 표시되지 않으면 Tick이 발생하지 않습니다(존재만으로는 틱되지 않음, 반드시 화면에 보여야 틱이 실행됨).
virtual void UUserWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
    //...
    
    // BP에서 이벤트 그래프로 구현되어 있다면
    if (bHasScriptImplementedTick)
    {
        // BP 버전 호출
      Tick(MyGeometry, InDeltaTime);
    }
}

  • On Animation Started: 위젯 애니메이션이 재생되기 시작할 때 호출됩니다. 어떤 애니메이션이 시작됐는지 확인하거나 활용할 수 있습니다.
    (블루프린트 사용자의 경우, 최근 언리얼 버전에서는 애니메이션 종료 이벤트는 이벤트 그래프에서만 사용 가능하며, 이전 버전에서는 오버라이드 함수로도 사용 가능)
virtual void UUserWidget::OnAnimationStartedPlaying(UUMGSequencePlayer& Player)
{
    // BP 버전 호출
    OnAnimationStarted(Player.GetAnimation());

    BroadcastAnimationStateChange(Player, EWidgetAnimationEvent::Started);
}

  • On Animation Finished: 위젯 애니메이션이 재생을 마쳤을 때 호출됩니다. 어떤 애니메이션이 끝났는지 확인하거나 활용할 수 있습니다.
    (블루프린트 사용자의 경우, 최근 언리얼 버전에서는 애니메이션 종료 이벤트는 이벤트 그래프에서만 사용 가능하며, 이전 버전에서는 오버라이드 함수로도 사용 가능)
virtual void UUserWidget::OnAnimationFinishedPlaying(UUMGSequencePlayer& Player)
{
    // 이 이벤트는 애니메이션이 끝날 때 시퀀스 플레이어에서 직접 호출됩니다.

    // BP 버전 호출
    OnAnimationFinished(Player.GetAnimation());

    BroadcastAnimationStateChange(Player, EWidgetAnimationEvent::Finished);

    if ( Player.GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped )
    {
      StoppedSequencePlayers.Add(&Player);

      if (AnimationTickManager)
      {
        AnimationTickManager->AddLatentAction(FMovieSceneSequenceLatentActionDelegate::CreateUObject(this, &UUserWidget::ClearStoppedSequencePlayers));
      }
    }

    UpdateCanTick();
}

  • On Focus Received: (블루프린트에서 찾기 어렵다면, Event Reply 구조체를 반환해야 하므로 함수 목록에서 오버라이드해야 하며 이벤트 그래프에서는 사용할 수 없습니다)
    이 User Widget(본인)에게 포커스가 주어질 때 호출됩니다. 반드시 Event Reply 구조체를 반환해야 하며, Handled 또는 Unhandled를 선택할 수 있습니다.
virtual FReply UUserWidget::NativeOnFocusReceived( const FGeometry& InGeometry, const FFocusEvent& InFocusEvent )
{
    // BP 버전 호출 및 반환값 반환
    return OnFocusReceived( InGeometry, InFocusEvent ).NativeReply;
}


  • On Added to Focus Path: 이 위젯 또는 이 User Widget 내의 자식 위젯이 포커스 경로에 추가(포커스됨)되고 이전에는 포함되어 있지 않았을 때 호출됩니다.
virtual void UUserWidget::NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent)
{
    // BP 버전 호출
    OnAddedToFocusPath(InFocusEvent);
}

  • On Focus Lost: 이 User Widget(본인)이 포커스를 잃을 때 호출됩니다.
virtual void UUserWidget::NativeOnFocusLost( const FFocusEvent& InFocusEvent )
{
    // BP 버전 호출
    OnFocusLost( InFocusEvent );
}

  • On Removed from Focus Path: On Focus Lost와 유사하지만, 이 User Widget 또는 그 자식 위젯이 더 이상 포커스 경로에 포함되지 않을 때 호출될 수 있습니다.
virtual void UUserWidget::NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent)
{
    // BP 버전 호출
    OnRemovedFromFocusPath(InFocusEvent);
}

profile
언리얼 엔진 주니어 개발자 입니다.

0개의 댓글