그리는 순서는 하이어라키가 아니라 숫자가 정한다
Esc

    그리는 순서는 하이어라키가 아니라 숫자가 정한다

    –UnityUINGUI라이브러리

    uGUI 는 NGUI 에서 시작했다에서 갈리는 지점 셋을 짚었습니다. 그중 첫째를 봅니다.

    하이어라키 맨 아래가 화면 맨 앞이다에서 uGUI 의 규칙을 봤습니다. 형제 중에 아래에 있는 오브젝트가 앞에 그려지고, 순서를 바꾸려면 하이어라키에서 끌어 올리거나 내립니다.

    NGUI 에는 그 규칙이 없습니다. 오브젝트를 맨 아래로 내려도 아무 일도 안 일어납니다.

    숫자 한 칸이 순서입니다

    UIWidget 에 depth 라는 정수가 있습니다. 소스 주석이 한 줄입니다.

    “Depth controls the rendering order – lowest to highest.”

    작은 것이 뒤에 깔리고 큰 것이 앞에 그려집니다. 인스펙터의 Depth 줄에는 Back 과 Forward 버튼이 붙어 있고, 누르면 숫자가 1 씩 내려가고 올라갑니다. 여러 개를 같이 선택하고 눌러도 됩니다.

    숫자를 바꾸면 그 자리에서 정리가 돌아갑니다. 세터가 하는 일이 이렇습니다. 위젯을 판에서 뺐다가 새 숫자로 다시 넣고, 에디터에서는 곧바로 위젯을 다시 정렬하고 드로우 콜을 전부 다시 만듭니다. 숫자 하나가 그 판의 묶음 전체를 다시 잡습니다.

    재미있는 흔적이 하나 남아 있습니다. depth 게터 안에 주석 처리된 코드가 있는데, 앞에 이렇게 적혀 있습니다.

    “Experiment with a transform-based depth, uGUI style”

    하이어라키의 형제 번호를 깊이로 쓰는 방식을 만든 사람도 실험해 봤다는 뜻입니다. 남겨 두기만 하고 켜지 않았습니다.

    오브젝트를 옮겨도 순서가 안 바뀝니다

    여기서 갈립니다. 같은 화면을 만들어 놓고 오브젝트를 하이어라키에서 끌어 올리면, uGUI 는 화면이 바뀌고 NGUI 는 안 바뀝니다.

    얻는 것이 있습니다. 하이어라키를 정리 목적으로 쓸 수 있습니다. 관련 있는 것끼리 묶어 두고, 폴더 역할의 빈 오브젝트를 넣고, 순서를 알파벳으로 맞춰도 화면은 그대로입니다. 프리팹으로 떼어다 다른 부모 밑에 붙여도 앞뒤가 유지됩니다. uGUI 에서는 이 셋 다 화면을 건드립니다.

    잃는 것도 있습니다. 숫자를 관리해야 합니다. 화면이 커지면 어느 숫자가 비어 있는지 알기 어렵고, 팝업을 새로 얹을 때 “이건 몇으로 줘야 하지” 가 매번 나옵니다. 규칙을 안 정해 두면 100, 101, 105, 999 같은 숫자가 쌓입니다.

    이어받은 프로젝트라면 먼저 숫자대를 파악하는 게 순서입니다. 배경이 0번대, 내용이 10번대, 팝업이 100번대 하는 식으로 이미 약속이 있는 경우가 많습니다.

    세 체계가 순서를 각각 다른 곳에 둡니다

    지금까지 본 셋을 한자리에 놓으면 차이가 분명해집니다.

    순서를 어디에 적어 두는가 uGUI Background Card Popup 아래에 있는 것이 앞 순서를 바꾸려면 끌어 옮깁니다 NGUI Background depth 0 Card depth 10 Popup depth 100 숫자가 큰 것이 앞 하이어라키는 상관없습니다 UI Toolkit HUD 패널 Sort 0 팝업 패널 Sort 10 판 안쪽은 쌓인 순서 판끼리는 숫자 판 안쪽은 순서. 둘이 섞여 있습니다 판 단위로 숫자를 주는 방식은 셋 중 둘이 씁니다
    다른 것은 판 안쪽을 무엇으로 정하느냐입니다.
    체계 순서를 정하는 것 바꾸는 방법
    uGUI 하이어라키의 형제 순서 오브젝트를 끌어 올리고 내립니다
    NGUI UIWidget.depth 정수 숫자를 바꿉니다
    UI Toolkit 한 판 안에서는 쌓인 순서, 판끼리는 Sort Order 둘이 섞여 있습니다

    해상도 대응은 씬이 아니라 에셋 한 장에 적힌다에서 본 UI Toolkit 의 Sort Order 가 NGUI 의 UIPanel.depth 와 같은 자리입니다. 판 단위로 숫자를 주는 방식은 셋 중 둘이 쓰고 있습니다. 다른 것은 판 안쪽을 무엇으로 정하느냐입니다.

    판이 먼저이고 위젯이 그다음입니다

    UIPanel 에도 depth 가 있습니다. 정렬은 두 단계입니다. 소스의 UIWidget.FullCompareFunc 가 그대로 두 줄입니다. 먼저 두 위젯이 속한 판을 비교하고, 판이 같을 때만 위젯끼리 비교합니다.

    판이 먼저, 위젯이 그다음 판 A · UIPanel.depth = 0 배경 depth 900 아주 앞에 두고 싶은 것 depth 950 판 B · UIPanel.depth = 1 작은 아이콘 하나 depth 0 숫자는 제일 작은데 판 A · 900 판 A · 950 판 B · 0 뒤 앞 판 B 의 0 이 판 A 의 950 보다 앞입니다
    팝업이 뒤에 깔린다면 위젯 숫자가 아니라 판 숫자를 봐야 합니다.

    그래서 다른 판에 있는 위젯끼리는 위젯 숫자가 아무리 커도 판 순서를 못 이깁니다. 팝업이 뒤에 깔린다면 위젯 depth 를 올릴 게 아니라 판 depth 를 봐야 합니다.

    입력 쪽에도 같은 규칙이 그대로 들어가 있습니다. UIWidget.raycastDepth 가 이렇게 계산됩니다.

    return (panel != null) ? mDepth + panel.depth * 1000 : mDepth;

    판 하나가 위젯 1000칸 값어치입니다. 한 판 안의 위젯 숫자를 999 까지 쓰는 동안은 판 순서가 항상 이깁니다. 그리는 순서와 클릭 받는 순서를 같은 규칙으로 맞춰 둔 것입니다.

    같은 숫자를 주면 재질로 묶습니다

    위젯 둘의 depth 가 같으면 어떻게 될까요. 소스의 UIWidget.PanelCompareFunc 가 답입니다. 숫자를 먼저 비교하고, 같으면 재질의 인스턴스 ID 를 비교합니다.

    숫자가 같은 위젯들은 재질끼리 알아서 붙는다는 뜻입니다. 둘 사이의 앞뒤는 정해 주지 않은 셈이라 겹치면 안 되지만, 안 겹치는 것들이라면 문제가 없습니다.

    NGUI 인스펙터가 이 상황을 알려 줍니다. 같은 숫자를 쓰는 위젯이 둘 이상이면 이런 줄이 뜹니다.

    “{n} widgets are sharing the depth value of {depth}”

    경고가 아니라 안내입니다. 겹치는 것들이면 숫자를 갈라 줘야 하고, 안 겹치는 아이콘 여러 개라면 그대로 두는 편이 오히려 낫습니다. 다음 절이 그 이유입니다.

    그래서 숫자 배치가 곧 드로우 콜입니다

    UIPanel.FillAllDrawCalls 가 하는 일은 단순합니다.

    1. 위젯을 PanelCompareFunc 로 정렬합니다
    2. 앞에서부터 차례로 돌면서 정점을 한 버퍼에 쌓습니다
    3. 재질이나 텍스처나 셰이더가 바뀌는 자리에서 드로우 콜을 끊고 새로 시작합니다

    정렬된 순서가 곧 쌓는 순서이고, 재질이 바뀌는 지점이 곧 끊는 지점입니다. 그러니 같은 아틀라스를 쓰는 것들 사이에 글자가 하나 끼면 그 자리에서 갈라집니다.

    한 칸이 바뀌면 그 캔버스는 통째로 다시 계산된다에서 본 uGUI 의 배치 깨짐과 같은 이야기입니다. 다른 점은 NGUI 에서는 그 순서를 숫자로 직접 정한다 는 것입니다. uGUI 에서는 하이어라키를 옮겨야 해서 화면 구조와 성능이 얽히는데, NGUI 에서는 화면 구조를 안 건드리고 숫자만 바꿔서 묶을 수 있습니다.

    실무 규칙이 여기서 나옵니다.

    • 같은 아틀라스를 쓰는 것들의 숫자를 붙여 둡니다. 사이에 글자를 끼우지 않습니다
    • 글자는 글자끼리 한 구간에 몰아 둡니다
    • 안 겹치는 아이콘 여러 개라면 같은 숫자를 줘도 됩니다. 재질로 알아서 붙습니다

    마지막에는 렌더 큐 번호가 됩니다

    정렬과 묶기가 끝나면 그 결과가 유니티의 렌더 큐 번호로 내려갑니다. UIPanel 이 판을 순서대로 돌면서 3000 부터 시작해서 드로우 콜마다 번호를 하나씩 올립니다. 판 하나가 드로우 콜 셋을 만들었으면 다음 판은 3003 부터입니다.

    그래서 화면에 보이는 앞뒤는 결국 숫자 하나입니다. 하이어라키가 끼어들 자리가 처음부터 없었던 셈입니다.

    무엇이 몇 개로 묶였는지는 Draw Call Viewer 창에서 봅니다. 소스 설명이 이렇습니다. “Draw Call Viewer shows a list of draw calls created by NGUI and lets you hide them selectively.” 하나씩 꺼 보면서 어느 묶음에 무엇이 들어갔는지 확인할 수 있습니다. 숫자를 바꾸기 전에 여기부터 보는 게 빠릅니다.

    정리

    • UIWidget.depth 정수가 순서입니다. 소스 주석으로 “lowest to highest” 입니다
    • 오브젝트를 하이어라키에서 옮겨도 화면이 안 바뀝니다. 하이어라키를 정리 목적으로 쓸 수 있는 대신 숫자를 관리해야 합니다
    • 정렬은 두 단계입니다. 판 depth 가 먼저이고 위젯 depth 가 그다음 입니다
    • 입력도 같은 규칙입니다. raycastDepth = mDepth + panel.depth * 1000 이라 판 하나가 위젯 1000칸입니다
    • 숫자가 같으면 재질 인스턴스 ID 로 정렬합니다. 같은 재질끼리 알아서 붙습니다
    • 드로우 콜은 정렬된 순서로 돌다가 재질이 바뀌는 자리에서 끊깁니다. 같은 아틀라스끼리 숫자를 붙여 두는 것이 그대로 최적화입니다
    • 결과는 3000 부터 매겨지는 렌더 큐 번호입니다. Draw Call Viewer 로 묶음을 확인합니다