2017년 8월 16일

UMG로 데이터 주도형 UI 만들기

저자: Cody Albert

게임 UI를 구현할 때, 우리는 보통 보수성에 대해 많이 생각하지 않습니다. 아티스트에게서 실물 모형을 받아, 개별 엘리먼트로 분류한 다음, 견본 아트를 가지고 위젯을 화면 상에 배치하고 준비가 되었을 때 최종 애셋에 드롭합니다. 이런 방식은 HUD나 메뉴처럼 반복 작업이 많이 필요하지 않은 엘리먼트에 아주 잘 작동하지만, 좀 더 복잡한 시스템을 노출해야할 때는 어떻게 해야 할까요? 새 아이템이 추가되거나 새 메뉴 옵션이 필요할 때마다 UI에 끊임없는 수정을 해 주기보다는, 데이터 주도형 UI를 구성하여 우리 자신을 파이프라인에서 제거하고, 내재된 데이터를 직접 인터페이스에 자동으로 결합해줄 수 있습니다.
UnrealEngine%2Fblog%2Fcreating-a-data-driven-ui-with-umg%2FDynamicUI_Fortnite_770-770x480-38b0abba26d654c36e5a4c02ebcb9314533cd6d8

데이터 주도형 UI

데이터 주도형 UI 엘리먼트는 직접 수동으로 빌드되지 않고 내재된 데이터 소스를 기반으로 절차적으로 구축되는 것입니다. 이 패턴의 장점은 디자이너가 UI 자체에는 아무 조정도 가하지 않고 UI에 의해 노출되어 있는 시스템에 변화를 만들 수 있다는 것입니다. 가장 큰 단점은 엘리먼트가 런타임에만 존재하기 때문에, 게임 내에서 어떻게 보일지 정교하게 통제하는 것과 프리뷰가 어렵다는 것입니다.

예를 들어 게임의 상점을 상상해 봅시다. 우리의 인터페이스는 구매할 수 있는 모든 아이템의 목록을 그 가격과 아이콘과 함께 보여주어야 합니다. 이 모든 정보를 가진 윈도우를 만드는 것이 그렇게 어렵지는 않겠지만, 만약 디자이너가 상점에서 아이템을 추가하거나 제거하고 싶어한다면 어떨까요? 만약 가격에 조정이 필요하거나 아이콘 아트가 업데이트 되어야 한다면 어떨까요? 이 모든 것은 인터페이스에 수정이 필요하며, 수정하는 것을 잊어버린다면 인터페이스가 데이터와 싱크가 맞지 않게 됩니다. 500골드라고 되어 있던 아이템을 샀더니 인벤토리에서 1000골드가 나가는 상황은 아무도 원하지 않습니다!
이 포스트에서 저는 데이터 테이블을 구성하고, 이것을 스크롤링 리스트에서 임의 개수의 아이템을 보여주는 상점 위젯에 결합하는 방법에 대해 설명할 것입니다. 또한 선택이 내려졌을 때 이벤트를 선언하는 방법도 보여드리고, 이런 아이디어를 여러분 프로젝트의 필요에 맞추어 부연하는 방법에 대해서도 다루어 보겠습니다.

시작하기

어떤 데이터 주도형 UI든 가장 중요한 부분은 데이터 그 자체이므로, 우리 상점의 인벤토리를 포함한 데이터 테이블을 구성해 봅시다. 제일 먼저 우리 테이블의 각 행이 포함할 열을 표시하는 구조체를 만들어야 합니다. '신규 추가'를 누르고, '블루프린트' 카테고리로 가서, '구조체'를 누릅니다.

저는 아래 이미지처럼 구조체를 구성했습니다:
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven1-750x105-63d44c3d94c993eedee52f540a7df3b41e1e0eda
이름 영역을 만들지 않았다는 점을 주목하세요. 이 부분은 데이터 테이블이 나중에 자동으로 추가할 것입니다. 테이블 내 특정 항목을 찾아볼 필요가 있을 경우 이름을 사용할 것입니다.

다음은 다시 한번 '신규 추가'를 누르고 '기타' 카테고리로 가서 데이터 테이블 자체를 만들겠습니다. 방금 만든 구조체를 선택한 다음, 항목 추가를 시작합니다!
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven2-379x232-261fcfc757bda6129374dbe2eb29611e859744ae
이제 우리의 데이터를 구했으니, 두 개의 위젯을 만들어야 합니다. 먼저 상점을 이용할 수 있을 때마다 만들어야 할 상점 메인 윈도우가 필요할 것입니다. 그 다음, 런타임에 데이터 테이블의 한 행을 표시하기 위해 생성될 ItemRow 위젯을 만들 것입니다.

ItemRow부터 시작하겠습니다. 목표는 자동으로 복제되어 채워질 수 있는 일반 위젯을 서로 다른 텍스트 길이 등 우리가 허용한 대로 조정할 수 있는 레이아웃으로 만드는 것입니다.

저는 최대 텍스트 크기 및 아이콘 크기를 정해 두었지만, 제 위젯은 다음과 같습니다:
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven3-750x267-e111de989c968bab6c7ef4151e1adf264cbce800
이 위젯은 부모에 의해 위치 및 크기가 정해질 것이라, 시작할 때 주어지는 Canvas Panel을 계층구조의 최상단에서 삭제했다는 점을 주목하시기 바랍니다.

또한 템플릿으로의 데이터 피드를 간소화하기 위해 SetValues 함수를 만들었습니다. 빠르고 간단한 솔루션으로 프로퍼티 바인딩을 사용할 수도 있었지만, 바인딩은 매 틱마다 값을 업데이트할 것입니다. 퍼포먼스가 문제될 수 있는 상황을 염두에 두고 바인딩은 피하면서, 대신 필요할 때만 프로퍼티를 업데이트하는 이벤트를 구성하는 것이 최선입니다.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven4-750x162-93d460524beece671d753d1f0392f0e5920ac5fd
다음은 항목을 담을 메인 위젯을 만들 것입니다. 이 위젯은 데이터 테이블을 읽어들이고 각 행마다 ItemRow 위젯을 만드는 블루프린트를 포함할 것입니다. 시각화를 하기 위해 임시로 ItemRow 위젯을 드롭할 수 있지만, 블루프린트에 의해 행이 추가될 것이므로 최종 생성물에서는 빈 스크롤박스가 필요합니다.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven5-750x410-78fd40a32f7d2322cfceead631967be6ad5278c6 UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven6-750x138-f7777971604338d1427b71ffcebc27bd02e97dab
이제 뷰포트에 주요 위젯을 추가하기만 하면, 데이터 테이블에서 얻은 모든 최신 정보로 채워질 것입니다! 만약 항목 중 무엇이든 추가, 제거 혹은 변경을 하고 싶다면, 데이터 테이블을 업데이트 하기만 하면 됩니다.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven7-750x693-9f5d94a0bd2994a87205c191c88a18d05168a71a

개선

여러분이 처음으로 느낄 골칫거리는 아마 상점 인터페이스를 보려면 실제로 에디터에서 플레이(Play In Editor)를 시작해야 한다는 점으로, 곧 조정 작업이 시행착오로 이어질 수도 있다는 뜻입니다. 다행히 4.15 이상의 버전이라면, 위젯 블루프린트에 '이벤트 Pre Construct' 노드가 추가되어 있습니다. 이것은 기본적으로 'Construct 이벤트' 노드와 동일하지만 편집 시에도 실행되기 때문에, 플레이를 누르지 않고도 뷰포트에서 위젯이 어떻게 보일지 확인할 수 있습니다. 그냥 행을 빌드하기 위해 사용하던 블루프린트를 Construct 이벤트 노드에서 연결을 끊고 이벤트 Pre Construct 노드에 연결하면 됩니다.

데이터 테이블을 수정한다면, 컴파일 버튼에 평상시의 체크 마크가 여전히 표시되어 있더라도 컴파일을 눌러 프리뷰 위젯을 다시 빌드해야 한다는 점을 주의하시기 바랍니다.

이벤트 Pre Construct에 연결된 노드는 예전처럼 계속 런타임에 호출될 것이지만, 이제는 블루프린트 에디터 내 프리뷰 위젯에서도 호출된다는 점을 명심하는 것도 중요합니다.계속 장식성 코드만 고수하고, 런타임까지는 존재하지 않는 것은 무엇이든 레퍼런싱을 피해야 합니다.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven8-448x385-d167cd1eb3372b4815bf7907e3bed63cb6e2d690
고려해야 할 점 또 한가지는 현재 임의의 아이템 리스트를 표시하고 있기는 하지만, 골드를 지불하고 플레이어의 인벤토리에 그 아이템을 추가할 수 있도록 버튼을 클릭했을 때 (캐릭터 컨트롤러같은) 다른 게임 시스템에 알릴 방법이 필요합니다. 저는 최대한 모듈식으로 유지하기 위해, 특정 버튼을 눌렀을 경우 각 항목과 이벤트를 받길 원하는 쪽 사이에서 중재 역할을 할 이벤트 디스패처를 최고 레벨 위젯에 만드는 방식을 좋아합니다. ItemRow는 자신의 OnMouseButtonDown를 오버라이드해서 호출할 이벤트를 발동시킬 수 있도록 루트 위젯으로 돌아가는 처리가 필요할 것입니다.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven9-1117x289-21ea993e6cd9611cbfdbc9c7c8fce7c071dfb511
ItemRow 위젯에서

관련된 어느 파티든 Custom Event를 루트 위젯의 이벤트 디스패처에 바인드하고, 클릭된 아이템의 이름을 파라미터로 받고, 자세한 전체 정보를 데이터 테이블에서 알아서 조회할 수 있습니다.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven10-750x300-1d1f174e2d03a7a0d07e96abfd67540c9b43ff9b
메인, 최고 레벨 위젯에서
 
제 예시에서 사용된 데이터 테이블은 편집 시에만 수정될 수 있다는 점을 이해하시는 것도 중요합니다. 게임 진행 중에도 수정이 가능한, 보다 동적인 데이터 구조체가 필요한 경우도 있을 수 있습니다. 데이터 테이블을 데이터의 시작 상태로 사용하고, 이것을 또 다른 구조체에 복사한 다음 필요한 대로 수정을 하고 싶을 수도 있습니다. 최대한의 융통성을 위해 임의의 프로퍼티와 이 프로퍼티를 어떻게 표시할지 UI에 알려줄 메타데이터로 데이터 구조체를 만들 수도 있습니다.어느 시점에서 예상치 못한 한계에 부딪히는 것보다는, 시간을 할애해서 어떤 솔루션이 자신에게 가장 잘 맞는지 알아보는 것이 충분한 가치가 있습니다.

최종 정리

일부 UI 엘리먼트는 데이터 주도형 위젯을 구성하는 데 들인 추가적인 시간과 노력에 미치지 못하는 변화를 보여주기도 하지만, 많은 시스템이 스스로가 보여줄 수 있는 유연성으로부터 이점을 얻을 수 있습니다. 빠른 반복 작업은 좋은 게임 디자인의 핵심이며, 인터페이스를 절차적으로 생성하는 것은 아이디어를 즉각 실행할 수 있게 해 줍니다.