TECH.ART.FLOW.IO

[번역][연재물] 언리얼 엔진 개발 가이드. Slate part two

jplee 2025. 4. 26. 19:58

기본

  • SButton :버튼
  • SCheckBox:체크박스
  • SComboBox<OptionType> :사용자 정의 요소의 콤보 박스, 메뉴 항목의 위젯 생성기 제공 필요
  • STextComboBox :텍스트 콤보 박스
  • SComboButton :콤보 버튼, ComboBox와 다른 점은 ComboButton은 메뉴 항목 생성기가 아닌 전체 메뉴 생성기를 제공해야 함
  • SInputKeySelector :키 입력 선택기
  • SNumericDropDown<NumType> :숫자 드롭다운 위젯
  • SNumericEntryBox<NumType> :숫자 선택 상자
  • SRotatorInputBox = SNumericRotatorInputBox<float> :회전 입력 상자
  • SSlider :슬라이더
  • SSpinBox<NumericType> :사용자 정의 숫자 타입의 스핀 박스
  • SVectorInputBox = SNumericVectorInputBox<float, UE::Math::TVector<float>, 3> :벡터 입력 상자
  • SVolumeControl :볼륨 조절 위젯

텍스트

  • STextBlock :텍스트 표시 블록
  • SRichTextBlock :리치 텍스트 표시 블록
  • SEditableText :편집 가능한 텍스트
  • SInlineEditableTextBlock :한 줄 텍스트 편집 블록(편집 시에만 테두리 표시)
  • SEditableTextBox :편집 가능한 텍스트 상자
  • SSuggestionTextBox :자동 완성 및 입력 기록이 있는 텍스트 상자
  • SSearchBox :검색 상자
  • SMultiLineEditableText :여러 줄 텍스트 편집 블록
  • SMultiLineEditableTextBox :여러 줄 편집 가능한 텍스트 상자
  • SHyperlink :하이퍼링크

색상

  • SColorBlock :단일 색상 표시 위젯
  • SSimpleGradient :단순 그라데이션 색상 표시 위젯, 시작 색상과 끝 색상만 설정 가능
  • SComplexGradient :복잡한 그라데이션 색상 표시 위젯, 임의의 색상 수로 구성된 그라데이션 지원
  • SColorPicker :단일 색상 조절 통합 위젯(모듈 AppFramework 필요)

이미지

  • SImage:이미지 표시용

내비게이션

  • SBreadcrumbTrail

알림

  • SErrorHint:에러 아이콘, ToolTip 설정 가능
  • SErrorText:빨간색 배경의 텍스트 블록
  • FNotificationInfo:컨트롤이 아닌, 메시지 알림용
FNotificationInfo NotificationInfo(FText::FromString("FNotificationInfo"));
NotificationInfo.Text = FText::FromString("This Is Notification Info");
NotificationInfo.bFireAndForget = true;
NotificationInfo.ExpireDuration = 100.0f; 
NotificationInfo.bUseThrobber = true;
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
  • SProgressBar:진행 바
  • SHeader:제목 컨트롤
  • SScrollBar:스크롤 바
  • SSpacer:빈 컨트롤, 일반적으로 레이아웃에서 채우기용으로 사용

컨트롤 컨테이너(SPanel)

레이아웃과 크기

  • SVerticalBox:수직 레이아웃
  • SHorizontalBox:수평 레이아웃
  • SOverlay:중첩 레이아웃
  • SGridPanel:그리드 레이아웃
  • SUniformGridPanel:균일한 그리드 크기를 사용하는 그리드 레이아웃 패널
  • SWrapBox:플로우 레이아웃, 배열 방향 설정 가능, 컨트롤이 크기를 초과할 때 줄바꿈
  • SUniformWrapPanel:균일한 크기의 플로우 레이아웃
  • SCanvas:임의의 좌표와 크기로 Child를 배치할 수 있음
  • SConstraintCanvas:정적
  • SSplitter:분할선을 드래그하여 위젯 간 크기를 조절할 수 있음
  • SScrollBox:Content에 스크롤 바 추가
  • SWidgetSwitcher:위젯 전환 컨트롤, 위젯을 모두 Slot에 추가하고 Slot Index 설정으로 해당 위젯 표시
  • SDPIScaler:DPI 스케일 조정용
  • SBox:Content의 고정 크기, 최소 크기 또는 최대 크기 제어
  • SScaleBox:Content의 크기를 균일하게 스케일링

  • SListView:리스트 뷰
  • STileView:타일 뷰
  • STreeView:트리 뷰

도킹 윈도우

  • SDockTab
const TSharedRef<SDockTab> MajorTab = SNew(SDockTab).TabRole(ETabRole::MajorTab);
TabManager = FGlobalTabmanager::Get()->NewTabManager(MajorTab);
TabManager->RegisterTabSpawner(FName("MyDockTab"), FOnSpawnTab::CreateLambda([](const FSpawnTabArgs& Args) {
    TSharedRef<SDockTab> Tab = SNew(SDockTab).Content()[SNew(STextBlock).Text(FText::FromString("MyDockContent"))];
    return Tab;
})).SetDisplayName(FText::FromString("MyDock"))
    .SetMenuType(ETabSpawnerMenuType::Hidden);
TabManager->TryInvokeTab(FName("MyDockTab"));

기타

  • SBackgroundBlur:Content의 배경 블러 처리
  • SBorder:Content에 테두리 추가
  • STooltipPresenter:Content에 ToolTip 추가
  • SExpandableArea:Content를 확장하거나 축소 가능
  • SFxWidget
  • SMenuAnchor:영역에 컨텍스트 메뉴 추가

코드 참조

UE는 위젯 리플렉터를 제공하여 해당 UI의 생성 코드를 빠르게 찾을 수 있습니다. 시작점은 에디터 메인 패널의 상단 메뉴 바 - 도구 - 디버그 - 위젯 리플렉터입니다:

또한, 공식 엔진 소스 코드에는 대부분의 Slate 컨트롤의 기본 사용법을 보여주는 SlateViewer 프로젝트가 포함되어 있습니다:

IDE의 코드 검색 도구를 사용하여 엔진에서 해당 컨트롤의 매개변수 예제를 찾을 수 있으며, Visual Studio의 솔루션 탐색기에서 클래스 파생 관계를 통해 모든 컨트롤을 개괄적으로 볼 수 있습니다:

고급 사용자화

위에서 설명한 기능들은 UI 개발의 대부분의 요구사항을 충족시킬 수 있지만, 복잡한 상호작용 메커니즘과 렌더링 로직이 필요한 경우에는 SLeafWidget을 사용자 정의해야 합니다.
여기 드래그하여 이동할 수 있는 위젯을 구현한 좋은 예시가 있습니다:

코드는 다음과 같습니다:

#pragma once
#include "Styling/StyleColors.h"
#include "Brushes/SlateColorBrush.h"
class SCustomSlateWidget :public SLeafWidget {
    SLATE_DECLARE_WIDGET(SCustomSlateWidget, SLeafWidget)   //매크로 SLATE_DECLARE_WIDGET으로 Slate 컨트롤 선언
public:
    SLATE_BEGIN_ARGS(SCustomSlateWidget) {}
    SLATE_END_ARGS()
    void Construct(const SCustomSlateWidget::FArguments InArgs) {
        SetCursor(EMouseCursor::GrabHand);                  //컨트롤 영역에 마우스가 들어왔을 때의 스타일 설정
    }
protected:
    FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override {
        if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) {
            LastMouseDownPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());//마우스 위치를 스크린 공간에서 컨트롤의 로컬 공간으로 변환
            return FReply::Handled().CaptureMouse(SharedThis(this));                                //전역 마우스 이동의 고주파 캡처 시작
        }
        return FReply::Handled();
    }
    FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override{
        if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) {
            auto Window = FSlateApplication::Get().FindWidgetWindow(SharedThis(this));                  //해당 컨트롤의 윈도우 가져오기
            auto CurrentMousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
            auto CurrWindowPosition = Window-&gt;GetPositionInScreen();
            auto TargetPosition = CurrentMousePosition + CurrWindowPosition - LastMouseDownPosition;    //위치 이동 계산
            Window-&gt;MoveWindowTo(TargetPosition);
        }
        return FReply::Handled();
    }
    FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override {
        LastMouseDownPosition = FVector2D::ZeroVector;
        return FReply::Handled().ReleaseMouseCapture();                                         //마우스 캡처 해제
    }
    int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override {
        static FSlateFontInfo FontInfo = FCoreStyle::GetDefaultFontStyle("Bold",30);
        static const FSlateBrush* Bursh = FAppStyle::GetBrush("WhiteBrush");
        FLinearColor BackgroundColor = IsHovered()? FLinearColor(0.6f, 0.5f, 0.9f) : FLinearColor(0.1f, 0.5f, 0.9f);
        FSlateDrawElement::MakeBox(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Bursh, ESlateDrawEffect::None, BackgroundColor);       //배경 그리기
        FSlateDrawElement::MakeText(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), FText::FromString("Hello Slate"), FontInfo);          //텍스트 그리기
        return LayerId;
    }
    FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override {
        return FVector2D::ZeroVector;                                                           //제로 벡터를 사용하면 부모 컨트롤의 내용을 채움
    }
private:
    FVector2D LastMouseDownPosition = FVector2D::ZeroVector;
};
// 아래 코드는 cpp에 배치해야 함
SLATE_IMPLEMENT_WIDGET(SCustomSlateWidget)                          //매크로 SLATE_IMPLEMENT_WIDGET으로 코드 구현 생성
void SCustomSlateWidget::PrivateRegisterAttributes(FSlateAttributeInitializer& AttributeInitializer)    //이 함수는 반드시 구현해야 함
{
}`

`FSlateApplication::Get().AddWindow(
    SNew(SWindow)
    .ClientSize(FVector2D(200,200))                 //윈도우 크기를 200*200으로 설정
    .SizingRule(ESizingRule::FixedSize)             //윈도우 크기 고정
    .SupportsMaximize(false)                        //최대화 비활성화
    .SupportsMinimize(false)                        //최소화 비활성화
    .CreateTitleBar(false)                          //타이틀 바 생성 안 함
    .AutoCenter(EAutoCenter::PreferredWorkArea)     //중앙 정렬 표시
    .IsTopmostWindow(true)                          //최상위 윈도우로 설정
    [
        SNew(SCustomSlateWidget)
    ]
);

사용자 정의 SLeafWidget의 작업 순서는 다음과 같습니다:

  • 매크로 **SLATE_DECLARE_WIDGET(WidgetType, ParentType)**를 사용하여 컨트롤 선언
  • 매크로 **SLATE_IMPLEMENT_WIDGET(WidgetType)**를 cpp에서 코드 구현을 정의하고, 다음 함수를 구현:
    • void WidgetType::PrivateRegisterAttributes(FSlateAttributeInitializer& AttributeInitializer)
  • 컨트롤 크기 계산 함수 재정의:
    • virtual FVector2D SWidget::ComputeDesiredSize(float LayoutScaleMultiplier) const = 0
  • 각종 상호작용 이벤트 재정의
  • 컨트롤의 속성 상태 기계 조정
  • 속성 상태 기계에 따라 그리기 함수(OnPaint)에서 그리기 요소 추가

상호작용 이벤트

SWidget은 개발자가 오버라이드할 수 있는 다양한 상호작용 이벤트(On으로 시작하는 가상 함수)를 제공합니다:

  • OnFocusReceived: 포커스 획득
  • OnFocusLost: 포커스 상실
  • OnFocusChanging: 포커스 변경
  • OnKeyChar: 키보드 입력 시
  • OnKeyDown: 키보드 키 누름
  • OnKeyUp: 키보드 키 뗌
  • OnMouseButtonDown: 마우스 버튼 누름
  • OnMouseButtonUp: 마우스 버튼 뗌
  • OnMouseMove: 마우스 이동
  • OnMouseEnter: 마우스가 위젯 영역에 들어가는 순간
  • OnMouseLeave: 마우스가 위젯 영역을 떠나는 순간
  • OnMouseButtonDoubleClick: 마우스 더블 클릭
  • OnMouseWheel: 마우스 휠 스크롤
  • OnDragEnter: 드래그한 항목이 위젯 영역에 들어가는 순간
  • OnDragLeave: 드래그한 항목이 위젯 영역을 떠나는 순간
  • OnDragOver: 드래그한 항목이 위젯 영역을 덮을 때
  • OnDrop: 위젯 위에서 드래그한 항목을 놓을 때
  • ...

자세한 설명은 SWidget의 정의를 참고하세요. 각 이벤트의 설명과 트리거 시점이 있으며, 사용법은 UE 소스 코드에서 해당 예제를 검색할 수 있습니다.

사용 설명:

  • 이러한 이벤트들은 FSlateApplication이 호출을 담당하며, 함수 매개변수는 일반적으로 해당 이벤트가 가진 모든 정보를 포함합니다. 우리는 이를 오버라이드(override)하고 이벤트 데이터에 따라 자체 로직을 수행하기만 하면 됩니다.
  • 이벤트의 반환 타입은 FReply로, 이벤트의 후속 처리를 정의하는 데 사용됩니다. 생성자가 private이므로 다음과 같은 정적 함수를 사용하여 인스턴스를 생성해야 합니다:
    • FReplay::Handled(): 해당 이벤트가 처리되었음을 나타내며, 이벤트는 더 이상 하위 레벨로 전달되지 않습니다.
    • FReplay::Unhandled(): 해당 컨트롤이 이벤트를 처리하지 않음을 나타내며, 이벤트는 하위 레벨로 계속 전달됩니다.
  • FReply는 이벤트 종료 시 처리를 위한 일련의 작업을 제공합니다:

그리기 이벤트

SWidget의 실제 그리기 함수는 Paint이지만, 그리기 과정에 몇 가지 고정된 단계가 있어 Slate는 그리기 이벤트 OnPaint를 제공하여 사용자 정의 그리기 로직을 구현할 수 있게 합니다. 정의는 다음과 같습니다:

virtual int32 OnPaint(const FPaintArgs& Args, 
                      const FGeometry& AllottedGeometry,
                      const FSlateRect& MyCullingRect, 
                      FSlateWindowElementList& OutDrawElements, 
                      int32 LayerId,
                      const FWidgetStyle& InWidgetStyle,
                      bool bParentEnabled) const = 0;

매개변수 설명:

  • const FPaintArgs& Args: 그리기 이벤트의 매개변수로, 현재 시간, 간격, 부모 창 등을 얻을 수 있음
  • const FGeometry& AllottedGeometry: 이 SWidget에 할당된 기하학적 크기(부모 창 기준)
  • const FSlateRect& MyCullingRect: 이 SWidget의 클리핑 영역으로, 그리기 중 이 사각형을 기준으로 그리기 요소를 제외할 수 있음
  • FSlateWindowElementList& OutDrawElements: 전체 SWindow의 그리기 요소 목록으로, 우리는 이 객체를 수정하여 그리기를 수행
  • int32 LayerId: 그리기의 LayerID로, 일반적으로 UI 레이어를 구분하여 같은 레벨의 그리기 요소를 통합 처리하는 데 사용
  • const FWidgetStyle& InWidgetStyle: 부모 창에서 전달된 Style
  • bool bParentEnabled: 부모 창이 활성화 상태인지 여부

OnPaint 함수에서는 주로 FSlateDrawElement의 Make로 시작하는 다양한 정적 함수를 통해 그리기 요소를 생성합니다. 다음과 같은 함수들이 있습니다:

static void MakeBox( //박스 그리기, Brush 설정을 통해 색상 블록이나 이미지가 될 수 있음
    FSlateWindowElementList& ElementList,
    uint32 InLayer,
    const FPaintGeometry& PaintGeometry,
    const FSlateBrush* InBrush,
    ESlateDrawEffect InDrawEffects = ESlateDrawEffect::None,
    const FLinearColor& InTint = FLinearColor::White );
static void MakeRotatedBox(...);        //회전된 박스 그리기
static void MakeGradient(...);          //그라데이션 박스 그리기 
static void MakeText(...);              //텍스트 그리기
static void MakeShapedText(...);        //형상 텍스트 그리기
static void MakeLines(...);             //선 그리기
static void MakeSpline(...)             //곡선 그리기
static void MakeDrawSpaceSpline(...);    //그리기 공간에서 곡선 그리기
static void MakeCubicBezierSpline(...);  //3차 베지어 곡선 그리기
static void MakeViewport(...)           //FSlateViewport 그리기
static void MakeCustom(...);            //사용자 정의 요소 그리기, 요소의 Render 함수를 직접 구현 가능
static void MakeCustomVerts(...);       //정점을 기반으로 그리기
static void MakePostProcessPass(...);   //후처리 필터 그리기(블러)

이러한 함수들은 UE에서 매우 많이 사용되며, 관련 코드를 검색하여 참고할 수 있습니다.

자세한 내용은 Runtime\SlateCore\Public\Rendering\DrawElements.h를 참조하세요

FSlateBrush

FSlateBrush는 채우기 영역을 제어하는 데 사용되며, 인터페이스에서 사용 빈도가 높기 때문에 Slate는 다음과 같은 편리한 확장을 제공합니다:

  • FSlateBorderBrush: 테두리 채우기
  • FSlateBoxBrush: 박스 브러시
  • FSlateRoundedBoxBrush: 둥근 사각형 브러시
  • FSlateColorBrush: 색상 브러시
  • FSlateImageBrush: 이미지
    • FSlateVectorImageBrush: 벡터 이미지
  • FSlateDynamicImageBrush: 동적으로 이미지를 전환할 수 있는 브러시
  • FSlateNoResource: 리소스가 없는 브러시

위의 브러시들은 본질적으로 특정 매개변수를 통해 FSlateBrush를 구성하는 것입니다. 위의 브러시들을 조합해야 하는 경우, 해당 생성자 매개변수를 기반으로 직접 조합할 수 있습니다.

UE는 몇 가지 내장된 FSlateBrush를 제공하며, 소스 코드에서 ::Get().GetBrush를 검색하여 관련 사용법을 찾을 수 있습니다:

예시

SWidget 전체를 빨간색으로 채우려면 다음과 같은 코드를 사용할 수 있습니다:

int32 SCustomSlateWidget::OnPaint(const FPaintArgs& Args,
                                  const FGeometry& AllottedGeometry,
                                  const FSlateRect& MyCullingRect,
                                  FSlateWindowElementList& OutDrawElements,
                                  int32 LayerId,
                                  const FWidgetStyle& InWidgetStyle,
                                  bool bParentEnabled) const {
    static FSlateColorBrush ColorBrush(FColor(255,0,0));
    FSlateDrawElement::MakeBox(OutDrawElements,
                               LayerId, 
                               AllottedGeometry.ToPaintGeometry(),
                               &ColorBrush,
                               ESlateDrawEffect::None, 
                               ColorBrush.TintColor.GetColor(InWidgetStyle));
    return LayerId;
}

ESlateDrawEffect

ESlateDrawEffect는 Slate에 몇 가지 효과를 제공하며, 다음과 같은 값을 가질 수 있습니다:

enum class ESlateDrawEffect : uint8
{
    None                    = 0,            //효과 없음
    NoBlending          = 1 &lt;&lt; 0,           //블렌딩 없음
    PreMultipliedAlpha  = 1 &lt;&lt; 1,           //사전 곱셈된 알파
    NoGamma             = 1 &lt;&lt; 2,           //감마 보정 없음
    InvertAlpha         = 1 &lt;&lt; 3,           //알파 반전
    // ^^ These Match ESlateBatchDrawFlag ^^
    NoPixelSnapping     = 1 &lt;&lt; 4,           //픽셀 스냅핑 끄기
    DisabledEffect      = 1 &lt;&lt; 5,           //비활성화 상태 효과
    IgnoreTextureAlpha  = 1 &lt;&lt; 6,           //텍스처 알파 무시
    ReverseGamma            = 1 &lt;&lt; 7        //감마 반전
};

FSlateFontInfo

FSlateFontInfo는 폰트 정보를 지정하는 데 사용되며, UE의 폰트 에셋에 해당합니다. 다음은 그 일부 정의입니다:

struct SLATECORE_API FSlateFontInfo
{
    /**The font object (valid when used from UMG or a Slate widget style asset) */
    TObjectPtr<const UObject> FontObject;
    /**The material to use when rendering this font */
    TObjectPtr<UObject> FontMaterial;
    /**Settings for applying an outline to a font */
    FFontOutlineSettings OutlineSettings;
    /**The composite font data to use (valid when used with a Slate style set in C++) */
    TSharedPtr<const FCompositeFont> CompositeFont;
    /**The name of the font to use from the default typeface (None will use the first entry) */
    FName TypefaceFontName;
    /**
     * The font size is a measure in point values. The conversion of points to Slate Units is done at 96 dpi.  So if 
     * you're using a tool like Photoshop to prototype layouts and UI mock ups, be sure to change the default dpi 
     * measurements from 72 dpi to 96 dpi.
     */
    int32 Size;
    /**The uniform spacing (or tracking) between all characters in the text. */
    int32 LetterSpacing = 0;
    /**The font fallback level. Runtime only, don't set on shared FSlateFontInfo, as it may change the font elsewhere (make a copy). */
    EFontFallback FontFallback;
};

UE에는 전용 폰트 편집기가 있어 C++에서 직접 구성하기는 까다롭지만, UE는 다음과 같은 내장 FSlateFontInfo를 제공합니다:

  • FCoreStyle::Get().GetFontStyle("NormalFont")
  • FCoreStyle::Get().GetFontStyle("SmallFont")
  • FCoreStyle::GetDefaultFontStyle("Regular",FontSize);
  • FCoreStyle::GetDefaultFontStyle("Bold",FontSize)
  • FCoreStyle::GetDefaultFontStyle("Italic",FontSize);
  • FCoreStyle::GetDefaultFontStyle("Mono",FontSize);

FontMeasure

대부분의 GUI 프레임워크에는 **폰트 측정(FontMeasure)**이라는 개념이 있습니다: 특정 폰트 형식의 텍스트의 그리기 너비와 높이를 측정합니다.
UE에서는 다음과 같은 방법으로 측정할 수 있습니다(의사 코드):
FSlateApplication::Get().GetRenderer()-&gt;GetFontMeasureService()-&gt;Measure( const FText& Text, //텍스트 const FSlateFontInfo& InFontInfo, //폰트 float FontScale //폰트 크기 조정 ) -&gt; FVector2D //너비(X)와 높이(Y) 반환

성능 최적화

Slate는 UI의 Paint와 Tick 성능을 최적화하기 위해 SInvalidationPanel 컨트롤을 제공합니다. 이 컨트롤을 사용하면 자식 컨트롤에서 리소스를 많이 소모하는 작업을 캐시할 수 있으며, 특정 작업으로 인해 **캐시가 무효화(Invalidate)**될 때만 다시 실행됩니다.
자세한 내용은 다음을 참조하세요: 언리얼 엔진 Slate 아키텍처 이해하기 - 폴링 데이터 흐름과 위임 | 언리얼 엔진 5.2 문서
자식 컨트롤의 Invalidate() 함수를 수동으로 호출하여 캐시를 무효화할 수 있습니다:
void SWidget::Invalidate(EInvalidateWidgetReason InvalidateReason);
EInvalidateWidgetReason의 값은 다음과 같을 수 있습니다:

enum class EInvalidateWidgetReason : uint8
{
    None = 0,
    Layout = 1 &lt;&lt; 0,        //위젯의 크기를 변경해야 하는 경우 레이아웃을 무효화합니다. 이는 비용이 많이 드는 작업이므로, 단순히 위젯을 다시 그리기만 하면 되는 경우에는 사용하지 마세요
    Paint = 1 &lt;&lt; 1,             //그리기 무효화
    Volatility = 1 &lt;&lt; 2,        //Volatility가 변경될 때 트리거됨
    ChildOrder = 1 &lt;&lt; 3,        //자식 위젯이 추가되거나 제거될 때 트리거되며, 무효화하면 Child의 순서가 재구성됨
    RenderTransform = 1 &lt;&lt; 4,   //RenderTransform이 변경될 때 트리거됨
    Visibility = 1 &lt;&lt; 5,        //가시성이 변경될 때 트리거됨
    AttributeRegistration = 1 &lt;&lt; 6,     //속성이 바인딩될 때 트리거됨
    Prepass = 1 &lt;&lt; 7,                   //Prepass 무효화
    /**
     * 페인팅이나 크기 조정과 관련된 일반 속성을 변경할 때는 Paint 무효화를 사용하세요.
     * 또한 변경된 속성이 Volatility에 영향을 미치는 경우, Volatility를 무효화하여
     * 재계산되고 캐시되도록 하는 것이 중요합니다.
     */
    PaintAndVolatility = Paint | Volatility,
    /**
     * 페인팅이나 크기 조정과 관련된 일반 속성을 변경할 때는 Layout 무효화를 사용하세요.
     * 또한 변경된 속성이 Volatility에 영향을 미치는 경우, Volatility를 무효화하여
     * 재계산되고 캐시되도록 하는 것이 중요합니다.
     */
    LayoutAndVolatility = Layout | Volatility,
};

Slate는 속성(SlateAttribute) 기반의 방식을 제공합니다: 속성의 변경을 캐시 재구성과 연결합니다
구체적인 방법은 다음과 같습니다:

  • TSlateAttribute로 속성을 감싸고(TAttribute와는 용도가 다름), TSlateAttribute로 감싼 속성은 생성자 초기화 시 (*this) 매개변수를 전달해야 합니다
  • void WidgetType::PrivateRegisterAttributes(...) 함수에서 SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(_Initializer, _Property, _Reason) 매크로를 사용하여 속성을 등록합니다

다음은 간단한 예시입니다:

class SCustomSlateWidget :public SLeafWidget {
    SLATE_DECLARE_WIDGET(SCustomSlateWidget, SLeafWidget)
public:
    SLATE_BEGIN_ARGS(SCustomSlateWidget){}
    SLATE_END_ARGS()
    void Construct(const SCustomSlateWidget::FArguments InArgs){            
    }
protected:
    SCustomSlateWidget()
        : ClickedCounter(*this) {   //SlateAttribute 초기화
    }
    FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override {
        ClickedCounter.Set(*this, ClickedCounter.Get() + 1);        //카운터 +1, 여기서는 마우스 클릭으로 간주
        return FReply::Handled();
    }
    int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override {
        static FSlateFontInfo FontInfo = FCoreStyle::GetDefaultFontStyle("Bold",30);
        FSlateDrawElement::MakeText(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), FText::FromString(FString::FromInt(ClickedCounter.Get())), FontInfo);                 //현재 마우스 클릭 횟수 그리기
        return LayerId;
    }
    FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override {
        return FVector2D::ZeroVector;                                                           
    }
private:
    TSlateAttribute&lt;int&gt; ClickedCounter;        //마우스 클릭 횟수를 기록하는 카운터
};

SLATE_IMPLEMENT_WIDGET(SCustomSlateWidget)
void SCustomSlateWidget::PrivateRegisterAttributes(FSlateAttributeInitializer& AttributeInitializer) {
    //ClickedCounter의 값 변경을 다시 그리기 이벤트와 연결
    //SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, ClickedCounter, EInvalidateWidgetReason::Paint);
}

FSlateApplication::Get().AddWindow(
    SNew(SWindow)
    .SupportsMaximize(false)
    .SupportsMinimize(false)
    .IsTopmostWindow(true)
    .ClientSize(FVector2D(200,200))
    .SizingRule(ESizingRule::FixedSize)
    .CreateTitleBar(false)
    .AutoCenter(EAutoCenter::PreferredWorkArea)
    [
        SNew(SInvalidationPanel)            // SInvalidationPanel 사용
        [
            SNew(SCustomSlateWidget)
        ]
    ]
);

이 코드는 클릭 가능한 블록을 생성합니다:

블록을 클릭하면 ClickedCounter+1이 되지만, 화면에는 변화가 없습니다. PrivateRegisterAttributes의 주석을 해제하면:

Slate에서 UMG로

UE의 로직 프로그래밍은 UObject 위에 구축되어 있으며, Slate는 극한의 성능을 추구하기 위해 UI 객체를 관리하는데 UObject와 같은 무거운 메커니즘을 사용하지 않았습니다
Slate에서는 **공유 포인터(Shared Pointer)**를 사용하여 UI 객체의 수명 주기를 관리합니다
게임 개발자의 UI 개발 효율성을 높이기 위해, UE는 UObject로 Slate 위젯을 감싸고, 리플렉션을 통해 Slate 위젯의 속성과 함수를 노출시켰으며, 동시에 시각적인 UI 디자인을 지원합니다. 이 인터페이스 레이어가 바로 우리가 잘 알고 있는 UMG입니다
UMG에 대해서는 공식 문서에 자세한 설명이 있습니다:

여기 Slate와 UMG의 관계를 잘 보여주는 간단한 코드가 있습니다:

#pragma once
#include "UObject/ObjectMacros.h"
#include "Widgets/Text/STextBlock.h"
#include "Components/Widget.h"
#include "CustomUWidget.generated.h"
UCLASS(meta = (DisplayName = "CustomWidget"))                   //DisplayName은 위젯 이름입니다
class UMGEXTENDUTILITIES_API UCustomWidget : public UWidget
{
    GENERATED_BODY()
protected:
    virtual TSharedRef&lt;SWidget&gt; RebuildWidget() override {                      //위젯을 재구축하고 멤버 변수에 할당합니다
        return SAssignNew(MyTextBlock,STextBlock);
    }
    virtual void SynchronizeProperties() override {                             //UObject의 속성을 Slate 위젯에 동기화합니다
        MyTextBlock-&gt;SetText(Text);
        Super::SynchronizeProperties();
    }
    virtual void ReleaseSlateResources(bool bReleaseChildren) override {        //Slate 리소스를 해제합니다
        MyTextBlock.Reset();
    }
#if WITH_EDITOR
    virtual const FText GetPaletteCategory() override {                         //에디터에서의 위젯 카테고리
        return FText::FromString("CustomCategory");
    }
#endif
public:
    UFUNCTION(BlueprintCallable)        //블루프린트에 함수 공개
    FText GetText() const {
        return Text;
    }
    UFUNCTION(BlueprintCallable)
    void SetText(FText InText) {
        Text = InText;
        if (MyTextBlock) {              //속성 설정, 위젯이 이미 생성되었을 때 직접 값을 위젯에 설정합니다
            MyTextBlock-&gt;SetText(Text);
        }
    }
protected:
    UPROPERTY(EditAnywhere, BlueprintGetter = "GetText", BlueprintSetter = "SetText")
    FText Text;                         //에디터에 속성 공개
    TSharedPtr&lt;STextBlock&gt; MyTextBlock; //실제 Slate 위젯
};

C++에서 UMG를 사용하는 방법은 다음과 같습니다:

UWidget* UMGWidget = LoadObject&lt;UWidget&gt;(nullptr, TEXT("{AssetPath}"));                //에셋에서 로드, NewObject로도 가능
TSharedPtr&lt;SWidget&gt; SlateWidget = UMGWidget-&gt;TakeWidget();                             //이 작업은 새로운 SWidget을 생성합니다
TSharedPtr&lt;SWidget&gt; CachedSlateWidget = UMGWidget-&gt;GetCachedWidget();                  //마지막으로 생성된 SWidget을 가져옵니다
//이후 Slate 사용 방식으로 사용

생명 주기 관리

UWidgetUObject의 가비지 컬렉션을 따르며, SWidget의 공유 포인터를 보유합니다. 컨트롤이 표시될 때(TSharedPtr&lt;SWidget&gt;가 여전히 존재할 때) UWidget이 가비지 컬렉션되지 않도록 하기 위해, UMG는 FGCObject를 상속받은 SObjectWidget 컨트롤을 사용하여 UWidget에 대한 참조를 추가합니다.

위의 예제 코드로 돌아가보면, UWidget::TakeWidget()을 호출해서 얻는 것은 UWidget::RebuildWidget()에서 생성된 컨트롤이 아니라, SObjectWidget으로 한 번 더 감싼 것임을 알 수 있습니다. 생성 과정에서의 계층 관계는 다음과 같습니다:

소멸 시 해제 순서는 다음과 같습니다:

  • SObjectWidgetFGCObject를 상속받으며, UWidget에 대한 참조를 추가하여 SObjectWidget이 존재하는 동안 UWidget이 GC되지 않도록 보장합니다
  • 컨트롤이 소멸을 시작할 때, SObjectWidget의 참조 카운트가 0이 되어 소멸되며, 이때 UWidget에 대한 참조가 해제됩니다
  • UWidget의 참조가 0이 되면, 다음 가비지 컬렉션 시에 해제되며, 이때 보유하고 있던 SWidget 공유 포인터도 해제됩니다

 
... 파트 4부터는 플러그인 개발로 계속...