CODEONWORT

언리얼에서 UMG 작업하며 느낀 것들 본문

Season 2

언리얼에서 UMG 작업하며 느낀 것들

codeonwort 2018.12.29 00:20

이 글에서는 서드 파티는 논외로 하고 UI와 UMG를 동의어로 쓴다.

입력 처리하기 골치아프다

UI에서 입력을 받는 방법은 적어도 세 가지가 있다.


    • UMG 블루프린트에서 key down, key up 이벤트를 직접 처리한다.
    • 입력 바인딩(action/axis)을 활용한다. 이 때 input mode의 존재 때문에 두 가지 방법이 있다.
      • player controller에서 입력을 바인딩하고 응답은 UI에 위임한다.
      • UMG에서 owning controller의 input component에 직접 입력을 바인딩하고 응답한다.

    일단 key down/up은 안 쓰는 게 나을 것 같은게, 로직 변경이 힘들다. 메뉴 여는 걸 P에 바인딩했는데 O로 바꾸려면? 이동이 기본은 WASD인데 조작 키를 바꿀 수 있는 옵션을 추가한다면? 그리고 요즘은 크로스플랫폼이 대세인데 키보드 키와 게임패드 키를 같은 액션에 대응시키려면? IsKeyDown_OpenMenu 같은 매크로 함수를 만들고 여기에 OR로 여러 키를 묶는 방법도 있지만, 그럴 거면 이미 언리얼이 지원하는 입력 바인딩을 쓰지 쓸데 없는 노가다다.


    입력 바인딩을 쓴다고 모든 문제가 해결되지는 않는다. input mode, 포커스 설정에 따라 입력 바인딩에 연결한 키가 어떤 건 되고 어떤 건 안 되는 경우가 있다. 그리고 단순히 같은 액션에 여러 키를 연결하는 걸 넘어, 플랫폼마다 UI 레이아웃이나 조작법이 완전히 바뀌는 경우가 있다. 가령 RPG에서 포션, 장비 등을 등록하는 퀵 슬롯이 PC에서는 가로 10줄인데 콘솔에서는 십자 모양으로 4개만 있는 경우가 있다.

    기본 위젯으로는 부족하다

    버튼, 텍스트, 리스트, 스크롤 등이 UI를 이루는 기본 위젯이지만, 요즘 게임에 쓰는 위젯의 기본 단위는 형태나 동작이 더 복잡하다.


      • 버튼에는 [Icon: Label] 처럼 아이콘과 레이블이 있어야 한다. 아이콘은 입력 장치에 따라 바로 바뀌어야 한다. PC에서도 키보드 키로 나오다가 게임 패드의 아무 키나 누르면 바로 해당 게임 패드의 아이콘을 표시해야 한다.
      • 아이템 삭제 같은 중요한 동작인데 버튼을 실수로 누르는 걸 방지하기 위해, 누르고 있으면 게이지가 차는 버튼도 있다. 그런데 디자인에 따라 게임 패드 쓸 때만 게이지 버튼이고 키보드/마우스 쓸 때는 그냥 누르게 만들어야 할 수도 있다.
      • 스크롤 가능한 리스트나 격자. 단순하게 scroll box 안에다 horizontal box나 grid를 넣으면 만들 수 있지만, 하이라이트된 슬롯만 살짝 확대해서 강조한다면? 애니메이션을 넣는다면? 화면에 보이는 슬롯은 10개가 안 되는데 전체 슬롯은 1000개면 그냥 무식하게 1000개 만들고 클리핑해야 하나? 12개만 만들어서 돌려쓰는 방식으로 최적화를 해야 하나?
      • 스크롤할 때 모션 블러를 넣고 싶은데 위젯에 특수효과 옵션이 있나? (없다. retainer box를 잘 쓰면 만들 수는 있다.) 특수 효과는 백그라운드 블러 하나 뿐이다.

    UMG와 3D

    UMG는 슬레이트(Slate)를 감싸는 프레임워크고, 슬레이트에서는 거의 모든 지오메트리를 사각형처럼 다룬다. 바운딩 박스가 사각형이건 뭐건 투명도, 머티리얼 등을 잘 활용하면 많은 것을 표현할 수 있지만 슬레이트 렌더러에는 3D 월드를 그릴 때의 FXAA나 TAA 같은 안티 앨리어싱 패스가 없기 때문에 뭔가 찌그러뜨리면 (하다못해 skew라도 하면) 바로 계단 현상이 보인다.


    UMG에는 3D를 표현하는 기능이 없어서 커스텀 렌더 패스를 코딩할 줄 아는 그래픽스 프로그래머가 아니면 평면적인 레이아웃을 벗어나는 걸 만들기 어렵다. SMeshWidget이라는 게 있긴 한데 렌더링 지식 없이 쓰기 어렵고 역시 안티 앨리어싱 문제가 있다. 그리고 머티리얼 안에서 버텍스 좌표 맞추느라 똥꼬쇼를 해야 한다.


    UMG를 월드에 그리는 3D 위젯도 있는데 이건 정말 월드의 일부가 되는 것이기 때문에, 일반적인 UI처럼 월드 위에 덮어서 그리는 느낌을 내기 어려울 수 있고, 위젯에 월드의 라이팅이 반영된다. (라이팅 반영은 여러 옵션을 잘 조절해보면 없앨 수 있을 지도.)


    보통 씬 캡쳐를 써서 월드에 있는 물체를 UMG에 그리는데 씬 캡쳐는 비용이 비싸다. 또한 월드의 라이팅 환경이 캡쳐 결과에 반영되기 때문에 원하는 색을 내기 힘들 수도 있다. 배경과 캐릭터가 있는데 캐릭터만 캡쳐할 때는 씬 캡쳐에 특정 액터만 찍는 기능이 있긴 한데, 또 라이팅이 걸림돌이 된다. 캐릭터가 땅을 딛고 서있으면 캐릭터만 캡쳐해도 땅에 의한 라이팅 변화가 그대로 반영된다.

    비동기 로딩

    리스트나 그리드에 고해상도 텍스처 수백 장을 뿌리고 그 중 하나를 고르는 UI를 이렇게 만든다고 치자.


      • 옵션 키를 누르면 위젯을 생성한다.
      • 텍스처 수백 장을 synchronous load한다.
      • 위젯에 추가한다.
      • 위젯을 화면에 표시한다.

    이러면 위젯을 열 때 렉이 심하게 걸린다. 텍스처를 모두 AddToRoot()로 루트에 박아서 메모리에 유지하면 다음에 열 때는 렉이 안 걸리겠지만 리소스를 심하게 낭비한다. UI 말고도 월드에는 리소스를 먹는 물괴들이 넘쳐난다.


    별 생각 없이 짜면 데이터 애셋에 로우 텍스처 포인터의 배열을 저장하고, UI에서 이 애셋의 모든 텍스처를 synchronous load하는 식으로 짜게 된다. 비동기 로딩하려면 텍스처 포인터를 TSoftObjetPtr로 바꾸고 FStreamableManager로 로드해야 한다. 그리고 적절하게 리소스를 해제해야 한다.

    디자이너가 다 해줄 거야

    '언리얼 엔진에서는 디자이너와 아티스트가 간단한 블루프린트를 통해 프로그래머가 고생하던 걸 쉽게 해결할 수 있다'는 선전을 언젠가 본 것 같은데... 그런 일은 절대 일어나지 않았다.


    이상: 디자이너 혹은 아티스트가 UMG 디자이너 탭에서 레이아웃을 만든다. 프로그래머는 블루프린트 로직만 작성한다.

    현실: 전부 프로그래머가 한다.

    지옥에서 온 블루프린트

    다른 프로그래머들이 만든 블루프린트를 보면 대부분 노드 연결선이 대륙을 횡단하고, 로컬 변수는 하나도 없고, 함수조차 없어서 이벤트 그래프 하나에 모든 걸 때려박는다.


    코드는 어떻게든 클래스, 함수, enum 등의 심벌을 따라서 제어 흐름을 추적하는 게 되는데 그게 블루프린트에 박혀있으면 답이 없다. 블루프린트에도 검색이 있지만 코드보다 훨씬 불편하다. 비주얼 어시스트와 함께면 엔진 코드 분석도 문제가 없는데 블루프린트로 게임 로직을 찾을려고 하면 미춰버리겠다.

    월드를 제어하는 UI가 있다?

    UI 만들면서 일어나는 문제점은 다양하고 각자 중요한 이유가 있지만 내 생각에는 이게 제일 심각하다. 월드 이동, 플레이어 폰의 상태 변경, 게임 플레이 로직 등을 UI에서 제어하는 것이다. 특히 블루프린트 UI에서.


    UI를 C++로 작성해도 월드 제어 로직이 UI에 있는 건 문제라고 보는데, 내가 디자인 패턴을 더 이상 신뢰하지는 않지만 MVC는 나름 황금률로서 가치가 있다고 생각한다. UI는 뷰 역할만 해야 한다. UI에서 월드에 변화를 주고 싶으면 UI 내부에서 바로 변화를 일으키는 게 아니라, UI에서는 델리게이트를 호출하고, 월드에 있는 무언가(매니저 류의 개체)가 그 델리게이트를 수신해서 월드를 제어해야 한다.


    모든 언어의 모든 UI 라이브러리가 위젯을 화면에 배치하면 사용자가 지지고 볶아서 이벤트를 발생시키고, 응용 프로그래머는 그 이벤트를 수신해서 반응하게 만들어져 있는데 왜 UMG 블루프린트에 컨트롤 로직을 박는 걸까? 도저히 이해할 수가 없다.


    0 Comments
    댓글쓰기 폼