KoreanFoodie's Study

[언리얼] Slate(슬레이트)의 구조와 철학 (공식 문서 해설 및 정리) 본문

Game Dev/Unreal C++ : Dev Log

[언리얼] Slate(슬레이트)의 구조와 철학 (공식 문서 해설 및 정리)

GoldGiver 2022. 12. 7. 16:29

슬레이트 구조 (아키텍처)

사실 언리얼에는 이미 UMG 라는, 매우 편리한 위젯 관련 툴이 존재한다. 하지만 코드로 UI 를 컨트롤할 수 있다는 장점 때문에, 실제로 많은 곳에서 슬레이트(Slate) 를 활용하고 있다.

슬레이트는 어떤 녀석이고, 어떻게 디자인되어 있는지, 공식 문서(번역본)를 통해 먼저 간단하게 짚고 넘어가도록 하자!

이번 글에서는 슬레이트가 추구하는 방향과 철학에 대해 다룬다.

 

일단, 에디터 인터페이스의 대부분은 슬레이트로 제작되어 있다. Contents Browser 뿐만 아니라 블루프린트 에디터, 애니메이션 에디터 등 엔진 인터페이스의 대부분을 차지하고 있다.

슬레이트는 다음과 같은 접근법을 사용한다 : (불투명 캐시와 중복 스테이트를 피하기 위해. 전통적으로 UI 는 스테이트를 캐시에 담고, 실효(invalidation)는 명시적으로 할 것을 요구한다고 함)

일단 용어가 어려울 것 같아서 주석을 덧붙이면, 실효(invalidation)은 기존의 UI 를 파기하고(즉, 기존 State를 정리하고) UI 를 재생성한다고 이해하면 되며, 폴링(polling)은 현재 State 를 변경하지 않고, 수정된 부분만 갱신한다고 받아들이면 될 것 같다.
  1. 폴링(polling)
  2. 투명 캐시
  3. 불투명 캐시에 드물게(low-grain) 실효(invalidation)

번역이 어색한데, 사실 위의 해석은 공식 문서를 따라한 것이라서 그렇다😅

공식 문서 내용을 조금 더 풀어보자면...

UI 구조가 변할 때, 알림(notification)보다 폴링(polling)을 선호한다 : 즉, 바뀔 때마다 계속 알림을 보내서 상태 변화를 알리는 것이 아니라, 일정 주기로 UI 구조 변화를 캐치한다고 받아들이면 될 거 같다. 매 Tick 마다 검사하는 식으로 폴링을 하면 되는 것일까?

피드백 루프를 회피한다. 즉 모든 레이아웃은 프로그래머 세팅에서 계산하며, 절대 이전 레이아웃 스테이트에 의존하지 않는다. 예외로는 UI 스테이트가 모델이 되는 경우(예를 들어 스크롤바가 UI 스테이트를 시각화시키는 경우)이다. 이는 성능 때문이라기보다는 프로그래머를 위해서이다.

사실 잘 와닿지 않는데, 조금 더 읽어보도록 하자.

 

 

델리게이트와 데이터 흐름 폴링

아마 많은 사람들이 MVC 패턴을 기억할 것이다. 여기에 비록 컨트롤러는 없지만, UI 와 데이터 모델은 분리되어 있다.

즉 STextBlock 이라는, 어떤 텍스트를 표시하는 슬레이트가 있다고 했을 때, Text 를 Model Data 의 framerate를 읽어 표시해준다고 가정하자. 이때 데이터를 UI 에 직접 표시해줄 수도 있겠지만, 그것보다는 델리게이트를 하나 만들어 주어서, STextBlock 이 해당 Text 를 불러올 필요가 있을 때 바인딩된 델리게이트가 호출되도록 만들 수 있을 것이다.

이는 슬레이트를 통해 텍스트를 변경할 때도 마찬가지로, 델리게이트를 통해 Model Data 를 수정할 수도 있다. 아래 그림과 같이.

사용자가 SEditableText 라는 슬레이트를 통해 특정 item name 이라는 데이터를 수정하고 싶다면, OnTextChanged 라는 함수에 연결된 델리게이트가 실행되어 Model Data 의 item name 데이터가 수정되게 될 것이다(다음 틱에)!

MVC 패턴을 떠올려 보면, 위의 구조는 슬레이트인 SEditableText 가 View 와 Controller 의 역할을 동시에 수행한다고 이해해도 편할 것이다.

 

 

성능 이슈에서 고려할 것들

위처럼 델리게이트를 활용하는 것은 성능 이슈를 야기할 수도 있다. 하지만 다음의 사항들을 고려해보면, 성능적으로 크게 걱정을 하지 않아도 된다는 것을 알 수 있다(뭐.. 에픽게임즈가 그렇다고 하니 일단 그렇다고 생각한다).

  • UI 복잡도는 현재 활성화된 위젯의 수에 좌우된다.
  • 콘텐츠 스크롤링 작업은 가능하면 가상화시키자. 이를 통해 활성 위젯이 화면 밖으로 나가는 것을 막을 수 있다. 화면 밖에 있는 위젯 수가 많으면 Slate 성능을 악화시키기 때문이다.
    • 가상화시킨다는 게 무슨 뜻일까? 내가 이해하기로는, 스크롤을 할 때 실제로 화면에 보여주는 녀석을 제외하면 굳이 업데이트를 제대로 해 줄 필요가 없다는 뜻인 것 같다. 아래로 스크롤을 할 때, 현재 화면에 보이는 슬롯 + 5개 정도만 실제로 UI 업데이트를 해주는 식으로...
  • 큰 모니터를 쓰는 사람은 컴퓨터 성능도 더 좋을 것이라고 가정한다.
    • ㅋㅋㅋㅋㅋ이런 게 공식 문서에 있을 줄이야

 

추가로, ESlateVisibility 라는 옵션이 있어, 해당 값을 설정하여 Slate 를 보이게 하거나 안 보이게 만들 수 있다. 각각의 특징을 보면...

UENUM(BlueprintType)
enum class ESlateVisibility : uint8
{
    Visible,    
    Collapsed,  
    Hidden,     
    HitTestInvisible 
    SelfHitTestInvisible
};
  터치 가능 여부 화면 보임 여부 레이아웃 공간 차지 여부
Visible O O O
Collapsed X X X
Hidden X X O
HitTestInvisible X (자손까지 막음) O O
SelfHitTestInvisible X (자신만 막음) O O

HitTestInvisible, SelfHitTestInvisible 옵션의 경우 위젯 리플렉터의 HitTest 기능을 사용하는 것과 관련이 있다. 초간단 가이드는 해당 포스트 참고.

Visible 의 경우, UI 상에서 클릭 내지는 터치 이벤트 발생 시, 게임 스레드의 오버헤드가 증가한다고 한다. 따라서 버튼 같이 인터랙션이 필요한 경우가 아니라면 HitTestInvisible 이나 SelfHitTestInvisible 로 Visibility 를 설정하는 것이 권장된다.

또한 Collapsed 는 레이아웃 공간을 차지하지 않으므로 Pass 계산을 무시하기 때문에 Hidden 보다 성능이 더 낫다고 한다(뜬금없이 Pass 얘기가 나온 것 같은데, Slate 레이아웃은 2 패스로 이루어지며, 레이아웃에 맞게 배치를 어떻게 할지를 계산하는 과정이라고 이해하면 될 것 같다. 공식 문서에 관련 내용이 있음.).

 

나머지 공식 문서에 보면 자손 슬롯, 위젯 규칙, 레이아웃 등이 있는데... 사실 해당 내용들의 경우, 그래도 옮겨오는 것 보다는 직접 읽어보는 게 더 나을 것 같다.

 

참고 링크 : Slate란 무엇인가(해당 카테고리의 다른 글들도 읽으면 큰 도움이 됨), Slate에 SetVisibility 하기

Comments