EveryDay.DevUp

[TACampus] 9주차 과제 - Unity 최적화 플로우 차트이해하기 본문

TA/TACampus-Com2us

[TACampus] 9주차 과제 - Unity 최적화 플로우 차트이해하기

EveryDay.DevUp 2025. 7. 28. 17:58


1. 프로파일링 전 타겟 디바이스 설정

성능 최적화 작업을 시작하기 전에 반드시 타겟 환경을 명확히 정의해야 합니다. 이는 최적화 목표와 방향성을 결정하는 핵심 단계입니다.

플랫폼 결정

Unity는 PC, 모바일, 휴대용 게임기 등 다양한 플랫폼을 지원하며, 크로스 플랫폼 개발 환경이 잘 구축되어 있어 하나의 프로젝트로 여러 플랫폼을 동시에 지원할 수 있습니다. 각 플랫폼의 특성을 이해하고 프로젝트에 적합한 플랫폼을 선택해야 합니다.

타겟 디바이스 스펙 분석

매출 극대화를 위해서는 최대한 넓은 범위의 저사양 디바이스를 지원하는 것이 유리하지만, 프로젝트의 장르와 특성에 따라 지원 가능한 디바이스 스펙에는 한계가 존재합니다.

타겟 디바이스 설정 시 고려해야 할 핵심 요소들은 다음과 같습니다:

운영체제와 아키텍처: 지원할 OS 버전의 범위와 32비트 또는 64비트 아키텍처 지원 여부를 결정해야 합니다. 또한 실제 하드웨어인지 시뮬레이터 환경인지도 고려해야 합니다.

하드웨어 성능: GPU 그래픽 카드 성능, 메모리 용량, CPU 성능 등의 하드웨어 스펙을 종합적으로 분석해야 합니다.

시장 환경 분석: GPU, CPU, 메모리의 발전 속도가 과거에 비해 둔화되었기 때문에, 미래의 디바이스 스펙 발전을 지나치게 낙관적으로 예측하면 출시 시점에 지원 가능한 디바이스 범위가 제한될 수 있습니다.

최종 타겟 설정: 프로젝트 개발 시점의 디바이스 점유율과 GPU, CPU, 메모리 성능 분포를 종합적으로 분석하여 최종 타겟 디바이스를 설정합니다.

성능 목표 설정

타겟 디바이스 설정 후에는 최소 60fps를 유지하는 것을 목표로 하며, 각 프레임 내에서 CPU와 GPU가 수행하는 작업량을 최소화하여 디바이스 발열을 제어하는 것이 중요합니다.


2. 프로파일링을 통한 병목 지점 분석

성능 병목은 크게 CPU Bound와 GPU Bound로 구분됩니다. 각각의 원인을 체계적으로 분석하고 해결 방안을 찾아야 합니다.

CPU Bound 최적화

CPU Bound는 메인 스레드와 잡 워커 스레드에서 발생하는 성능 병목을 의미합니다.

가비지 컬렉션(GC) 최적화

GC Spike 발생 원인: GC는 힙 메모리에 사용하지 않는 메모리가 지속적으로 축적되어 발생하는 현상입니다. 콘텐츠가 많고 시스템이 복잡할수록 GC가 발생하기 쉬운 구조가 됩니다.

GC Spike 해결 방법: 씬 전환 시에 빈 씬을 삽입하여 GC를 의도적으로 호출할 수 있는 특정 지점을 만들면 예상치 못한 Spike를 줄일 수 있습니다. Unity에서 지원하는 Incremental GC 옵션을 사용하면 GC를 분할 처리할 수 있지만, 분할 처리 중에도 GC가 계속 축적되면 오히려 오버헤드가 발생할 수 있습니다.

근본적 해결책: 오브젝트 풀링, 구조체 사용, 문자열 관련 최적화, 코루틴 대신 UniTask 사용을 통해 메모리 할당과 관리를 최소화하여 GC 발생을 억제하는 코드를 작성해야 합니다. 또한 박싱과 언박싱이 발생하지 않도록 주의해야 합니다.

물리 처리 최적화

물리 요소가 많지 않은 게임의 경우 Auto Sync Transform 옵션을 비활성화하여 불필요한 연산을 제거할 수 있습니다. Layer Collision Matrix 설정을 통해 불필요한 충돌 검사를 제거하고, Primitive Collider를 사용하여 연산 부하를 줄일 수 있습니다. 런타임 성능 개선을 위해 Physics.BakeMesh를 활용하고, Maximum Allowed Timestep을 설정하여 물리 업데이트에 소요되는 최대 시간을 제한할 수 있습니다.

UI 시스템 최적화

렌더링 모드 최적화: Camera Render Mode에서 Overlay 설정이 Camera 설정보다 성능상 유리합니다. Camera Render Mode를 None으로 설정하면 Camera.main을 호출하면서 오버헤드가 발생하므로 주의해야 합니다.

픽셀 퍼펙트 설정: Pixel Perfect가 필요하지 않은 게임이라면 해당 기능을 비활성화해야 합니다. RectTransform이 변경될 때마다 UI 요소의 정점을 모두 재계산하는 오버헤드가 발생하기 때문입니다.

상호작용 최적화: 사용자 상호작용이 필요하지 않은 UI에서는 Graphic Raycaster 컴포넌트를 제거하여 불필요한 오버헤드를 줄일 수 있습니다.

캔버스 분리: Canvas에서 동적으로 움직이는 UI 요소와 정적 UI를 분리하고 Nested Canvas로 구성하면 렌더링 오버헤드를 크게 줄일 수 있습니다.

애니메이션 최적화: UI에 애니메이션이 포함된 경우 DOTween이나 LeanTween과 같은 Tween 라이브러리를 사용하는 것이 Animator 컴포넌트를 사용하는 것보다 성능상 유리합니다.

레이아웃 컴포넌트 관리: Layout 컴포넌트는 필요한 경우에만 활성화하고, 가능한 한 비활성화 상태를 유지하여 오버헤드를 줄여야 합니다.

Z축 처리: UI Object의 Z축 이동 대신 Sorting Order를 사용하는 것이 성능상 더 효율적입니다.

텍스처 아틀라스 최적화: UI 이미지에 텍스처 아틀라스를 사용할 때, 52x52 크기의 이미지 하나를 위해 2048x2048 아틀라스를 로드하는 것은 비효율적입니다. 아틀라스가 만능 해결책은 아니며, UI 특성에 따라 적절한 리소스 처리가 필요합니다. 경우에 따라서는 하나의 UI에 하나의 아틀라스를 사용하는 것이 최적화 관점에서 더 유리할 수 있습니다.

UI 렌더링에서도 드로우 콜이 최소화되도록 설계하는 것이 중요하며, 프레임 디버거를 적극 활용해야 합니다.

애니메이션 최적화

데이터 최적화: 연산에 사용되는 본과 애니메이션 클립 데이터를 최적화할 수 있는지 검토해야 합니다. 불필요한 스케일 키가 있다면 제거하고, Animation Clip의 Key Frame Reduction 설정을 확인해야 합니다.

VAT 활용: GPU에서만 애니메이션을 처리할 수 있는 VAT(Vertex Animation Texture) 기법을 고려해볼 수 있습니다. 특히 비중이 낮은 군중 애니메이션의 경우 CPU 부담을 GPU로 분산시킬 수 있습니다.

Animator 최적화: Animator에서 문자열 대신 해시 값을 캐싱하여 사용하면 성능을 개선할 수 있습니다.

스크립트 최적화

캐싱 활용: GetComponent, Find 함수, name, tag 속성은 반복적으로 호출되는 구문에서 직접 사용하지 말고 캐싱하여 사용해야 합니다. tag 비교 시에는 "==" 연산자 대신 CompareTag 함수를 사용하는 것이 효율적입니다.

불필요한 요소 제거: 비어있는 Unity 이벤트 함수는 제거하고, Debug.Log가 상용 빌드에 포함되지 않도록 Conditional Attribute나 전처리기를 사용해야 합니다.

Transform 최적화: Transform 변경 시 position, rotation, scale을 개별적으로 처리하지 말고 한 번에 처리해야 합니다. SetParent로 부모를 변경하는 작업은 최소화해야 합니다.

계층 구조 최적화: GameObject의 구조가 복잡할수록 매트릭스 연산에 더 많은 시간이 소요되므로, 가능한 한 단순한 계층 구조를 사용해야 합니다.

컬렉션 최적화: Collection 사용 시 예상되는 크기를 미리 안다면 해당 크기로 초기화하는 것이 좋습니다. 크기를 지정하지 않고 사용하면 Collection 크기를 초과할 때 크기를 확장하는 과정에서 부하가 발생합니다.

LINQ 사용 제한: LINQ는 내부적으로 반복문으로 변환되므로 직접 반복문을 작성하는 것과 동일한 결과를 얻을 수 있습니다. LINQ 사용 시 내부적으로 객체 할당이 발생할 가능성이 높고, 일반적인 반복문보다 성능이 떨어지는 경우가 많습니다.

수학 연산 최적화: 삼각 함수 사용 시 입력값이 정해져 있다면 미리 값을 계산하여 캐싱해서 사용하고, 절댓값 계산 시 삼항 연산자를 정의하여 사용하며, 벡터 연산 시 스칼라 계산을 먼저 수행한 후 벡터 연산을 처리하고, 나눗셈보다는 곱셈을 사용하는 것이 효율적입니다.

개발 도구 활용: 최신 IDE와 AI Code Assist는 최적화된 코드 작성에 큰 도움을 주므로 적극적으로 활용해야 합니다.

오디오 최적화

부하 측정 방법: 오디오 부하를 간단히 확인하는 방법은 오디오 재생을 완전히 중단하고 게임을 플레이해보는 것입니다. 이때 볼륨을 0으로 설정하는 것이 아니라 실제 재생 자체가 되지 않도록 처리해야 합니다.

부하 발생 패턴: 대부분의 경우 BGM 재생보다는 효과음이 동시에 다수 재생될 때 부하가 발생할 가능성이 높습니다.

기타 최적화 요소

Target Frame Rate 조절: 로딩 화면에서는 최소한의 프레임을 유지하여 CPU와 GPU에 여유를 주고, 중요한 씬에서는 최대 프레임을 활용할 수 있도록 유동적으로 설정해야 합니다. CPU와 GPU가 지속적으로 고부하 상태에서 작동하면 발열이 발생하고, 온도가 상승할수록 시스템 성능에 제한이 걸리게 됩니다.


GPU Bound 최적화

GPU Bound는 Render Thread Bound와 GPU Bound로 세분화됩니다.

Render Thread Bound 최적화

메인 스레드가 고수준 렌더링 명령을 생성하면 렌더 스레드가 이를 저수준 그래픽스 명령으로 변환하여 GPU 드라이버에 전달합니다. 명령어가 GPU에 도달하기 전 CPU 단계에서 발생하는 부하를 Render Thread Bound라고 합니다.

드로우 콜과 배칭: 렌더링을 최소화하고 그룹화하여 최적화하는 것이 핵심 목표입니다. 동일한 머티리얼을 공유하여 사용함으로써 배칭이 가능하도록 설계해야 합니다.

Static Batching: 움직이지 않는 오브젝트를 Static Batching으로 묶어서 렌더링 호출을 줄일 수 있습니다. 단, 메시가 합쳐지기 때문에 메모리 사용량이 증가한다는 점을 고려해야 합니다.

SRP Batcher: SRP Batcher를 사용할 경우 같은 셰이더를 사용하는 머티리얼을 묶어서 렌더링하므로 Set Pass Call을 효과적으로 줄일 수 있습니다.

GPU Instancing: GPU Instancing을 활용하여 드로우 콜을 대폭 줄일 수 있습니다.

LOD 시스템: LOD를 활용하여 카메라에서 거리에 따라 오브젝트의 세부 표현 수준을 차등화할 수 있습니다.

Occlusion Culling: Occlusion Culling을 사용하여 카메라에 보이지 않는 가려진 영역에 대한 렌더링을 최소화할 수 있습니다.

GPU Bound 최적화

GPU가 렌더링 작업을 완료하는 데 과도한 시간이 소요되는 경우입니다. 렌더링 파이프라인에 따라 CPU가 GPU에게 오브젝트 렌더링 명령(Draw Call)을 전송하면, GPU는 그래픽스 파이프라인에 따라 3D 오브젝트를 2D 화면에 렌더링합니다.

픽셀 셰이더 최적화: 가장 많은 부하가 발생하는 지점은 픽셀 셰이더이며, 해상도에 따라 연산량이 급격히 증가합니다. 필요에 따라 해상도를 낮춰서 렌더링하는 것도 효과적인 방법입니다.

오브젝트 겹침 최적화: 반투명 오브젝트는 겹쳐지는 오브젝트의 개수와 화면에서 차지하는 비율에 비례하여 부하가 증가하므로 이를 고려한 최적화가 필요합니다.

버텍스 LOD: 거리에 따른 세부 표현을 차등화하기 위해 버텍스 LOD를 적용해야 합니다.

텍스처 최적화: 텍스처도 LOD에 따라 밉맵을 사용하고, 텍셀 배치를 통해 품질을 향상시킬 수 있는지 검토해야 합니다.

최적화와 품질의 균형

GPU 최적화와 게임의 시각적 표현 사이에는 적절한 균형이 필요합니다. 우선순위에 따라 한정된 리소스를 효율적으로 분배하는 것이 최적화의 핵심 기준입니다.


결론

게임 최적화는 클라이언트, 아트, 기획, 서버 개발 팀이 각각 독립적으로 수행할 수 없는 종합적인 작업입니다. 팀 간의 긴밀한 협력을 통해 부하가 발생하는 영역을 정확히 파악하고 체계적인 해결 방안을 수립하는 것이 중요합니다.

성공적인 최적화를 위해서는 적극적인 수용과 지속적인 개선 의지를 가져야 합니다. 이러한 태도를 바탕으로 할 때만이 사용자에게 만족스러운 경험을 제공하는 고품질 게임을 개발할 수 있습니다.

 

최적화 체크리스트

Unity 게임 최적화 완벽 가이드

Unity 프로젝트의 성능 분석과 최적화를 위한 체계적인 접근 방법을 정리한 가이드입니다. TACampus의 강의 분석 및 프로젝트 최적화 경험을 바탕으로 실무에서 직접 적용할 수 있는 단계별 체크리스트를 제공합니다.

1. 프로파일링 전 타겟 디바이스 설정

성능 최적화 작업을 시작하기 전에 반드시 타겟 환경을 명확히 정의해야 합니다. 이는 최적화 목표와 방향성을 결정하는 핵심 단계입니다.

플랫폼 결정

현재 개발 중인 프로젝트가 지원해야 하는 주요 플랫폼은 무엇인가요? (복수 선택 가능)

💡 팁: Unity는 크로스 플랫폼 개발에 강하지만, 각 플랫폼의 특성을 이해하고 프로젝트에 적합한 플랫폼을 선택하는 것이 중요합니다.

타겟 디바이스 스펙 분석

💡 팁: 매출 극대화를 위해 저사양 디바이스 지원이 유리하지만, 프로젝트 장르와 특성에 따라 한계가 있습니다. 미래 디바이스 스펙 발전을 지나치게 낙관하지 않는 것이 중요합니다.

성능 목표 설정

2. 프로파일링을 통한 병목 지점 분석

프로파일링 결과를 바탕으로 성능 병목 지점을 진단하고 해결 방안을 찾아봅니다. 병목은 크게 CPU BoundGPU Bound로 나뉩니다.

CPU Bound 최적화 체크리스트

CPU Bound는 메인 스레드와 잡 워커 스레드에서 발생하는 성능 병목을 의미합니다.

1. 가비지 컬렉션 (GC) 최적화

GC Spike는 힙 메모리에 사용하지 않는 메모리가 축적될 때 발생합니다.

다음 해결책 중 적용 가능한 것을 선택하세요 (복수 선택):

2. 물리 처리 최적화

3. UI 시스템 최적화

💡 팁: UI 렌더링에서 드로우 콜을 최소화하고 프레임 디버거를 적극 활용하세요.

4. 애니메이션 최적화

5. 스크립트 최적화

6. 오디오 최적화

7. 기타 최적화 요소

GPU Bound 최적화 체크리스트

GPU Bound는 Render Thread Bound와 실제 GPU Bound로 세분화됩니다.

1. Render Thread Bound 최적화

Render Thread Bound는 CPU가 GPU 드라이버에 명령을 전달하기 전 CPU 단계에서 발생하는 부하입니다.

2. 실제 GPU Bound 최적화

GPU가 렌더링 작업을 완료하는 데 과도한 시간이 소요되는 경우입니다.

최적화와 품질의 균형

결론

게임 최적화는 클라이언트, 아트, 기획, 서버 개발 팀이 각자의 영역에서 독립적으로 수행할 수 없는 종합적인 작업입니다. 팀 간의 긴밀한 협력을 통해 부하가 발생하는 영역을 정확히 파악하고 체계적인 해결 방안을 수립하는 것이 매우 중요합니다.

성공적인 최적화를 위해서는 적극적인 수용과 지속적인 개선 의지를 가져야 합니다. 이러한 태도를 바탕으로 할 때만이 사용자에게 만족스러운 경험을 제공하는 고품질 게임을 개발할 수 있습니다.

참고자료

- https://rito15.github.io/posts/unity-opt-script-optimization/

- https://gdev.tistory.com/56

- https://wlsdn629.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%8C%81

- https://everyday-devup.tistory.com/212

- TACampus 발표 자료