
오늘 학습 키워드
챌린저 반 강의
오늘 학습 한 내용을 나만의 언어로 정리하기
챌린저 반 강의 (주제 : UI 관리)
UI 팁
TextMeshProUGUI vs TMP_Text
- TextMeshProUGUI : UI 전용의 Text
- TMP_Text : UI, 3D 공통의 Text
RectTransform 크기 조정
- 무조건 가로 세로를 조절하셈. 스케일을 조정하지 마시오.
- 계산이 어려우면 Aspect Ratio Filter를 쓰되 다 썼으면 지우셈 (성능 이슈)
플랫 이미지는 하얀색으로 준비
- 원본 색상에 설정한 색을 더해주는게 UI 컬러기 때문에, 기본 색이 흰색인 이미지를 써라.
보이지 않는 범위에 주의
- 범위가 실제 눈에 보이는 것과 달라서 Raycasting이 이상하게 될 수 있으니 주의.
- 텍스트 Raycast 안하게 할 수도 있음.
자연스러운 페이드 효과
- 그래디언트 이미지를 UI 위에 올리면 자연스럽게 페이드가 있는 것처럼 보임
- 한 축으로는 엄청 짧아도 됨. 그냥 늘리면 되니까
딤 처리
- UI 주변 어둡게 만드는거
- 그냥 이미지 하나 만들고 전체 사이즈로 늘려서 어둡게 하고 투명도 조절하면 Dim 됨
Unity Dynamic Parameters
- 함수의 매개변수와 이벤트의 매개변수가 같으면 Dynamic으로 쓸 수 있음
레이아웃을 잡아보자
- 실제 업무에서는 디자이너가 만든대로 UI를 만들어어야 함
- Tip 1. 전체 디자인의 패턴을 찾자
- 공통되는 컴포넌트를 모으자
- 그리고 변화가 필요한 부분을 찾자
- Tip 2. 개별 UJI가 필요한지 체크
- 간혹 각각의 요소가 개별적인 아이템인 경우가 있음. 이 경우 해당하는 클래스를 분리하면 관리가 편해짐
- Tip 3. 큰 레이아웃부터 잡아가자
- 정 모르겠으면 스크린샷 따고 그거 올려놓고 배치하셈
UI를 구성하는 기준
- 하나의 역할을 가지고 있는 것끼리 UI를 묶는 것도 좋음
- 하나의 캔버스에 너무 많은게 몰리면 좀 그러니까…
UI 성능과 캔버스
-
드로우콜 : CPU가 GPU에게 화면을 그리라고 요청하는 명령.
-
드로우콜이 높으면 안좋음!!!!
-
드로우콜 상황은 Game View의 Stats에서 확인 가능함 (Batches)
- 대체적으로 이미지 하나 당 배치 1 올라감
-
스프라이트 아틀라스 : 여러 이미지를 묶어다가 하나로 연결해줌
- 이걸 쓰면 드로우콜이 줄어듬. 왜냐면 하나의 이미지에서 잘라다 쓰는거니까.
- 아틀라스 썼는데 뭔가 이미지 끝자락에 이상한게 있으면 Tight 설정 끄기
-
Window > Analysis > Frame Debugger 들어가면 프레임이 그려지는 순서를 확인할 수 있음
-
캔버스가 늘어나면 배치가 늘어남. 캔버스 별 최소 1개의 배치는 필요
-
그럼 캔버스 하나 두고 거따가 때려박으면 되나요?
- 아뇨.. 중간에 이미지가 사라지거나 이동, 크기 변경등의 UI 변화가 일어난다면 사진을 다시 찍어야 하는 것처럼, 캔버스 내에서 변경이 일어나도 그렇게 다시 찍음
- 그걸 UI Rebuild라고 함. 같은 캔버스 내에 있는게 전부 Rebuild가 되는거임. 변하지 않은 부분도.
-
Rebuild 디테일
- 캔버스는 캔버스 렌더러들을 모아서 배치를 생성함
- 이때 각 이미지, 텍스트 등을 메쉬 버퍼로 만들어서 하나의 배치로 통합함 → 최초 배치 시에는 버퍼 자체를 생성함
- 이후 Dirty Flag (변경) 감지했을 시에 Rebuild 진행
- 배치 리빌드만 하고 버텍스 재생성은 안하고 매트릭스만 갱신하는 경우
- 트랜스폼만 바꾸는거
- 배치 리빌드도 하고 버텍스 재생성도 하는거
- 그래픽 변경 (색이나 스프라이트 교체)
- 레이아웃 변경 (CSF, Layout Group)
- 자식 오브젝트에 영향을 주기 때문에 특히 위험
- Layout이 많으면 캔버스 분리하세요
- UI 구성할 때에만 사용하고 제거하거나 비활성화 하는걸 추천함
- 재활용 스크롤에 대해 알아두기
- 머터리얼 변경
- 배치 리빌드만 하고 버텍스 재생성은 안하고 매트릭스만 갱신하는 경우
-
하나의 캔버스에 너무 많은걸 두면 그 안에 하나만 바껴도 죄다 리빌드 해버리니까 위험함
-
그렇다고 캔버스를 많이두면 드로우콜이 많아짐
-
그러니까 적당히 중간지점을 찾아서 만드는게 중요함
- 자주 변경이 있는 부분과 변경이 거의 없는 부분을 나눠서 만들기
캔버스 관리에 대한 고민
- 세 가지 방법이 있음
-
캔버스 하나를 두고 하위에 UI 배치 → 소규모 프로젝트, 프로토타이핑
- 짧은 기간 프로젝트에 적용 가능
- 캔버스 소속 UI가 많아서 리빌드 자주 발생 → 성능 저하
-
UI별로 캔버스 개별 적용 → 화면 갱신 주기 설정을 잘 구분한다면 좋음
- UIManager 기본형에서 설명
- UIManager가 익숙하지 않다면 먼저 적용해보기
- 리빌드 범위는 괜찮지만 드로우콜 자체가 많아질 수 있음
- 웹에서 반응형으로 UI마다 스케일링을 분리한다면 적용할 수 있음
- 캔버스 구조 샘플 1 : 캔버스 별로 스케일 관리가 구분되어야 하는 경우 (웹 반응형), 정렬 관리 필요
- 캔버스 구조 샘플 2 : 캔버스 스케일, Raycast, 정렬 관리가 용이함. 리빌드 지역을 분리
- UIManager 기본형에서 설명
// 구조 샘플 1
- UIUserInfo (Canvas)
- UISetting (Canvas)
- UIMenu (Canvas)
...
// 구조 샘플 2
Canvas_HUD (Canvas + CanvasScaler + Raycaster)
- UIUserInfo (Canvas (Override Sorting: off))
- UISetting (Canvas (Override Sorting: off))
Canvas_GameInterface (Canvas + CanvasScaler + Raycaster)
- UIInventory (Canvas (Override Sorting : on, Sotring Order : 100))
- UIQuest (Canvas (Override Sorting : on, Sotring Order : 100))
...
- 레이어를 기준으로 그룹을 분리, 레이어마다 Canvas를 하나씩 적용 → 실무 권장
- 실무적으로 가장 권장하는 방식
- 리빌드도 괜찮고, 드로우콜도 안정적으로 관리됨
- 렌더링 순서도 잘 관리되어서 좋음
- UIManager 적용시 익숙하지 않다면 작업 규모가 번거로움
- 캔버스 구조 샘플
Canvas_HUD (Canvas)
- UIUserInfo
- UISetting
Canvas_GameInterface (Canvas, sortingOrder up)
- UIShop
- UIQuest
-
UI 프리팹 : 1 UI 프리팹 = 1 캔버스 = 1 UI 스크립트
-
프리팹 내부에 있는 오브젝트는 컴포넌트 인스펙터에 연결하는게 큰 문제 없음
-
개별 UI가 캔버스를 가지고 있다면 sorting order 챙겨줘야됨
-
UI 바인딩 - 케바케다. 같은 프리팹 안에 있는거면 상관없다.
- 동적 바인딩 : 개발자만 건드릴 때
- 장점 : 코드 만들어두면 연결 편리
- 단점 : 프리팹 구조, 이름 변경에 취약
- 인스펙터 연결 : 디자이너도 건드려야 할 때
- 장점 : 빠르고 안전. 런타임 동작을 안함. 이름 변경에 강함
- 단점 : 필드가 많아지면 연결 번거로움
- 동적 바인딩 : 개발자만 건드릴 때
POT : Power Of Two
- 텍스쳐의 가로, 세로 크기를 2의 제곱으로 관리하시오.
- GPU 호환성이 좋음. 고효율 압축 포맷이 잘 맞음.
- 메모리 최적화
- mipmap을 쓸 수 있음 (원본 텍스쳐를 저해상도 텍스쳐로 미리 만들어 두는 기능.)
- mipmap 하면 멀리있는거 저해상도로 만들어서 더 자연스럽게 보임
- 아틀라스로 관리할거면 아틀라스만 POT면 됨. 개별이미지는 NPOT여도 아틀라스에 포함되면 POT로 관리되기 때문임
UI 스크립트 관리
UI Base
- UI Base : 단일 UI들을 위한 베이스 클래스
- 주요 기능 : 열기/닫기 표준화, 확장 지점 제공, UIManager와 맞물리게 하기
public abstract class UIBase : MonoBehaviour
{
public virtual void OpenUI()
{
gameObject.SetActive(true);
OnOpen();
}
public virtual void CloseUI()
{
OnClose();
gameObject.SetActive(false);
}
protected virtual void OnOpen() { }
protected virtual void OnClose() { }
}- 데이터 전달은 어떻게 할 것인가?
- OpenUI를 시작함수로 정해버리면 데이터 전달 해줄 수 있음
public virtual void OpenUI(object args = null)
{
...
}- 근데 이러면 전달은 되겠지만 안정성이 떨어지고, OpenUI 별 일관성이 떨어짐.
- 그리고 박싱/언박싱이 발생할 수 있음. (근데 UI는 그렇게 치명적이진 않음. 제네릭으로 설정가능)
// 제네릭을 통해 데이터 안정성 확보
public virtual void OpenUI<T>(T args = null)
{
...
}- 또는, 데이터 처리용 클래스 사용 (DTO)
public class ConfirmDialogArgs{
public string message;
public Action<bool> callback;
}
public class ConfirmDialog : UIPopup
{
public override void OnShow(object args = null)
{
base.OnShow(args);
var data = args as ConfirmDialogArgs;
}
}-
익숙해지면 편하지만, 전달하는 입장에서는 파라미터를 명확하게 파악하기가 힘듬.
-
그래서 차라리 OpenUI 등의 공통 메소드에서 설정하지 않고, UI에 개별 세팅 기능을 설정.
public class ConfirmDialogArgs{
public string message;
public Action<bool> callback;
}
public class ConfirmDialog : UIPopup
{
public override void SetPopupData(ConfirmDialogArgs args)
{
...
}
}- 호출 구조에 대한 숙지가 필요함.
- Open 이후에 Set을 안하면 화면 갱신이 안될 수 있음.
- 외부 모듈에서 Open과 Set을 연결할 수도 있지만, 과도하면 안좋음.
- 팀 리뷰를 통해 기능을 꼭 공지!
- 메서드 체이닝을 통해 한 코드로 연결할 수 있지만 이 또한 과도하면 안좋음.
UIManager
- UI를 생성/보관/전달하기 위한 매니저 클래스
- 싱클톤을 적용해 어느 오브젝트에서든 쉽게 접근하도록
- 주요 기능 :
- UI 생성/관리 : 파괴 대신 비활성화 해서 보관.
- 열기/닫기 : UIBase 에서 동작하는거 연결해서 실행
- 리소스 정리 : 사용하지 않는 시점에 UI 정리 필요.
// 코드 예제
public class UIManager : Singleton<UIManager>
{
public const string UIPrefabPath = "UI/Prefabs";
private bool _isCleaning;
private Dictionary<string, UIBase> _uiDictionary = new Dictionary<string, UIBase>();
private void OnEnable()
{
SceneManager.sceneUnloaded += OnSceneUnloaded;
}
private void OnDisable()
{
SceneManager.sceneUnloaded -= OnSceneUnloaded;
}
public void OpenUI<T>() where T : UIBase
{
var ui = GetUI<T>();
ui?.OpenUI();
}
public void CloseUI<T>() where T : UIBase
{
if (IsExistUI<T>())
{
var ui = GetUI<T>();
ui?.CloseUI();
}
}
public T GetUI<T>() where T : UIBase
{
if(_isCleaning) return null;
string uiName = GetUIName<T>();
UIBase ui;
if (IsExistUI<T>())
ui = _uiDictionary[uiName];
else
ui = CreateUI<T>();
return ui as T;
}
private T CreateUI<T>() where T : UIBase
{
if (_isCleaning) return null;
string uiName = GetUIName<T>();
// 만약에 이미 같은 이름으로 오브젝트 있으면 삭제함
if (_uiDictionary.TryGetValue(uiName, out var prevUi) && prevUi != null)
{
Destroy(prevUi.gameObject);
_uiDictionary.Remove(uiName);
}
// 1. 프리팹 로드
string path = GetPath<T>();
GameObject prefab = Resources.Load<GameObject>(path);
if (prefab == null)
{
Debug.LogError($"[UIManager] prefab not found: {path}");
return null;
}
// 2. 인스턴스 생성
GameObject go = Instantiate(prefab);
// 3. 컴포넌트 획득
T ui = go.GetComponent<T>();
if (ui == null)
{
Debug.LogError($"[UIManager] preefab has no component : {uiName}");
Destroy(go);
return null;
}
// 4. Dictionary 등록
_uiDictionary[uiName] = ui;
return ui;
}
public bool IsExistUI<T>() where T : UIBase
{
string uiName = GetUIName<T>();
return _uiDictionary.TryGetValue(uiName, out var ui) && ui != null;
}
private string GetPath<T>() where T : UIBase
{
return UIPrefabPath + GetUIName<T>();
}
private string GetUIName<T>() where T : UIBase
{
return typeof(T).Name;
}
private void OnSceneUnload(Scene scene)
{
CleanAllUIs();
StartCoroutine(CoUnloadUnusedAssets());
}
private void CleanAllUIs()
{
if (_isCleaning) return;
_isCleaning = true;
try
{
foreach (var ui in _uiDictionary.Values)
{
if (ui == null) continue;
Destroy(ui.gameObject);
}
_uiDictionary.Clear();
}
finally
{
_isCleaning = false;
}
}
private IEnumerator CoUnloadUnusedAssets()
{
yield return Resources.UnloadUnusedAssets();
System.GC.Collect();
}
}-
확장 가능 포인트
- Root Canvas를 이용한 레이어 분리
- 레이어에 따른 생성/관리/접근 로직의 변화 필요
- 스택형 UI 관리
- 입력 잠금 설정
- 어드레서블 대응
- 비동기 로드 필요. 콜백처리, async(unitask 추천)
- 전환 애니메이션
- 실행은 Manager, 동작은 UIBase
- SafeArea 설정 (아이폰 노치)
- 다중 인스턴스 대응 방법
- 동일 UI가 여러개 필요하다면?
- 작업하면서 필요한 경우는 드뭄
- 슬롯 아이템들은 필요
- Root Canvas를 이용한 레이어 분리
-
UI 관리 고민
- 메인 컬렉션
- 고려 가능한 컬렉션 목록
- 리스트 : 일괄 순회 정렬 쉬움
- 스택 : 팝업 닫기 등 뒤로 기능
- 큐 : 토스트, 알림 대기열
- 딕셔너리 : key값을 통한 빠른 접근
- 메인 UI 관리는 접근이 효율적인 딕셔너리 활용
- 보조 관리는 Stack, Queue 만들어서 팝업이나 대기열 관리 가능
- 고려 가능한 컬렉션 목록
- UI Key 관리
- enum
- 오타 없음. 코드 자동완성됨
- 키 추가/삭제 시 별도 빌드 필요, 협업시 충돌 가능성
- 문자열
- UI 확장 유리, 어드레서블 레이블 대응 편함.
- 키가 있나 없나 파악 불가 (제네릭 타입오브 쓰면 편함)
- SO
- 코드 수정 없이 신규 UI 적용 가능
- 어떤 SO가 필요한지 접근 지점을 설정해야 함.
- enum
- 리소스 로드 방식
- 리소스 로드 : 동기방식
- 어드레서블 : 비동기 가능. 근데 복잡
- 메인 컬렉션
챌린저 반 강의 (주제 : 최적화)
최적화란 무엇인가
- 자원대비 효율을 극대화 하는 것
- 성능 + 자원 + 경험의 균형을 잡는 전략적 과정
- 최적화는 유저 경험에 큰 영향을 줌.
- 게임 몰입을 유지
- 기다림을 줄임
- 저사양 기기에서도 원활한 플레이
- 최적화를 안하면?
- 프레임 드랍
- 발열
- 메모리 초과
- 유저 이탈률 증가
- 스토어에 안좋은 리뷰…
- 최적화를 해야하는 시기
- 마지막 단계에서 한 번에 한다고 생각하면 안됨
- 설계 단계부터 고려
- 집중적으로 적용. 모든 구간이 아닌 병목 구간을 찾아서 거기에 집중하는게 효율적
프로파일링
- 성능 지표를 측정하고 분석하는 과정을 프로파일링이라고 함
- 유니티 프로파일러는 프레임 단위로 cpu/gpu/메모리/렌더링/오디오/네트워크 지표를 기록, 분석하는 툴
- 유니티에서 Window > Analysis > Profiler 들어가면 됨
- 또는 Ctrl + 7
- 최적화는 추측이 아니고 데이터 기반으로 해야함
- 프로파일링 > 원인 파악 > 추정 > 재측정 순서를 반드시 지키기.
주요 탭 별 설명
- CPU Usage
- 게임 내 모든 코드와 시스템이 CPU 시간을 얼마나 점유했는지 보여줌
- Timeline : 한 프레임 내 실행 순서를 시간축으로 보여줌
- Hierarchy : 함수별 호출 시간과 비율을 계층 구조로 보여줌
- 확인 포인트
- Update 호출 횟수 / 비용
- physics 비중
- GC 호출 시점과 비용
- 예시
- Update가 2000개 호출 : 구조 개선 필요
- GC가 2초마다 발생 : 메모리 할당 최적화 필요
- 게임 내 모든 코드와 시스템이 CPU 시간을 얼마나 점유했는지 보여줌
- GPU Usage
- 렌더링 파이프라인 성능 확인
- Timeline 뷰에서 GPU가 각 오브젝트를 그리는데 걸린 시간 확인 가능함
- 확인 포인트
- SetPass Calls / Draw Calss 수치
- 쉐이더 연산량
- VSync, Post Proccessing 비용
- 예시
- 드로우콜 5000개 이상 : Batching / Instancing 적용 필요
- 오버드로우 심함 : UI 정리, 파티클 최적화
- 메모리
- 메모리 사용량과 GC 발생 패턴 추적
- Heap, Texture, Mesh, Audio 등 자원별 사용량 분석
- 확인 포인트
- Mono Heap : C# 스크립트 메모리 관리 영역
- GC Alloc : 매 프레임 할당되는 메모리 추적 (노란색 경고 표시 주목)
- 예시
- string + string 반복 사용 → 매 프레임 GC 발생 → StringBuilder 쓰기
- 렌더링
- 렌더링 세부 성능 분석 (CPU ←> GPU 사이 렌더링 지연)
- 확인 포인트
- Batches (드로우콜 개수)
- SetPass Calls (머티리얼 변경 횟수)
- Triangles/Vertices 수
- 예시
- 배치가 3000개 이상 → Static Batching / SRP Batcher 활용 필요
- 물리 (Physics)
- 충돌, 리지드바디, 조인트, 레이캐스트 등 물리 연산 부하 분석
- FixedUPdate 주기와 Physics Step 비용 확인
- 예시
- 모든 오브젝트에 메시콜라이더 사용 → 충돌 비용 증가 → 박스나 스피어 콜라이더로 교체
- 오디오
- 오디오 클립 디코딩 / 믹싱 비용 확인
- 예시
- Decompress On Load 설정이 많은 경우 → 로딩 지연 발생
- 네트워크
- 멀티플레이 환경에서 패킷 전송량, RTT, 대역폭 확인
- 예시
- 매 프레임 불필요한 패킷 전송 → 데이터 전송 최적화 필요
활용 방법 (실전 단계)
- 프레임 드랍 하락 구간을 기록함
- CPU / GPU 먼저 확인
- CPU 가득 차 있으면 → 로직/물리/GC 문제
- GPU 가득 차 있으면 → 렌더링/쉐이더 문제
- 메모리 확인
- GC Alloc이 자주 발생 → 메모리 구조 개선 필요
- 렌더링/물리 세부 분석
- 드로우콜, 충돌 연산량 체크 후 최적화 포인트 도출
GC 최적화
- GC : C# 런타임이 더 이상 참조되지 않는 객체를 자동으로 정리하는 메모리 관리 시스템
- GC가 실행되는 동안에는 CPU가 메모리 정리에 집중하다보니 게임 로직과 렌더링이 멈춤.
- GC는 언제 실행될지 예측이 안됨.
발생하는 시기
- new
- string 연산 (+, substring, replace 등등)
- 박싱/언박싱
- LINQ
- foreach가 구조체 열거자가 아닌 경우
- Instantiate/Destroy 반복
최적화 방법
- 할당 줄이기
- List.Clear() + Capacity 조정으로 리스트 재사용
- 배열/버퍼를 미리 만들어놓고 재사용
- 문자열은 StringBuilder 사용
- 딕셔너리 foreach 쓰면 GC Alloc 거의 발생함. 꼭 바꿔서 쓰기.
- 오브젝트 풀링
- 총알, 이펙트, 텍스트 등 반복 생성되는 오브젝트는 풀링으로 관리
- NonAlloc API 사용
- Physics.RaycastNonAlloc, OverlapSphereNonAlloc 등 할당 없는 함수 사용
- GC 호출 지연/제어
- System.GC.Collect() 를 직접 호출하는 것은 권장되지 않음 (스파이크 심각)
- 그러나 로딩 화면, 씬 전환 시점에 한정적으로 수동 호출하는 전략은 가능
프로파일러에서 확인하는 방법
- CPU Usage : 하단 뷰에서 GC 샘플이 크게 보이면, 그 프레임에 GC가 실행된 것
- Allocation : 하단 Hierachy에서 GC Alloc/Allocation을 노출해 어떤 함수가 몇 바이트를 할당했는지 확인.
- Timeline에서는 Allocations(Managed)를 켜고 파란 Allocation 샘플을 더블클릭하면 콜스택으로 원인 추적 가능
- 메모리 그래프 교차검증
- Used Heap이 계단식으로 증가하다가 뚝 떨어지는 지점이 GC 실행 지점. 직전 구간에서 어떤 코드가 할당을 쌓았는지 확인해가기.
드로우 콜
- CPU가 GPU에게 메시를 머티리얼/셰이더 상태로 카메라/패스에 그리라고 명령하는 요청
- 상태가 조금이라도 달라지면 새 드로우 콜을 날림
- 관련 지표
- Batches : 제출된 그리기 묶음 수 (= 거의 Draw Call 수와 동일) 엔진이 제출한 드로우 요청의 개수
- SetPass Calls : 셰이더 패스/머티리얼 상태 변경 횟수(더 비쌈). 같은 머티리얼로 많이 그리면 SetPass가 줄어듬. SRP Batcher가 cpu 시간을 크게 감소시켜줌.
- 요약 : 상태 변화가 적고, 같은 머티리얼로 묶어 많이 그릴수록 효율적
- 최적화 이점
- FPS 향상
- 배터리 절약 / 발열 감소
- 씬 확장성 개선 (더 많은 오브젝트 추가 가능)
- 확인 방법
- 게임 뷰에 stats에 Batches, SetPass Calls, Tris/Verts 바로 확인 가능
- Profiler → Rendering/GPU Usage : Batches/SetPass 추세, 스파이크 위치 파악
- Frame Debugger : 프레임을 한 호출씩 스텝함
- 어떤 오브젝트가 어떤 머티리얼로 왜 새 드로우콜이 생겼는지 자세하게 확인 가능
유니티의 드로우 콜 최적화 방법들
- GPU 인스턴싱 : 같은 메시 여러개를 동시에 렌더링. (ex, 나무, 풀 등 반복 오브젝트)
- 드로우 콜 배칭 : 메시를 결합해 드로우 콜을 줄임. 아래는 유니티에서 제공하는 배칭 타입
- 정적 배칭 : batching static 애들을 안 움직이는 애들을 묶어서 한번에 개별적으로 그림. 그럼 batches가 눈에 띄게 줄음
- 동적 배칭 : cpu에서 동일한 설정을 공유하는 버텍스를 그룹화해서 하나의 드로우 콜로 렌더링함. 자동 유니티 안에서 해줌. 메시/버텍스 작은애들 알아서 하나로 그려줌
- 설정방법 : Project Settings > player > other > dynamic batching 키기
프레임 디버거
- Window > Analysis > Frame Debugger
- 플레이
- Enable 버튼 클릭
- 현재 프레임에서 수행한 모든 드로우콜 목록이 표시됨
아틀라스
- 여러 텍스쳐를 하나의 아틀라스로 묶어서 하나의 머터리얼로 다양한 오브젝트 그림. GPU 부하 감소
최적화 요약
- 동일 머티리얼 공유해서 드로우콜 줄이기
- 정적 배치로 움직이지 않는 애들 한번에 그리기
- 동적 배치로 소형 메쉬 애들 합쳐서 한번에 그리기
- SRP Batcher로 쉐이더 구조 최적화
UI 최적화
- 동적 영역은 별도 캔버스로 분리하기
- 스프라이트/TMP 아틀라스로 머티리얼/텍스쳐 통일하기
- 마스크/아웃라인/그림자는 추가 패스를 유발하기 때문에 꼭 필요한 곳에만 사용
연산 최적화
- 필요한 계산만, 한 번만, 가능한 빠르게
- 성능이 중요한 부분(물리, AI, 대규모 NPC 처리 등) 에서는 항상
- 비싼 함수 피하기
- 루프 안 중복 연산 제거
- 불필요한 연산 없애기