[Unreal Engine] SWidget과 UWidget

Jangmanbo·2024년 9월 10일
1

SWidget

UE5 슬레이트 개요
슬레이트는 언리얼에서 UI를 만들기 위한 프레임워크이며, SWidget은 슬레이트를 구성하는 모든 UI들의 기본 클래스이다.

인게임 화면뿐만 아니라 이런 언리얼 에디터 화면도 슬레이트로 구성한 것이다.

SWidget의 기능


UE5 슬레이트 아키텍처
언리얼 문서에서 말하는 SWidget의 주요 함수는 다음과 같다. 이중에 렌더링과 입력 처리 쪽을 살펴보았다.

1. 렌더링

int32 SCompoundWidget::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{

	// A CompoundWidget just draws its children
	FArrangedChildren ArrangedChildren(EVisibility::Visible);
	{
		this->ArrangeChildren(AllottedGeometry, ArrangedChildren);
	}

	// There may be zero elements in this array if our child collapsed/hidden
	if( ArrangedChildren.Num() > 0 )
	{
		check( ArrangedChildren.Num() == 1 );
		FArrangedWidget& TheChild = ArrangedChildren[0];

		FWidgetStyle CompoundedWidgetStyle = FWidgetStyle(InWidgetStyle)
			.BlendColorAndOpacityTint(ColorAndOpacity.Get())
			.SetForegroundColor( GetForegroundColor() );

		int32 Layer = 0;
		Layer = TheChild.Widget->Paint( Args.WithNewParent(this), TheChild.Geometry, MyCullingRect, OutDrawElements, LayerId + 1, CompoundedWidgetStyle, ShouldBeEnabled( bParentEnabled ) );
		
        return Layer;
	}
	return LayerId;
}
int32 SBorder::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
	const FSlateBrush* BrushResource = BorderImage.Get();
		
	const bool bEnabled = ShouldBeEnabled(bParentEnabled);

	if ( BrushResource && BrushResource->DrawAs != ESlateBrushDrawType::NoDrawType )
	{
		const bool bShowDisabledEffect = ShowDisabledEffect.Get();
		const ESlateDrawEffect DrawEffects = (bShowDisabledEffect && !bEnabled) ? ESlateDrawEffect::DisabledEffect : ESlateDrawEffect::None;

		if (bFlipForRightToLeftFlowDirection && GSlateFlowDirection == EFlowDirection::RightToLeft)
		{
			const FGeometry FlippedGeometry = AllottedGeometry.MakeChild(FSlateRenderTransform(FScale2D(-1, 1)));
			FSlateDrawElement::MakeBox(
				OutDrawElements,
				LayerId,
				FlippedGeometry.ToPaintGeometry(),
				BrushResource,
				DrawEffects,
				BrushResource->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint() * BorderBackgroundColor.Get().GetColor(InWidgetStyle)
			);
		}
		else
		{
			FSlateDrawElement::MakeBox(
				OutDrawElements,
				LayerId,
				AllottedGeometry.ToPaintGeometry(),
				BrushResource,
				DrawEffects,
				BrushResource->GetTint(InWidgetStyle) * InWidgetStyle.GetColorAndOpacityTint() * BorderBackgroundColor.Get().GetColor(InWidgetStyle)
			);
		}
	}

	return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bEnabled );
}

SWidget이 실제로 화면에 그려지는 순간에 OnPaint에서 렌더링 관련 처리를 한다.

ArrangeChildren로 자식 위젯이 있다면 자식 위젯들의 배치를 정하고,
이미지나 텍스트가 그려져야 한다면 FSlateDrawElement::MakeBox로 그리기도 한다.

2. 입력

SWidget은 화면에 어떻게 그려지는지를 처리하는 것 뿐만 아니라 유저와의 상호작용에도 SWidget이 담당하고 있다.

bool FSlateApplication::ProcessMouseButtonDownEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& MouseEvent )
{
	// ...
	if (!SlateUser->IsDragDropping())
	{
		FReply Reply = FReply::Unhandled();
		if (SlateUser->HasCapture(MouseEvent.GetPointerIndex()))
		{
			// ...
			if ( !Reply.IsEventHandled() )
			{
				Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), MouseEvent,
					[this] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
				{
					// ...
					if ( !Event.IsTouchEvent() || ( !TempReply.IsEventHandled() && this->bTouchFallbackToMouse ) )
					{
                    	// SWidget::OnMouseButtonDown 호출
						TempReply = InMouseCaptorWidget.Widget->OnMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
					}
					return TempReply;
				}, ESlateDebuggingInputEvent::MouseButtonDown);
			}
		}
        // ...
	}

	return true;
}

사용자가 어떤 입력을 했을 때 FSlateApplication에서 SWidget의 함수를 호출하는 것을 볼 수 있다.
참고: [Unreal Engine] Input System




UWidget

UMG(Unreal Motion Graphics) 시스템에서 제공하는 클래스이다.
즉, UMG에서의 UI 배치, 스타일 변경 등을 편리하게 관리하기 위한 클래스이다.

/**
 * This is the base class for all wrapped Slate controls that are exposed to UObjects.
 */
UCLASS(Abstract, BlueprintType, Blueprintable)
class UMG_API UWidget : public UVisual
{
	// ...
protected:
	/** The underlying SWidget. */
	TWeakPtr<SWidget> MyWidget;

	/** The underlying SWidget contained in a SObjectWidget */
	TWeakPtr<SObjectWidget> MyGCWidget;

	/** Native property bindings. */
	UPROPERTY(Transient)
	TArray<UPropertyBinding*> NativeBindings;

	static TArray<TSubclassOf<UPropertyBinding>> BinderClasses;
    
    // ...
};

This is the base class for all wrapped Slate controls that are exposed to UObjects.

UWidgetUObject 중에서 슬레이트 컨트롤을 래핑하는 모든 클래스들의 기본 클래스이다.

따라서 UWidgetSWidget을 포인터로 가지고 있지만
UWidget의 부모 클래스인 UVisual, UObjectSWidget 관련 기능이 없는 것을 볼 수 있다.

UWidget 기능

일반적인 UI를 구성할 때 프로그래머가 직접 SWidget을 변경하는 일은 흔치 않을 것이다.
왜냐하면 앞서 말했던 렌더링도, 입력도 UWidget에서 변경하고 처리할 수 있기 때문이다.

UWidget을 상속받는 UButton을 살펴보자.

void UButton::SetColorAndOpacity(FLinearColor InColorAndOpacity)
{
	ColorAndOpacity = InColorAndOpacity;
	if ( MyButton.IsValid() )
	{
		MyButton->SetColorAndOpacity(InColorAndOpacity);
	}
}

화면에 어떻게 그려지는지를 변경하는 함수 중 하나인 SetColorAndOpacity를 보면 MyButtonSetColorAndOpacity를 호출하는 것을 볼 수 있다.

TSharedRef<SWidget> UButton::RebuildWidget()
{
	MyButton = SNew(SButton)
		.OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
		.OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
		.OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
		.OnHovered_UObject( this, &ThisClass::SlateHandleHovered )
		.OnUnhovered_UObject( this, &ThisClass::SlateHandleUnhovered )
		.ButtonStyle(&WidgetStyle)
		.ClickMethod(ClickMethod)
		.TouchMethod(TouchMethod)
		.PressMethod(PressMethod)
		.IsFocusable(IsFocusable)
		;

	if ( GetChildrenCount() > 0 )
	{
		Cast<UButtonSlot>(GetContentSlot())->BuildSlot(MyButton.ToSharedRef());
	}
	
	return MyButton.ToSharedRef();
}

SButton에서 발생하는 다양한 델리게이트는 UButton에서 바인드하고 있어, UButton에서 다양한 처리를 할 수 있다.

결국 실제 게임에서 그려지거나 동작에 대한 처리는 모두 SWidget에서 처리할 수 있지만,
UMG 시스템을 이용하여 더 편리하기 작업하기 위해서 SWidget을 래핑하는 UWidget 클래스를 사용하고 있다고 볼 수 있겠다.

0개의 댓글