오늘 학습 키워드

챌린저 반 강의

오늘 학습 한 내용을 나만의 언어로 정리하기

챌린저 반 강의 (주제 : 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 구성할 때에만 사용하고 제거하거나 비활성화 하는걸 추천함
          • 재활용 스크롤에 대해 알아두기
        • 머터리얼 변경
  • 하나의 캔버스에 너무 많은걸 두면 그 안에 하나만 바껴도 죄다 리빌드 해버리니까 위험함

  • 그렇다고 캔버스를 많이두면 드로우콜이 많아짐

  • 그러니까 적당히 중간지점을 찾아서 만드는게 중요함

    • 자주 변경이 있는 부분과 변경이 거의 없는 부분을 나눠서 만들기

캔버스 관리에 대한 고민

  • 세 가지 방법이 있음
  1. 캔버스 하나를 두고 하위에 UI 배치 소규모 프로젝트, 프로토타이핑

    • 짧은 기간 프로젝트에 적용 가능
    • 캔버스 소속 UI가 많아서 리빌드 자주 발생 성능 저하
  2. UI별로 캔버스 개별 적용 화면 갱신 주기 설정을 잘 구분한다면 좋음

    • UIManager 기본형에서 설명
      • UIManager가 익숙하지 않다면 먼저 적용해보기
    • 리빌드 범위는 괜찮지만 드로우콜 자체가 많아질 수 있음
    • 웹에서 반응형으로 UI마다 스케일링을 분리한다면 적용할 수 있음
    • 캔버스 구조 샘플 1 : 캔버스 별로 스케일 관리가 구분되어야 하는 경우 (웹 반응형), 정렬 관리 필요
    • 캔버스 구조 샘플 2 : 캔버스 스케일, Raycast, 정렬 관리가 용이함. 리빌드 지역을 분리
// 구조 샘플 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))
...
  1. 레이어를 기준으로 그룹을 분리, 레이어마다 Canvas를 하나씩 적용 실무 권장
    • 실무적으로 가장 권장하는 방식
    • 리빌드도 괜찮고, 드로우콜도 안정적으로 관리됨
    • 렌더링 순서도 잘 관리되어서 좋음
    • UIManager 적용시 익숙하지 않다면 작업 규모가 번거로움
    • 캔버스 구조 샘플
Canvas_HUD (Canvas)
	- UIUserInfo
	- UISetting

Canvas_GameInterface (Canvas, sortingOrder up)
	- UIShop
	- UIQuest
  • UI 프리팹 : 1 UI 프리팹 = 1 캔버스 = 1 UI 스크립트

  • 프리팹 내부에 있는 오브젝트는 컴포넌트 인스펙터에 연결하는게 큰 문제 없음

  • 개별 UI가 캔버스를 가지고 있다면 sorting order 챙겨줘야됨

  • UI 바인딩 - 케바케다. 같은 프리팹 안에 있는거면 상관없다.

    1. 동적 바인딩 : 개발자만 건드릴 때
      • 장점 : 코드 만들어두면 연결 편리
      • 단점 : 프리팹 구조, 이름 변경에 취약
    2. 인스펙터 연결 : 디자이너도 건드려야 할 때
      • 장점 : 빠르고 안전. 런타임 동작을 안함. 이름 변경에 강함
      • 단점 : 필드가 많아지면 연결 번거로움

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가 여러개 필요하다면?
      • 작업하면서 필요한 경우는 드뭄
        • 슬롯 아이템들은 필요
  • UI 관리 고민

    • 메인 컬렉션
      • 고려 가능한 컬렉션 목록
        • 리스트 : 일괄 순회 정렬 쉬움
        • 스택 : 팝업 닫기 등 뒤로 기능
        • 큐 : 토스트, 알림 대기열
        • 딕셔너리 : key값을 통한 빠른 접근
      • 메인 UI 관리는 접근이 효율적인 딕셔너리 활용
      • 보조 관리는 Stack, Queue 만들어서 팝업이나 대기열 관리 가능
    • UI Key 관리
      • enum
        • 오타 없음. 코드 자동완성됨
        • 키 추가/삭제 시 별도 빌드 필요, 협업시 충돌 가능성
      • 문자열
        • UI 확장 유리, 어드레서블 레이블 대응 편함.
        • 키가 있나 없나 파악 불가 (제네릭 타입오브 쓰면 편함)
      • SO
        • 코드 수정 없이 신규 UI 적용 가능
        • 어떤 SO가 필요한지 접근 지점을 설정해야 함.
    • 리소스 로드 방식
      • 리소스 로드 : 동기방식
      • 어드레서블 : 비동기 가능. 근데 복잡

챌린저 반 강의 (주제 : 최적화)

최적화란 무엇인가

  • 자원대비 효율을 극대화 하는 것
  • 성능 + 자원 + 경험의 균형을 잡는 전략적 과정
  • 최적화는 유저 경험에 큰 영향을 줌.
    • 게임 몰입을 유지
    • 기다림을 줄임
    • 저사양 기기에서도 원활한 플레이
  • 최적화를 안하면?
    • 프레임 드랍
    • 발열
    • 메모리 초과
    • 유저 이탈률 증가
    • 스토어에 안좋은 리뷰…
  • 최적화를 해야하는 시기
    • 마지막 단계에서 한 번에 한다고 생각하면 안됨
    • 설계 단계부터 고려
    • 집중적으로 적용. 모든 구간이 아닌 병목 구간을 찾아서 거기에 집중하는게 효율적

프로파일링

  • 성능 지표를 측정하고 분석하는 과정을 프로파일링이라고 함
  • 유니티 프로파일러는 프레임 단위로 cpu/gpu/메모리/렌더링/오디오/네트워크 지표를 기록, 분석하는 툴
    • 유니티에서 Window > Analysis > Profiler 들어가면 됨
    • 또는 Ctrl + 7
  • 최적화는 추측이 아니고 데이터 기반으로 해야함
  • 프로파일링 > 원인 파악 > 추정 > 재측정 순서를 반드시 지키기.

주요 탭 별 설명

  1. CPU Usage
    • 게임 내 모든 코드와 시스템이 CPU 시간을 얼마나 점유했는지 보여줌
      • Timeline : 한 프레임 내 실행 순서를 시간축으로 보여줌
      • Hierarchy : 함수별 호출 시간과 비율을 계층 구조로 보여줌
    • 확인 포인트
      • Update 호출 횟수 / 비용
      • physics 비중
      • GC 호출 시점과 비용
    • 예시
      • Update가 2000개 호출 : 구조 개선 필요
      • GC가 2초마다 발생 : 메모리 할당 최적화 필요
  2. GPU Usage
    • 렌더링 파이프라인 성능 확인
    • Timeline 뷰에서 GPU가 각 오브젝트를 그리는데 걸린 시간 확인 가능함
    • 확인 포인트
      • SetPass Calls / Draw Calss 수치
      • 쉐이더 연산량
      • VSync, Post Proccessing 비용
    • 예시
      • 드로우콜 5000개 이상 : Batching / Instancing 적용 필요
      • 오버드로우 심함 : UI 정리, 파티클 최적화
  3. 메모리
    • 메모리 사용량과 GC 발생 패턴 추적
    • Heap, Texture, Mesh, Audio 등 자원별 사용량 분석
    • 확인 포인트
      • Mono Heap : C# 스크립트 메모리 관리 영역
      • GC Alloc : 매 프레임 할당되는 메모리 추적 (노란색 경고 표시 주목)
    • 예시
      • string + string 반복 사용 매 프레임 GC 발생 StringBuilder 쓰기
  4. 렌더링
    • 렌더링 세부 성능 분석 (CPU > GPU 사이 렌더링 지연)
    • 확인 포인트
      • Batches (드로우콜 개수)
      • SetPass Calls (머티리얼 변경 횟수)
      • Triangles/Vertices 수
    • 예시
      • 배치가 3000개 이상 Static Batching / SRP Batcher 활용 필요
  5. 물리 (Physics)
    • 충돌, 리지드바디, 조인트, 레이캐스트 등 물리 연산 부하 분석
    • FixedUPdate 주기와 Physics Step 비용 확인
    • 예시
      • 모든 오브젝트에 메시콜라이더 사용 충돌 비용 증가 박스나 스피어 콜라이더로 교체
  6. 오디오
    • 오디오 클립 디코딩 / 믹싱 비용 확인
    • 예시
      • Decompress On Load 설정이 많은 경우 로딩 지연 발생
  7. 네트워크
    • 멀티플레이 환경에서 패킷 전송량, RTT, 대역폭 확인
    • 예시
      • 매 프레임 불필요한 패킷 전송 데이터 전송 최적화 필요

활용 방법 (실전 단계)

  1. 프레임 드랍 하락 구간을 기록함
  2. CPU / GPU 먼저 확인
    • CPU 가득 차 있으면 로직/물리/GC 문제
    • GPU 가득 차 있으면 렌더링/쉐이더 문제
  3. 메모리 확인
    • GC Alloc이 자주 발생 메모리 구조 개선 필요
  4. 렌더링/물리 세부 분석
    • 드로우콜, 충돌 연산량 체크 후 최적화 포인트 도출

GC 최적화

  • GC : C# 런타임이 더 이상 참조되지 않는 객체를 자동으로 정리하는 메모리 관리 시스템
  • GC가 실행되는 동안에는 CPU가 메모리 정리에 집중하다보니 게임 로직과 렌더링이 멈춤.
  • GC는 언제 실행될지 예측이 안됨.

발생하는 시기

  • new
  • string 연산 (+, substring, replace 등등)
  • 박싱/언박싱
  • LINQ
  • foreach가 구조체 열거자가 아닌 경우
  • Instantiate/Destroy 반복

최적화 방법

  1. 할당 줄이기
    • List.Clear() + Capacity 조정으로 리스트 재사용
    • 배열/버퍼를 미리 만들어놓고 재사용
    • 문자열은 StringBuilder 사용
    • 딕셔너리 foreach 쓰면 GC Alloc 거의 발생함. 꼭 바꿔서 쓰기.
  2. 오브젝트 풀링
    • 총알, 이펙트, 텍스트 등 반복 생성되는 오브젝트는 풀링으로 관리
  3. NonAlloc API 사용
    • Physics.RaycastNonAlloc, OverlapSphereNonAlloc 등 할당 없는 함수 사용
  4. 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 부하 감소

최적화 요약

  1. 동일 머티리얼 공유해서 드로우콜 줄이기
  2. 정적 배치로 움직이지 않는 애들 한번에 그리기
  3. 동적 배치로 소형 메쉬 애들 합쳐서 한번에 그리기
  4. SRP Batcher로 쉐이더 구조 최적화

UI 최적화

  • 동적 영역은 별도 캔버스로 분리하기
  • 스프라이트/TMP 아틀라스로 머티리얼/텍스쳐 통일하기
  • 마스크/아웃라인/그림자는 추가 패스를 유발하기 때문에 꼭 필요한 곳에만 사용

연산 최적화

  • 필요한 계산만, 한 번만, 가능한 빠르게
  • 성능이 중요한 부분(물리, AI, 대규모 NPC 처리 등) 에서는 항상
    • 비싼 함수 피하기
    • 루프 안 중복 연산 제거
    • 불필요한 연산 없애기