콘텐츠로 이동

Unity uGUI 최적화 — Canvas Batching & Draw Call

uGUI(Unity UI)는 Canvas 단위로 배칭(Batching)해 Draw Call을 줄입니다. Canvas가 다시 그려지는 Rebuild 비용이 과도하면 프레임 드롭이 발생합니다. 원인을 파악하고 Canvas를 올바르게 분리하는 것이 핵심입니다.


Canvas는 다음 상황에서 전체 재계산(Rebuild)됩니다:

- 자식 UI 요소의 위치/크기/색상 변경
- 활성화/비활성화 (SetActive)
- 텍스트 내용 변경
- Sprite 교체
- 레이아웃 컴포넌트(LayoutGroup) 자식 변경

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 처리됨
}

// 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 없음
}

// 나쁜 예: 매 프레임 문자열 생성
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
}
}

같은 Canvas의 UI 요소가 배칭되려면:

  1. 같은 Atlas(Sprite Sheet) 사용
  2. 같은 Material 사용
  3. 같은 Rendering Layer
  4. Z-Depth 겹침 없음
// Sprite Atlas 패킹 설정
// Project Settings → Editor → Sprite Packer Mode: Always Enabled
// 또는 Sprite Atlas 에셋 생성 후 Sprites 필드에 추가

// 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() 호출 없음
}
}

Window → Analysis → UI Profiler
또는 Profiler → UI 섹션 확인
주요 확인 항목:
- Layout Rebuild Count (높으면 LayoutGroup 남용)
- Graphic Rebuild Count (높으면 Canvas 분리 필요)
- Batch Count (높으면 Atlas 미통합)

// 클릭 불필요한 이미지는 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);
}

항목최적화 방법
자주 변경되는 요소Dynamic Canvas 분리
패널 숨기기CanvasGroup.alpha 사용
텍스트 갱신값 변경 시에만, TMP SetText
스프라이트 배칭Sprite Atlas 통합
레이캐스트불필요한 Raycast Target 해제
레이아웃동적 리스트는 직접 위치 계산

uGUI 최적화의 핵심은 Canvas Rebuild 빈도를 줄이는 것입니다. 자주 변경되는 요소를 별도 Dynamic Canvas로 격리하고, SetActive 대신 CanvasGroup.alpha로 가시성을 제어하며, 텍스트는 값이 실제로 바뀔 때만 갱신하세요. UI Profiler로 Rebuild Count를 기준 삼아 우선순위를 정하면 효율적으로 최적화할 수 있습니다.