그리는 순서는 하이어라키가 아니라 숫자가 정한다
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 | 하이어라키의 형제 순서 | 오브젝트를 끌어 올리고 내립니다 |
| NGUI | UIWidget.depth 정수 |
숫자를 바꿉니다 |
| UI Toolkit | 한 판 안에서는 쌓인 순서, 판끼리는 Sort Order |
둘이 섞여 있습니다 |
해상도 대응은 씬이 아니라 에셋 한 장에 적힌다에서 본 UI Toolkit 의 Sort Order 가 NGUI 의 UIPanel.depth 와 같은 자리입니다. 판 단위로 숫자를 주는 방식은 셋 중 둘이 쓰고 있습니다. 다른 것은 판 안쪽을 무엇으로 정하느냐입니다.
판이 먼저이고 위젯이 그다음입니다
UIPanel 에도 depth 가 있습니다. 정렬은 두 단계입니다. 소스의 UIWidget.FullCompareFunc 가 그대로 두 줄입니다. 먼저 두 위젯이 속한 판을 비교하고, 판이 같을 때만 위젯끼리 비교합니다.
그래서 다른 판에 있는 위젯끼리는 위젯 숫자가 아무리 커도 판 순서를 못 이깁니다. 팝업이 뒤에 깔린다면 위젯 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 가 하는 일은 단순합니다.
- 위젯을
PanelCompareFunc로 정렬합니다 - 앞에서부터 차례로 돌면서 정점을 한 버퍼에 쌓습니다
- 재질이나 텍스처나 셰이더가 바뀌는 자리에서 드로우 콜을 끊고 새로 시작합니다
정렬된 순서가 곧 쌓는 순서이고, 재질이 바뀌는 지점이 곧 끊는 지점입니다. 그러니 같은 아틀라스를 쓰는 것들 사이에 글자가 하나 끼면 그 자리에서 갈라집니다.
한 칸이 바뀌면 그 캔버스는 통째로 다시 계산된다에서 본 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 로 묶음을 확인합니다