✅ 오늘의 학습 목표
1. 프로그래밍 최적화
2. 오디오 최적화
3. UI 최적화
1. 프로그래밍 최적화
1. 메모리와 GC
C#은 메모리를 자동으로 관리하지만 GC가 실행될 때마다 순간적인 멈춤이 생길 수 있다.
즉, 너무 자주 객체를 만들면 GC가 폭주하고 프레임 드랍이 발생한다는 뜻이다.
C#에서는 값 형식(Value Type) 과 참조 형식(Reference Type) 으로 메모리 관리 방식이 구분된다.
- 값 형식(int, float, bool 등)은 스택(Stack) 메모리에 저장되며,
범위를 벗어나면 자동으로 소멸된다. - 참조 형식(클래스, 리스트 등)은 힙(Heap) 에 저장되며,
참조가 남아 있으면 계속 유지되고, 참조가 사라질 때 GC가 해제한다.
스택은 빠르고 단순하게 덮어쓰는 방식으로 관리되지만
힙은 GC가 주기적으로 관리하기 때문에 오버헤드가 생길 수 있다.
예를 들어 GetComponent()의 결과를 캐싱하면 성능은 향상되지만
오랫동안 필요 없는 객체를 계속 참조하고 있으면 GC가 해제하지 못해 메모리를 낭비한다.
따라서 자주 사용하는 객체만 캐싱하는 것이 효율적이다.
- 문자열 연결은 StringBuilder로 처리
- foreach문 대신 for문 사용
- 불필요한 new 키워드 사용 방지
- GetComponent()는 한번만 캐싱
2. Update와 코루틴
Update() 메서드는 프레임마다 실행된다.
비어 있어도 호출되기 때문에 수백 개의 오브젝트가 Update()를 가지고 있으면 프레임당 수천 번의 불필요한 함수 호출이 발생한다.
또한 코루틴도 내부적으로 상태를 저장하는 객체를 생성하기 때문에 자주 돌면 GC 대상이 된다.
- 불필요한 Update는 삭제
- 반복이 필요한 경우 InvokeRepeating() 또는 누적 타이머 사용
- WaitForSeconds()는 매번 new하지 말고 캐싱
- Transform 변경은 한 프레임에 한 번만 수행 (SetPositionAndRotation 활용)
3. Object Pool
게임 중 계속해서 Instantiate()와 Destroy()를 반복하면 메모리 할당과 해제가 반복되어 성능이 급격히 하락한다.
Unity는 생성 시 오브젝트의 모든 컴포넌트를 초기화하고
삭제시에도 모든 참조를 해제해야하기 때문에 비용이 매우 크다.
그래서 이 과정을 반복하면 GC가 자주 발생하고 특히 모바일 환경에서는 프레임 드랍이 체감될 수 있다.
- 자주 사용하는 오브젝트는 풀링(Object Pool) 으로 재사용
- 풀에서 꺼낼 때 SetParent() 사용은 최소화 (트랜스폼 재계산 부담 큼)
- 미리 구조를 설계해 풀 기반으로 관리
4. 물리 연산
Unity의 물리 연산은 매우 비싸다.
물리 엔진은 매번 Transform의 변경을 감지하고 이를 실제 물리 연산 데이터 (충돌, Rigidbody, Collider 등)에 반영해야한다.
따라서 자동 동기화는 편리하지만 불필요한 프레임 연산이 많기에 Auto Sync Transform 옵션을 켜두면 프레임 부하가 커진다.
- Auto Sync Transform은 끄고 필요할 때만 수동으로 Physics.SyncTransforms() 호출
- 거리 비교는 sqrMagnitude 사용 (제곱근 계산 생략)
- 충돌은 Layer Collision Matrix 로 미리 필터링
- 콜라이더는 단순한 형태(Box/Sphere/Capsule) 로 제한
5. Debug.Log()
디버그 로그는 단순한 출력처럼 보이지만 문자열을 처리하고
콘솔로 전달하는 과정 자체가 CPU 사용량이 크다.
- 릴리스 빌드에서는 Debug.Log 비활성화 ([Conditional("ENABLE_LOG")] 사용)
- Profiler.Begin/EndSample() 로 특정 구간 성능 측정
- Memory Profiler 로 GC·메모리 점검
6. sqrMagnitude
CPU는 float 값을 곱하는 것을 나누는 것 보다 빠르게 처리한다.
그래서 Distance() 함수의 경우 제곱근 계산으로 인해 CPU 사용량이 증가할 수 있다.
그래서 sqrMagnitude 속성을 이용해 계산하는 것이 좋다.
2. 오디오 최적화
[mono]
불필요한 Stereo는 단일 채널 (Mono)로 설정하면 용량이 절반으로 줄고 메모리 사용량도 감소한다.
[샘플 레이트]
리샘플링으로 음질 손실 없이 용량을 줄일 수 있다.
[포맷]
WAV 포맷은 디코딩 시 가장 적은 CPU 자원을 사용하고 재압축 과정에서 손실을 줄이는 장점이 있다.
하지만 WAV 포맷은 용량이 크기 때문에 저장공간의 여우가 있을 경우에만 고려되어야 한다.
압축 시에는 Vorbis 포맷을 사용하고 효과음은 ADPCM을 사용한다.
[Load Type]
- Decompress On Load
: 압축 해제 후 메모리에 오디오 정보를 올립니다. BGM 같이 용량이 클 경우 이 옵션을 메모리
용량을 많이 차지하게 됩니다. 200 kb 미만의 크기를 가진 오디오 파일에 적합합니다. - Compressed in Memory
: 압축된 상태로 메모리에 오디오 정보를 올립니다. 200 kb 이상의 오디오 파일에 적합합니다. - Stream
: BGM에 적절한 옵션입니다
[사용하지 않는 오디오]
사용하지 않는 오디오의 볼륨을 0으로 설정하는 것 보단 삭제하는 게 훨씬 좋다.
[음원 클립 참조 관리]
유니티에서 음원 클립을 참조하고 있는 변수에 null을 할당해도 메모리에서는 해제하지 않는다.
자주 사용하는 음원은 한 번 할당 후 메모리에서 유지하는 것이 매번 메모리에 할당하는 것 보다 효과적이지만
그렇지 않은 경우에는 Resource.Load()와 Resource.UnloadAsset()을 통해 직접 생성하고 제거해야한다.
3. UI 최적화
UI는 Canvas 단위로 Draw call이 발생한다.
캔버스 하위에 배치한 UI 구성 요소 중 하나가 형태가 변경되거나 위치가 변경되면 캔버스 전체에 대한 업데이트가 진행된다.
[Canvas 분리]
캔버스는 반드시 하나만 존재해야하는 제약이 없기 때문에 정적인 UI 요소와 동적인 UI 요소를 캔버스로 구분하여 구성할 경우
보다 효율적으로 캔버스의 업데이트를 처리하게 된다.
특히 캔버스 하위에 캔버스를 배치할 수 있어 정적인 캔버스 하위에 동적인 캔버스 형태로 구조를 만들 수 있다.
[안 보이는 UI는 ‘이동’이 아닌 ‘비활성화’]
안 쓰는 UI는 화면 밖으로 밀어내는 게 아니라 SetActive(false) 처리로 비활성화해야 한다.
이동만 하면 여전히 CPU가 그 UI를 계산한다.
[Raycast Target 최소화]
- 클릭·터치가 필요한 버튼만 Raycast Target = ON
- 단순 장식용 UI는 반드시 OFF로 바꿔 연산량을 줄이도록 한다.
[Layout Group, 꼭 필요할 때만]
- 자동 정렬은 편리하지만 실시간 재배치 비용이 크다.
- ScrollView나 List는 재사용(Pooling) 형태로 구현하도록 한다.
[기타]
- 투명 패널도 실제로 렌더링된다 → 불필요하면 비활성화!
- 버튼은 배경+버튼 분리보다 합쳐진 단일 이미지가 효율적이다.
- 텍스트 대신 아이콘 중심 UI를 활용해 Draw Call을 줄이도록 한다.
- 전체 화면 UI가 켜져도 3D 공간은 여전히 렌더링되므로 게임 씬이 멈추지 않게 Time.timeScale로 제어할 수 있다.
