본문 바로가기

Dev/Unity

[Unity] Programming & Code Architecture - 모바일 게임 성능 최적화 팁 (3)

반응형

 

 


 

 

Programming & Code Architecture - 모바일 게임 성능 최적화 팁 (3)

Unity 공식 document인 "Optimize your mobile game performance" 를 정리한 내용입니다.

 

 

1. 프로그래밍 및 코드 아키텍처를 통한 최적화

Unity PlayerLoop에는 게임 엔진의 코어와 상호 작용하기 위한 함수가 포함되어 있다.
이 구조는 초기화와 프레임별 업데이틀르 처리하는 다양한 시스템을 포함한다.

 

모든 Script가 이 PlayerLoop를 활용해서 게임플레이를 생성하게 된다.

Unity의 수명 주기를 기억하자.


프로그래밍 및 코드 아키텍처를 통해 최적화 하는 방법은 다음과 같이 기재되어 있다.

  • Unity PlayerLoop 이해하기
  • 매 프레임에 실행되는 코드 최소화
  • Start/Awake에서 대규모 로직 사용 방지
  • 빈 Unity 이벤트 방지
  • Debug Log 구문 제거
  • 문자열 파라미터 대신 해시 값 사용
  • 올바른 데이터 구조 선택
  • 런타임 시 컴포넌트 추가 방지
  • 게임 오브젝트 및 컴포넌트 캐시하기
  • 오브젝트 풀 사용
  • 스크립터블 오브젝트 사용

 

Unity PlayerLoop 이해하기

모든 Unity Script는 사전에 정해진 순서(상단 수명 주기 참고)대로 여러 이벤트 함수를 실행한다.
Awake, Start, Update 및 Script의 수명 주기를 생성하는 다른 함수들 사이의 차이점을 이해해야 한다.

 

 

매 프레임에 실행되는 코드 최소화 ★★

코드를 작성할 때 반드시 모든 프레임에 실행해야 하는지 확인해야 한다.
불필요한 로직을 Update, LateUpdate, FixedUpdate에서 제외하자.

가능하다면 상황이 바뀌는 경우에만 로직을 실행한다.

 

반드시 Update 함수를 사용해야 한다면 n개 프레임마다 코드를 실행하는 방안을 검토한다.
이는 여러 프레임에 대규모 워크로드를 분산하는 일반적인 기법인 Time Slicing 기법 중 하나이다.

// example code

private int _interval = 3;

void Update()
{
	if(Time.frameCount % _interval == 0)
    {
		Func();
    }
}

 

 

Start / Awake에서 대규모 로직 사용 방지

첫 번째 Scene을 로드하면 아래의 함수가 각 object에 대해 호출된다.

 

Awake

OnEnable

Start

 

Application이 첫 프레임을 렌더링하기 전까지는 이러한 함수에서 비용이 많이 드는 로직 사용을 지양해야 한다.

그렇지 않으면 로딩 시간이 길어질 수 있다.

 

 

빈 Unity Event 방지

비어있는 MonoBehaviours도 리소스를 필요로 하므로 비어 있는 Update or LateUpdate는 제거해야 한다.

 

 

Debug Log 구문 제거

Log 구문, 특히 Update / LateUpdate / FixedUpdate에 있는 Log 구문은 성능을 낮출 수 있기 때문에 빌드 전 제거한다.

 

빌드 전에 제거하는 과정이 번거롭다면 프리 프로세서 지시문과 함께 조건부 속성을 활용해보자.

public static class Logging
{
     [System.Diagnostics.Conditional(“ENABLE_LOG”)]
     static public void Log(object message)
     {
     	UnityEngine.Debug.Log(message);
     }
}

커스텀 클래스로 Log 메시지를 생성한다.
Player Settings에서 ENABLE_LOG 프리 프로세서를 비활성화 하면 모든 Log 구문이 동시에 사라진다.

 

 

문자열 파라미터 대신 해시 값 사용

Unity는 내부적으로 Animator나 Material, Shader 프로퍼티를 식별할 때 문자열 이름을 사용하지 않는다.
빠른 처리를 위해 모든 프로퍼티 이름이 프로퍼티 ID에 해시되어 있고, 이 ID가 실제 프로퍼티 식별에 사용된다.

 

Animator, Material, Shader에서 Set or Get 메소드를 사용할 때는 문자열 값 메소드 대신 정수 값 메소드를 사용하자.

Animator : Animator.StringToHash

Material, Shader : Shader.PropertyToID

 

 

올바른 데이터 구조 선택

한 번 선택한 데이터 구조는 프레임당 수천 번씩 반복되므로 효율성/비효율성에 계속 영향을 준다.올바른 자료 구조 선택을 위해 노력해야 한다.

 

 

런타임 시 컴포넌트 추가 방지

런타임에 AddComponent를 호출하면 비용이 발생한다.
AddComponent 대신 Prefab을 인스턴스화 하는 방법이 일반적으로 더 효과적이다.

 

 

게임 오브젝트 및 컴포넌트 캐시하기

GameObject.Find, GameObject.GetComponent, Camera.main(2020.2 이전)은 비용이 많이 들 수 있으므로 Update 메소드에서 호출하지 말고 Start에서 호출 후 결과를 캐시한다.

 

즉 다음과 같은 코드를 지양하고

void Update()
{
     MyClass myClass = GetComponent<MyClass>();
     ExampleFunction(myClass);
}

 

아래의 코드처럼 활용한다.

private MyClass _myClass;

void Start()
{
 	_myClass = GetComponent<MyClass>();
}

void Update()
{
 	ExampleFunction(_myClass);
}

 

 

오브젝트 풀 사용

Instantiate 함수 및 Destroy 함수는 가비지와 GC 스파이크를 야기하며 일반적으로 속도가 느린 프로세스이다.
게임 오브젝트를 계속 인스턴스화하고 삭제하기보다는(예시: 총알 발사) 재사용 및 재활용할 수 있는 사전에 할당된 오브젝트 풀을 사용한다.

 

 

스크립터블 오브젝트 사용

변하지 않는 값 또는 설정 MonoBehaviour가 아닌 Scriptable Object에 저장한다.
Scriptable Object는 한 번만 설정하면 되는 프로젝트 내부의 에셋으로 Game Object에 직접 연결할 수 없다.

 

Scriptable Object에서 필드를 생성하고 값 또는 설정을 저장한 다음 MonoBehaviours 에서 Scriptable Object를 참조한다.

 

 

 

 

 

 


 

cpdm

 

choppadontbiteme.tistory.com

 

반응형