UnrealEngine/UMG

[UE5] UWidget 과 SWidget, UMG 깊게 파헤치기

H프레임 2022. 12. 24. 19:02

앞선 포스팅에서 UMG 라는 것이 있고, UI를 디자인 할 수 있는 UUserWidget을 부모로 하는 블루프린트 만들기를 해보았다. 그리고 간략하게 팔레트에 있는 위젯을 가져다 계층구조(하이어라키)에 넣으면 중앙 디자인 미리보기에 종류에 따른 위젯들이 나타나는 것을 보았다. 

 

거기까지는 아주 기초, 그리고 배치할 수 있다는 것을 알았으니 오늘은 조금 깊게 파고들고자 한다. 프로그래머가 아니라면 흥미 없을 수 있는 이야기다. 참고로 프로그래머도 흥미 없을 수 있다. 

 

 

 

UUserWidget을 상속받은 UMG를 열어보면 UI디자이너라는 패널에서 가장 왼쪽 상단에 Palette라는 곳이 있다. 이곳에 노출되는 위젯들은 모두 UWidget을 상속받은 위젯들이다. UWidget이란 SWidget이라는 언리얼 슬레이트 위젯을 컨트롤하고 있는 위젯 클래스를 말한다.

 

 

 

 

 

SWidget, Slate

 

 

 

 

그럼 SWidget은 무엇이며 슬레이트는 또 무엇일까.

 

슬레이트(Slate)란 언리얼에서 사용하는커스텀 UI 프레임워크를 뜻한다. 실제 언리얼에 보이는 모든 UI는 슬레이트를 거쳐 만들어졌다. 

 

 

 

 

 

이런 것 하나하나 다 슬레이트로 결국은 이루어진 셈이다.

 

그럼 슬레이트로 UI를 만들어야하는가? 꼭 그렇지는 않다. 하지만 필요에 의해 슬레이트를 사용해야하는 경우도 있다. 예를 들면 툴을 직접 만든다거나 하는 것이 되겠다. 슬레이트는 추후에 튜토리얼을 따라해보면서 해볼 생각이다. 앞에서도 말했지만 언리얼 UMG를 쓰다보면 꽁꽁 숨겨져있어서 직접 컨트롤하지 못하게 해둔 옵션이라거나 쓰고 싶으면 상속받아서 써~ 라고 해둔 것들이 있기 때문에 UWidget을 상속받는 나만의 위젯 하나,  Slate를 상속받아서 툴을 제공하는 것 하나를 만드는 것이 UMG 프로젝트의 최종 목표가 될 것 같다.

 

 

 

 

 

슬레이트는 따로 다루겠지만 혹시라도 먼저 궁금한 사람들을 위해 공식 문서 주소를 첨부해두겠다.

 

1. 개요

https://docs.unrealengine.com/5.1/ko/slate-overview-for-unreal-engine/

 

슬레이트 개요

크로스 플랫폼 Slate UI 프레임워크에 대한 개요입니다.

docs.unrealengine.com

 

 

2. 슬레이트 아키텍쳐

https://docs.unrealengine.com/5.1/ko/understanding-the-slate-ui-architecture-in-unreal-engine/

 

참고로 번역이 좀 이상하다. 영어로 보는게 더 나을 것 같다..

 

 

 

 

자 그러면 UWidget과 SWidget은 어떻게 이루어져 있을까? 

 

 

 

 

UWidget 

 

 

 

 

우선 UWidget은 UVisual 이라는 클래스를 상속받고 있다. UWidget 위에 부모클래스가 있는 이유는 슬레이트위젯을 컨트롤하지 않는 UI도 존재하기 때문이다. PanelSlot이 그 예가 되는데 이것도 추후 설명하도록 하겠다. 

 

언리얼 클래스의 기본은 UObject이다. 위젯들도 당연히 최상위 부모는UObject로 이루어져 있다. 

 

 

언리얼 코드로 들어가보면

 

 

 

 

 

 

UVisual은 UMG 요소들의 베이스가 되는 클래스라는 주석이 쓰여져있다.

 

 

 

 

 

UWidget의 경우 UObject에 노출된다는 설명과, 이 클래스는 슬레이트 컨트롤을 감싸고 있는 베이스 클래스가 된다라고 직역할 수 있을 것 같은데 wrapper 클래스 같은 역할을 하는 것 같다. 

 

UWidget 헤더에서 아래로 쭉 내려보면,

 

 

 

 

 

SWidget을 참조하고 있다는 사실을 확인할 수 있다. 그리고 UWidget의 기능을 통해 TWeakPtr을 SharedPtr로 변환해서 상속받은 자식 클래스들이 요긴하게 사용할 것이다.

 

사실 Palette에서 UWidget이라는 날것 그대로를 사용하지 않고 CanvasPanel, Image, TextBlock, Button, ScrollBox등을 가져다 쓰고있으니 이러한 것들을 예로 들어보자.

 

우선 자식위젯을 가질 수 있는 CanvasPanel과, 자식위젯을 가질 수 없는 Image 위젯을 하나씩 파헤쳐보기로 하자.

 

 

 

 

UImage

 

 

 

 

 

 

위에서 계속 언급했던 UWidget을 상속받은 UImage 클래스이다. 상단 주석을 확인해보면 No Children 이라고 명시되어있다.  그 이유가 뭘까? 사실 UWidget에는 자식을 갖거나, 그 자식을 통해 무언가를 처리하는 기능을 넣어둔 건 아니다. 하지만, 내가 어떠한 위젯의 자식인가? 하는 것을 알 수 있는 기능은 존재한다. 

 

 

 

 

 

 

UImage 헤더에서 SWidget을 상속받은 SImage를 갖고 있다는 것을 확인할 수 있다.

 

그럼 이 MyImage라는 SImage를 갖고 도대체 뭘 하는걸까?

 

UImage는 밖에서 일어난 변화를 감지해서 SWidget에 실질적으로 적용시켜주는 역할을 한다.

UMG에 Image를 넣고 클릭해보면 오른쪽 디테일 패널에 Image가 갖고있는 속성들을 보여줄것이다.

 

 

 

 

 

 

 

내가 여기서 Brush를 변경하거나(sprite같은 걸 넣을 수 있다.) Color and Opacity에 변화를 주게되면 UImage는 그걸 감지하고, SImage라는 Slate위젯에 변화된 내용만 업데이트 해주는것이다.

 

UWidget의 SynchronizeProperties 라는 함수가 있는데 UImage에서는 이 SynchronizeProperties를 override해서 내가 에디터에서 편집한 내용, 즉 color 변경과 같은 내용을 반영해서 그려주고 있는 것이다.

 

 

 

void UImage::SynchronizeProperties()
{
	Super::SynchronizeProperties();

	TAttribute<FSlateColor> ColorAndOpacityBinding = PROPERTY_BINDING(FSlateColor, ColorAndOpacity);
	TAttribute<const FSlateBrush*> ImageBinding = OPTIONAL_BINDING_CONVERT(FSlateBrush, Brush, const FSlateBrush*, ConvertImage);

	if (MyImage.IsValid())
	{
		MyImage->SetImage(ImageBinding);
		MyImage->SetColorAndOpacity(ColorAndOpacityBinding);
		MyImage->SetOnMouseButtonDown(BIND_UOBJECT_DELEGATE(FPointerEventHandler, HandleMouseButtonDown));
	}
}

 

 

내부 함수는 이렇게 되어있다. 

 

에디터에서 세팅한 것이 아니라 코드 혹은 블루프린트를 통해 변화를 줄 때 사용할 수 있는  public 함수들도 존재한다.

 

 

 

 

 

 

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

 

함수 내부를 보면 SImage인 MyImage의 함수를 호출해서 실제 위젯의 변화를 넘기고 있다.

 

자세한 건 나중에 Shop만들기를 하면서 하나씩 써보자.

 

 

 

 

 

 

UCanvasPanel

 

Image에서는 자식을 가질 수 없었다. 캔버스 패널은 어떻게 자식을 가질 수 있을까?

 

 

 

CanvasPanel은 UWidget을 바로 상속받은 것이 아니라 UPanelWidget이라는 클래스를 상속받고 있다.

 

 

UPanelWidget이 UWidget을 상속받고 있는 구조이다. 이 UPanelWidget이 하는 역할은 자식 위젯을 추가하고 제거하는 등 위젯의 배열관리를 하고 있다. 

 

 

 

실제 헤더 파일로 들어가보면 자식들에 대한 관리를 할 수 있는 기능만 제공하고 있다. 아마 자식 위젯을 가질 수 있는 위젯들을 만들기 위해 인터페이스처럼 쓰고자 만들어진 클래스가 아닐까 싶다. 그렇기에 다른 SWidget을 참조하고 있지는 않다.

 

AddChild라는 함수를 보면,

 

 

UPanelSlot* UPanelWidget::AddChild(UWidget* Content)
{
	if ( Content == nullptr )
	{
		return nullptr;
	}

	if ( !bCanHaveMultipleChildren && GetChildrenCount() > 0 )
	{
		return nullptr;
	}

	Content->RemoveFromParent();

	EObjectFlags NewObjectFlags = RF_Transactional;
	if (HasAnyFlags(RF_Transient))
	{
		NewObjectFlags |= RF_Transient;
	}

	UPanelSlot* PanelSlot = NewObject<UPanelSlot>(this, GetSlotClass(), NAME_None, NewObjectFlags);
	PanelSlot->Content = Content;
	PanelSlot->Parent = this;

	Content->Slot = PanelSlot;

	Slots.Add(PanelSlot);

	OnSlotAdded(PanelSlot);

	InvalidateLayoutAndVolatility();

	return PanelSlot;
}

 

 

추가할 위젯을 넣어주면 PanelSlot이라는 것을 생성해서 Content에는 추가된 위젯을, 그리고 부모로는 자신을 넣어준다.  이 UPanelWidget을 상속받았기 때문에 UCanvasPanel은 자식 위젯을 가질 수 있게 되는 것이다. 또한, 

CanvasPanel은 UPanelSlot을 상속받은 UCanvasPanelSlot 을 갖고 있는데 여기에서 앵커와 사이즈 등을 설정할 수 있는 기능을 제공하고 있다. 

 

 

 

CanvasPanel안에 있는 Image의 가장 상단부에

 

 

 

Slot 이라고 해서 CanvasPanelSlot이라는 것이 보일것이다. 이게바로 캔버스 패널을 부모로 하고 있기에 보이는 슬롯의 속성들이고 이것이 변경됨에 따라 Image의 사이즈, 앵커등을 조정할 수 있게된다. 물론 부모를 기준으로 한다는 것을 잊지말자.

 

여기서 하나 더 알려주면, 코드에서 동적으로 위젯의 사이즈를 변경하고자 한다면 부모의 PanelSlot을 캐스트해서 변화를 주어야하는 번거로움이 있다. 캔버스 패널의 경우 UCanvasPanelSlot을 HorizontalBox의 경우에는 UHorizontalBoxSlot을 가져와서 내부의 기능을 바꿔줄 수 있다. 

 

현 위젯의 부모가 어떤 위젯이냐에 따라 설정할 수 있는 속성도 다르다. 그래서 모양을 예쁘게 잡고 싶을 때, 정렬을 하고 싶을 때 UXUI 디자이너가 고생을 할 때도 있다. 

 

다른 것을 명확하게 보여주기 위해서 Image하나를 CanvasPanel의 자식으로, Button 하나를 Horizontal Box 자식으로 넣어보겠다.

 

 

 

위의 경우 어떤 변화가 있는지 확인해보자.

 

 

 

 

CanvasPanel안에 Image를 넣은 경우

 

이렇게 넣으면 Image의 Slot에는 앵커, Position, Size, Alignment Zorder 등등의 설정이 가능한 CanvasPanelSlot의 속성을 변경할 수 있을 것이고,

 

 

HorizontalBox안에 Button을 넣은 경우

 

버튼의 Slot에는 HorizontalBoxSlot 의 설정을 변경할 수 있는 세팅이 나올 것이다. HorizontalBoxSlot은 패딩값, size 세팅 ,정렬방식 등의 속성을 갖고 있다.

 

어떤 부모의 자식으로 들어가느냐에 따라 사이즈 세팅이 가능하고 불가능 한 것도 정해진다는 사실을 파악할 수 있다는 것을 깨달았다면 이제 고민해보자. Horizontalbox에 넣어도 사이즈가 유지됐으면 좋겠다 라던가 그런 요구를 받을 수도 있다. 이런 경우 다른 Widget을 사용하게 할 것인가? 아니면 직접 상속을 받거나 Slate를 사용해서 또 다른 UI 제공을 고려해볼 것인가?

 

다음에는 이런 재미없는 얘기말고 진짜 UI 만들어서 코드로 세팅 해봐야겠다.

'UnrealEngine > UMG' 카테고리의 다른 글

[UE5] UMG 위젯 Visibility 종류와 차이점  (0) 2023.01.15
[UE5] Shop UMG - C++ 코드 연동하기  (0) 2023.01.08
[UE5] Shop UMG 프로토타입 제작하기  (0) 2022.12.27
[UE5] DPI 스케일링?  (0) 2022.12.24
[UE5] UMG 사용하기  (1) 2022.12.24