Unity uGUI 최적화 — Canvas Batching & Draw Call
uGUI(Unity UI)는 Canvas 단위로 배칭(Batching)해 Draw Call을 줄입니다. Canvas가 다시 그려지는 Rebuild 비용이 과도하면 프레임 드롭이 발생합니다. 원인을 파악하고 Canvas를 올바르게 분리하는 것이 핵심입니다.
1. Canvas Rebuild 원인
섹션 제목: “1. Canvas Rebuild 원인”Canvas는 다음 상황에서 전체 재계산(Rebuild)됩니다:
- 자식 UI 요소의 위치/크기/색상 변경- 활성화/비활성화 (SetActive)- 텍스트 내용 변경- Sprite 교체- 레이아웃 컴포넌트(LayoutGroup) 자식 변경2. Static / Dynamic Canvas 분리
섹션 제목: “2. Static / Dynamic Canvas 분리”Canvas (Static) Canvas (Dynamic)├── Background ├── HP Bar ← 매 프레임 갱신├── Frame ├── Score Text ← 자주 변경├── Button Layout └── Timer└── Map Icon ← 빈번한 갱신 격리// 자주 변경되는 요소를 별도 Canvas로 분리// Inspector에서 Dynamic Canvas 오브젝트 생성 후 자식으로 이동[SerializeField] Canvas dynamicCanvas;[SerializeField] Canvas staticCanvas;
// HP 변경 시 dynamicCanvas만 Rebuild (staticCanvas 영향 없음)public void UpdateHP(float ratio){ hpBar.fillAmount = ratio; // dynamicCanvas 내부만 dirty 처리됨}3. CanvasGroup으로 Show/Hide
섹션 제목: “3. CanvasGroup으로 Show/Hide”// SetActive(false) 대신 CanvasGroup 사용 → Rebuild 방지[SerializeField] CanvasGroup panelGroup;
public void Show(){ panelGroup.alpha = 1f; panelGroup.interactable = true; panelGroup.blocksRaycasts = true;}
public void Hide(){ panelGroup.alpha = 0f; panelGroup.interactable = false; panelGroup.blocksRaycasts = false; // Canvas 계층은 유지 → Rebuild 없음}4. 텍스트 갱신 최적화
섹션 제목: “4. 텍스트 갱신 최적화”// 나쁜 예: 매 프레임 문자열 생성void Update(){ scoreText.text = "Score: " + score.ToString(); // GC 할당}
// 좋은 예: 값이 바뀔 때만 갱신int _lastScore = -1;
void Update(){ if (score != _lastScore) { _lastScore = score; scoreText.SetText("Score: {0}", score); // TMP Zero-alloc }}5. 이미지 배칭 조건
섹션 제목: “5. 이미지 배칭 조건”같은 Canvas의 UI 요소가 배칭되려면:
- 같은 Atlas(Sprite Sheet) 사용
- 같은 Material 사용
- 같은 Rendering Layer
- Z-Depth 겹침 없음
// Sprite Atlas 패킹 설정// Project Settings → Editor → Sprite Packer Mode: Always Enabled// 또는 Sprite Atlas 에셋 생성 후 Sprites 필드에 추가6. Layout Group 최적화
섹션 제목: “6. Layout Group 최적화”// LayoutGroup은 자식 수만큼 재계산 비용// 동적 리스트는 LayoutGroup 대신 직접 위치 계산
public class ManualList : MonoBehaviour{ [SerializeField] float itemHeight = 60f; [SerializeField] float padding = 10f;
public void ArrangeItems(List<RectTransform> items) { float y = 0f; foreach (var item in items) { item.anchoredPosition = new Vector2(0, -y); y += itemHeight + padding; } // LayoutGroup.SetDirty() 호출 없음 }}7. UI Profiler로 진단
섹션 제목: “7. UI Profiler로 진단”Window → Analysis → UI Profiler또는 Profiler → UI 섹션 확인
주요 확인 항목:- Layout Rebuild Count (높으면 LayoutGroup 남용)- Graphic Rebuild Count (높으면 Canvas 분리 필요)- Batch Count (높으면 Atlas 미통합)8. Raycast Target 최적화
섹션 제목: “8. Raycast Target 최적화”// 클릭 불필요한 이미지는 Raycast Target 해제// Inspector → Raycast Target 체크 해제
// 코드로 일괄 해제void DisableRaycastsRecursive(Transform t){ if (t.TryGetComponent<Graphic>(out var g)) g.raycastTarget = false;
foreach (Transform child in t) DisableRaycastsRecursive(child);}9. 체크리스트
섹션 제목: “9. 체크리스트”| 항목 | 최적화 방법 |
|---|---|
| 자주 변경되는 요소 | Dynamic Canvas 분리 |
| 패널 숨기기 | CanvasGroup.alpha 사용 |
| 텍스트 갱신 | 값 변경 시에만, TMP SetText |
| 스프라이트 배칭 | Sprite Atlas 통합 |
| 레이캐스트 | 불필요한 Raycast Target 해제 |
| 레이아웃 | 동적 리스트는 직접 위치 계산 |
uGUI 최적화의 핵심은 Canvas Rebuild 빈도를 줄이는 것입니다. 자주 변경되는 요소를 별도 Dynamic Canvas로 격리하고, SetActive 대신 CanvasGroup.alpha로 가시성을 제어하며, 텍스트는 값이 실제로 바뀔 때만 갱신하세요. UI Profiler로 Rebuild Count를 기준 삼아 우선순위를 정하면 효율적으로 최적화할 수 있습니다.