무엇을 찾고 계신가요?
Hero background image
고급 프로그래밍 및 코드 아키텍처
코드 아키텍처를 살펴보고 그래픽 렌더링을 더욱 최적화하세요. 이 글은 Unity 프로젝트를 위한 최적화 팁을 소개하는 시리즈 중 네 번째 글입니다. 더 적은 리소스로 더 높은 프레임 속도로 실행하기 위한 가이드로 활용하세요. 이러한 모범 사례를 시도해 본 후에는 시리즈의 다른 페이지도 확인해 보세요: 더 강력한 성능을 위한 Unity 프로젝트 구성 하이엔드 그래픽을 위한 성능 최적화 PC 및 콘솔 게임의 GPU 사용량 관리 원활한 게임 플레이를 위한 물리 성능 향상
유니티 플레이어루프 이해하기

유니티 플레이어루프에는 게임 엔진의 핵심과 상호 작용하는 함수가 포함되어 있습니다. 이 구조에는 초기화 및 프레임별 업데이트를 처리하는 여러 시스템이 포함되어 있습니다. 모든 스크립트는 이 플레이어루프에 의존하여 게임플레이를 생성합니다. 프로파일링 시 플레이어 루프 아래에 프로젝트의 사용자 코드가 표시되며 에디터 컴포넌트는 에디터 루프 아래에 있습니다.

Unity 프레임루프의 실행 순서를 이해하는 것이 중요합니다. 모든 Unity 스크립트는 미리 정해진 순서대로 여러 이벤트 함수를 실행합니다. 성능을 강화하기 위해 스크립트의 수명 주기를 생성하는 깨우기, 시작, 업데이트 및 기타 기능의 차이점을 알아보세요.

예를 들어 리지드바디를 다룰 때 업데이트 대신 고정 업데이트를 사용하거나 게임을 시작하기 전에 변수나 게임 상태를 초기화할 때 시작 대신 깨우기를 사용하는 것이 있습니다. 이를 사용하여 각 프레임에서 실행되는 코드를 최소화할 수 있습니다. Awake는 스크립트 인스턴스의 수명 동안 한 번만 호출되며 항상 시작 함수 전에 호출됩니다. 즉, 다른 객체와 대화하거나 초기화된 상태에서 쿼리할 수 있는 객체를 처리할 때는 시작을 사용해야 합니다.

이벤트 함수의 구체적인 실행 순서는 스크립트 수명 주기 순서도를 참조하세요.

사용자 지정 업데이트 관리자 다이어그램
사용자 지정 업데이트 관리자 구축

프로젝트에 까다로운 성능 요구 사항(예: 오픈월드 게임)이 있는 경우 업데이트, 늦은 업데이트 또는 고정 업데이트를 사용하여 사용자 지정 업데이트 관리자를 만드는 것을 고려하세요.

업데이트 또는 후기 업데이트의 일반적인 사용 패턴은 특정 조건이 충족될 때만 로직을 실행하는 것입니다. 이로 인해 이 조건을 확인하는 것 외에는 사실상 코드를 실행하지 않는 프레임별 콜백이 많이 발생할 수 있습니다.

Unity는 Update 또는 LateUpdate와 같은 메시지 메서드를 호출할 때마다 인터롭 호출, 즉 C/C++ 측에서 관리되는 C# 측으로 호출을 수행합니다. 개체 수가 적은 경우에는 문제가 되지 않습니다. 오브젝트가 수천 개에 달하면 이 오버헤드가 심각해지기 시작합니다.

콜백이 필요한 경우 활성 개체를 이 업데이트 관리자에 구독하고, 필요하지 않은 경우 구독을 취소하세요. 이 패턴을 사용하면 모노비헤이비어 객체에 대한 많은 인터롭 호출을 줄일 수 있습니다.

구현 예시는 게임 엔진별 최적화 기법을 참조하세요.

모든 프레임에서 실행되는 코드 최소화

코드가 매 프레임마다 실행되어야 하는지 고려하세요. 업데이트, 늦은 업데이트 및 고정 업데이트에서 불필요한 로직을 제거할 수 있습니다. 이러한 Unity 이벤트 함수는 매 프레임마다 업데이트해야 하는 코드를 넣을 수 있는 편리한 위치이지만, 해당 빈도로 업데이트할 필요가 없는 로직을 추출할 수도 있습니다.

상황이 변경될 때만 로직을 실행하세요. 특정 함수 서명을 트리거하기 위해 이벤트 형태의 관찰자 패턴과 같은 기술을 활용하는 것을 잊지 마세요.

업데이트를 사용해야 하는 경우 n프레임마다 코드를 실행할 수 있습니다. 이는 과중한 작업 부하를 여러 프레임에 분산하는 일반적인 기술인 타임 슬라이싱을 적용하는 한 가지 방법입니다.

이 예제에서는 3프레임에 한 번씩 ExampleExpensiveFunction을 실행합니다.

비결은 다른 프레임에서 실행되는 다른 작업과 인터리빙하는 것입니다. 이 예제에서는 Time.frameCount % 간격 == 1 또는 Time.frameCount % 간격 == 2일 때 다른 고비용 함수를 '예약'할 수 있습니다.

또는 사용자 지정 업데이트 관리자 클래스를 사용하여 n 프레임마다 구독된 개체를 업데이트할 수 있습니다.

고비용 함수의 결과 캐시

2020.2 이전 Unity 버전에서는 게임 오브젝트 찾기, 게임 오브젝트 가져오기, 카메라 메소드 호출에 비용이 많이 들 수 있으므로 업데이트 메서드에서 호출하지 않는 것이 좋습니다.

또한 자주 호출되는 경우 값비싼 메서드를 OnEnableOnDisable에 배치하지 않도록 하세요. 이러한 메서드를 자주 호출하면 CPU가 급증할 수 있습니다.

가능하면 다음과 같은 고가의 함수를 실행하지 마십시오. MonoBehaviour.AwakeMonoBehaviour.Start와 같은 비싼 함수를 초기화 단계에서 실행하세요. 필요한 참조를 캐시하여 나중에 재사용하세요. 스크립트 오더 실행에 대한 자세한 내용은 Unity PlayerLoop의 이전 섹션을 참조하세요.

다음은 반복되는 GetComponent 호출의 비효율적인 사용을 보여주는 예시입니다:

void Update()
{
Renderer myRenderer = GetComponent<Renderer>();
ExampleFunction(myRenderer);
}

대신 함수 결과가 캐시되므로 GetComponent를 한 번만 호출하세요. 캐시된 결과는 GetComponent를 추가로 호출하지 않고도 업데이트에서 재사용할 수 있습니다.

이벤트 함수의 실행 순서에 대해 자세히 알아보세요.

빈 Unity 이벤트 및 디버그 로그 문 방지

로그 문(특히 업데이트, 후기 업데이트 또는 수정 업데이트에서)은 성능을 저하시킬 수 있으므로 빌드하기 전에 로그 문을 비활성화하세요. 이 작업을 빠르게 수행하려면 조건부 속성 를 전처리 지시어와 함께 사용하는 것이 좋습니다.

예를 들어 아래와 같이 사용자 정의 클래스를 만들 수 있습니다.

사용자 지정 클래스로 로그 메시지를 생성합니다. 플레이어 설정 > 스크립팅 기호 정의에서 ENABLE_LOG 전처리기를 비활성화하면 모든 로그 기록이 한꺼번에 사라집니다.

문자열과 텍스트를 처리하는 것은 Unity 프로젝트에서 성능 문제의 일반적인 원인입니다. 그렇기 때문에 로그 문과 그 값비싼 문자열 형식을 제거하면 잠재적으로 큰 성능 향상을 가져올 수 있습니다.

마찬가지로 빈 모노비헤이비어에는 리소스가 필요하므로 빈 Update 또는 LateUpdate 메서드를 제거해야 합니다. 이러한 방법을 테스트에 사용하는 경우 전처리기 지시어를 사용하세요:

#if UNITY_EDITOR
void Update()
{
}
#endif

여기서 에디터 내 업데이트를 사용하면 빌드에 불필요한 오버헤드 없이 테스트할 수 있습니다.

10,000번의 업데이트 호출에 대한 이 블로그 게시물에서는 Unity가 Monobehaviour.Update를 실행하는 방법을 설명합니다.

스택 추적 로깅 비활성화

플레이어 설정의 스택 추적 옵션을 사용하여 표시되는 로그 메시지 유형을 제어할 수 있습니다. 애플리케이션이 릴리스 빌드에서 오류 또는 경고 메시지를 로깅하는 경우(예: 야생에서 충돌 보고서를 생성하는 경우) 스택 추적을 비활성화하여 성능을 개선하세요.

스택 추적 로깅에 대해 자세히 알아보세요.

문자열 매개변수 대신 해시 값 사용

유니티는 내부적으로 애니메이터, 머티리얼, 셰이더 프로퍼티를 지정할 때 문자열 이름을 사용하지 않습니다. 속도를 위해 모든 속성 이름은 속성 ID로해시되며, 이 ID는 속성 주소를 지정하는 데 사용됩니다.

애니메이터, 머티리얼 또는 셰이더에서 Set 또는 Get 메서드를 사용할 때는 문자열 값 메서드 대신 정수 값 메서드를 활용하세요. 문자열 값 메서드는 문자열 해싱을 수행한 다음 해싱된 ID를 정수 값 메서드로 전달합니다.

사용 Animator.StringToHash 를 애니메이터 프로퍼티 이름에 사용하고 Shader.PropertyToID 를 머티리얼 및 셰이더 프로퍼티 이름으로 사용합니다.

프레임당 수천 번 반복할 때 성능에 영향을 미치는 데이터 구조의 선택도 이와 관련이 있습니다. 올바른 구조를 선택하기 위한 일반적인 지침으로 C#의 데이터 구조에 대한 MSDN 가이드를 따르세요.

오브젝트 풀 스크립트 인터페이스
개체 풀링

인스턴스화파괴는 가비지 컬렉션 (GC) 스파이크를 생성할 수 있습니다. 이는 일반적으로 느린 프로세스이므로 게임 오브젝트를 정기적으로 인스턴스화하고 파괴하는 대신(예: 총에서 총알을 쏘는 것) 재사용 및 재활용할 수 있는 미리 할당된 오브젝트 풀을 사용하세요.

메뉴 화면이나 로딩 화면과 같이 CPU 스파이크가 눈에 띄지 않는 게임 내 시점에 재사용 가능한 인스턴스를 생성하세요. 컬렉션으로 이 개체 '풀'을 추적하세요. 게임 플레이 중에 필요할 때 다음 사용 가능한 인스턴스를 활성화하고 오브젝트를 파괴하는 대신 비활성화하여 풀에 반환하기만 하면 됩니다. 이렇게 하면 프로젝트에서 관리되는 할당 수가 줄어들고 GC 문제를 방지할 수 있습니다.

마찬가지로 런타임에 컴포넌트를 추가하는 것은 피하세요. 추가 컴포넌트를 호출하면 약간의 비용이 발생합니다. Unity는 런타임에 컴포넌트를 추가할 때마다 중복 또는 기타 필수 컴포넌트가 있는지 확인해야 합니다. 원하는 컴포넌트가 이미 설정된 상태에서 프리팹을 인스턴스화하면 성능이 향상되므로 오브젝트 풀과 함께 사용하세요.

관련해서 트랜스폼을 이동할 때는 Transform.SetPositionAndRotation 를 사용하여 위치와 회전을 한 번에 업데이트하세요. 이렇게 하면 트랜스폼을 두 번 수정해야 하는 오버헤드를 피할 수 있습니다.

런타임에 게임 오브젝트를 인스턴스화해야 하는 경우, 최적화를 위해 부모를 지정하고 위치를 변경하려면 아래를 참조하세요.

Object.Instantiate에 대한 자세한 내용은 스크립팅 API를 참조하세요.

여기에서 Unity에서 간단한 오브젝트 풀링 시스템을 만드는 방법을 알아보세요.

스크립트 가능한 개체 풀
스크립터블 오브젝트의 강력한 기능 활용하기

변경되지 않는 값이나 설정은 모노비헤이비어 대신 스크립터블 오브젝트에 저장합니다. 스크립터블 오브젝트는 프로젝트 내부에 존재하는 에셋입니다. 한 번만 설정하면 되며, 게임 오브젝트에 직접 연결할 수 없습니다.

스크립터블 객체에 필드를 만들어 값이나 설정을 저장한 다음, 모노비헤이비어에서 스크립터블 객체를 참조합니다. 스크립터블 오브젝트의 필드를 사용하면 해당 모노비헤이비어로 오브젝트를 인스턴스화할 때마다 불필요한 데이터 중복을 방지할 수 있습니다.

스크립터블 오브젝트 소개 튜토리얼을 시청하고 여기에서 관련 문서를 찾아보세요.

유니티 키 아트 21 11
무료 전자책 받기

가장 포괄적인 가이드 중 하나로, PC 및 콘솔용 게임을 최적화하는 방법에 대한 80개 이상의 실행 가능한 팁을 모았습니다. 유니티의 성공 및 가속화 솔루션 전문 엔지니어가 작성한 이 심층적인 팁은 Unity를 최대한 활용하고 게임 성능을 향상하는 데 도움이 될 것입니다.

이 콘텐츠가 마음에 드셨나요?