무엇을 찾고 계신가요?
Hero background image

고급 프로그래밍 및 코드 아키텍처

코드 아키텍처를 탐색하여 그래픽 렌더링을 더욱 최적화하세요. 이 글은 Unity 프로젝트에 대한 최적화 팁을 풀어내는 시리즈의 네 번째 기사입니다. 더 적은 자원으로 더 높은 프레임 속도로 실행하기 위한 가이드로 사용하세요. 이러한 모범 사례를 시도한 후에는 시리즈의 다른 페이지도 확인하세요: 더 강력한 성능을 위한 Unity 프로젝트 구성 고급 그래픽을 위한 성능 최적화 PC 및 콘솔 게임을 위한 GPU 사용 관리 부드러운 게임 플레이를 위한 향상된 물리 성능
이 웹페이지는 이해를 돕기 위해 기계 번역으로 제공됩니다. 기계 번역으로 제공되는 콘텐츠에 대한 정확도나 신뢰도는 보장되지 않습니다. 번역된 콘텐츠의 정확도에 관해 의문이 있는 경우 웹페이지의 공식 영어 원문을 참고해 주시기 바랍니다.

Unity PlayerLoop 이해하기

Unity PlayerLoop은 게임 엔진의 핵심과 상호작용하는 기능을 포함합니다. 이 구조는 초기화 및 프레임별 업데이트를 처리하는 여러 시스템을 포함합니다. 모든 스크립트는 게임 플레이를 생성하기 위해 이 PlayerLoop에 의존합니다. 프로파일링할 때, Editor 구성 요소가 EditorLoop 아래에 있는 프로젝트의 사용자 코드를 볼 수 있습니다.

Unity의 execution orderFrameLoop을 이해하는 것이 중요합니다. 모든 Unity 스크립트는 미리 정해진 순서로 여러 이벤트 함수를 실행합니다. 성능을 강화하기 위해 스크립트의 수명주기를 생성하는 Awake, Start, Update 및 기타 함수의 차이를 배우십시오.

일부 예로는 FixedUpdate를 사용하여 Rigidbody를 처리하거나 게임이 시작되기 전에 변수를 초기화하기 위해 Awake를 사용하는 것이 있습니다. 각 프레임에서 실행되는 코드를 최소화하는 데 사용하십시오. Awake는 스크립트 인스턴스의 수명 동안 한 번만 호출되며 항상 Start 함수보다 먼저 호출됩니다. 이는 Start를 사용하여 다른 객체와 상호작용할 수 있는 객체를 처리하거나 초기화된 객체를 쿼리해야 함을 의미합니다.

이벤트 함수의 특정 실행 순서를 보려면 Script lifecycle flowchart를 참조하십시오.

사용자 정의 업데이트 관리자 다이어그램

사용자 정의 업데이트 관리자 만들기

프로젝트에 성능 요구 사항이 높은 경우(예: 오픈 월드 게임), Update, LateUpdate 또는 FixedUpdate를 사용하여 사용자 정의 업데이트 관리자를 만드는 것을 고려하십시오.

Update 또는 LateUpdate의 일반적인 사용 패턴은 특정 조건이 충족될 때만 논리를 실행하는 것입니다. 이로 인해 이 조건을 확인하는 것 외에는 실제로 코드를 실행하지 않는 여러 프레임별 콜백이 발생할 수 있습니다.

Unity가 Update 또는 LateUpdate와 같은 메시지 메서드를 호출할 때마다, C/C++ 측에서 관리되는 C# 측으로의 호출인 인터롭 호출을 수행합니다. 소수의 객체에 대해서는 문제가 되지 않습니다. 수천 개의 객체가 있을 때 이 오버헤드는 상당해지기 시작합니다.

콜백이 필요할 때 이 업데이트 관리자에 활성 객체를 구독하고 필요하지 않을 때 구독 해제하십시오. 이 패턴은 Monobehaviour 객체에 대한 많은 인터롭 호출을 줄일 수 있습니다.

구현 예를 보려면 Game engine-specific optimization techniques을 참조하십시오.

매 프레임마다 실행되는 코드를 최소화하세요.

코드가 매 프레임마다 실행되어야 하는지 고려하십시오. 불필요한 로직을 Update, LateUpdate 및 FixedUpdate에서 제거할 수 있습니다. 이 Unity 이벤트 함수는 매 프레임마다 업데이트해야 하는 코드를 넣기에 편리한 장소이지만, 그 빈도로 업데이트할 필요가 없는 로직은 추출할 수 있습니다.

사물이 변경될 때만 로직을 실행하세요. 특정 함수 시그니처를 트리거하기 위해 이벤트 형태의 옵저버 패턴과 같은 기술을 활용하는 것을 기억하세요.

Update를 사용해야 하는 경우, 코드를 매 n 프레임마다 실행할 수 있습니다. 이것은 시간 분할을 적용하는 한 가지 방법으로, 여러 프레임에 걸쳐 무거운 작업 부하를 분산하는 일반적인 기술입니다.

이 예제에서는 예제 비싼 함수을 매 3프레임마다 한 번 실행합니다.

요령은 이를 다른 프레임에서 실행되는 다른 작업과 교차하여 수행하는 것입니다. 이 예제에서는 Time.frameCount % interval == 1 또는 Time.frameCount % interval == 2일 때 다른 비싼 함수를 "예약"할 수 있습니다.

또는 사용자 정의 Update Manager 클래스를 사용하여 구독된 객체를 매 n 프레임마다 업데이트하세요.

비용이 많이 드는 함수의 결과를 캐시하세요.

Unity 버전 2020.2 이전에서는 GameObject.Find, GameObject.GetComponent, 및 Camera.main이 비쌀 수 있으므로 Update 메서드에서 호출하지 않는 것이 좋습니다.

또한, 자주 호출되는 경우 OnEnableOnDisable에 비싼 메서드를 배치하는 것을 피하세요. 이 메서드를 자주 호출하면 CPU 스파이크에 기여할 수 있습니다.

가능한 한 MonoBehaviour.AwakeMonoBehaviour.Start와 같은 비싼 함수를 초기화 단계에서 실행하세요. 필요한 참조를 캐시하고 나중에 재사용하세요. 스크립트 순서 실행에 대한 자세한 내용은 Unity PlayerLoop에 대한 이전 섹션을 확인하세요.

여기 반복된 GetComponent 호출의 비효율적인 사용을 보여주는 예가 있습니다:

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

대신, 함수의 결과가 캐시되므로 GetComponent를 한 번만 호출하세요. 캐시된 결과는 추가적인 GetComponent 호출 없이 Update에서 재사용할 수 있습니다.

이벤트 함수의 실행 순서에 대해 더 읽어보세요.

빈 Unity 이벤트와 디버그 로그 문을 피하세요.

로그 문(특히 Update, LateUpdate 또는 FixedUpdate에서)은 성능을 저하시킬 수 있으므로 빌드를 만들기 전에 로그 문을 비활성화하세요. 이를 빠르게 수행하려면 조건부 속성과 함께 전처리 지시문을 만드는 것을 고려하세요.

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

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

문자열 및 텍스트 처리 는 Unity 프로젝트에서 성능 문제의 일반적인 원인입니다. 그래서 로그 문과 그 비싼 문자열 형식을 제거하는 것이 큰 성능 향상이 될 수 있습니다.

마찬가지로 빈 MonoBehaviour도 리소스를 요구하므로 빈 Update 또는 LateUpdate 메서드를 제거해야 합니다. 테스트를 위해 이러한 메서드를 사용하는 경우 전처리 지시문을 사용하세요:

#if UNITY_EDITOR
void Update()
{
}
#endif

여기에서 불필요한 오버헤드가 빌드에 포함되지 않도록 에디터 내에서 테스트를 위해 Update를 사용할 수 있습니다.

이 블로그 게시물은 10,000 Update 호출이 Unity가 MonoBehaviour.Update를 실행하는 방법을 설명합니다.

스택 추적 로깅 비활성화

로그 메시지의 유형을 제어하려면 플레이어 설정에서 스택 추적 옵션을 사용하세요. 응용 프로그램이 릴리스 빌드에서 오류 또는 경고 메시지를 기록하는 경우(예: 실제에서 충돌 보고서를 생성하기 위해) 성능을 개선하기 위해 스택 추적을 비활성화하세요.

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

문자열 매개변수 대신 해시 값을 사용하세요.

Unity는 내부적으로 애니메이터, 재질 또는 셰이더 속성에 문자열 이름을 사용하지 않습니다. 속도를 위해 모든 속성 이름은 속성 ID로 해시되고 이러한 ID가 속성을 참조하는 데 사용됩니다.

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

애니메이터 속성 이름에는 Animator.StringToHash를 사용하고 재질 및 셰이더 속성 이름에는 Shader.PropertyToID를 사용하세요.

관련된 것은 데이터 구조의 선택으로, 이는 프레임당 수천 번 반복할 때 성능에 영향을 미칩니다. C#에서 올바른 구조를 선택하기 위한 일반 가이드로 MSDN 가이드를 따르세요.

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

객체를 풀링하세요.

인스턴스화파괴가비지 수집 (GC) 스파이크를 생성할 수 있습니다. 이는 일반적으로 느린 프로세스이므로, 게임 오브젝트를 정기적으로 인스턴스화하고 파괴하기보다는 (예: 총에서 총알을 발사하는 경우) 재사용 및 재활용할 수 있는 미리 할당된 객체의 을 사용하세요.

게임의 특정 시점, 예를 들어 메뉴 화면이나 로딩 화면에서 재사용 가능한 인스턴스를 생성하세요. 이때 CPU 스파이크가 덜 눈에 띕니다. 이 “풀” 객체를 컬렉션으로 추적하세요. 게임 플레이 중에는 필요할 때 다음 사용 가능한 인스턴스를 활성화하고, 파괴하는 대신 객체를 비활성화한 후 풀로 되돌리세요. 이것은 프로젝트에서 관리되는 할당의 수를 줄이고 GC 문제를 방지할 수 있습니다.

유사하게, 런타임에 구성 요소를 추가하는 것을 피하세요; AddComponent 호출은 비용이 발생합니다. Unity는 런타임에 구성 요소를 추가할 때마다 중복 또는 다른 필수 구성 요소를 확인해야 합니다. 원하는 구성 요소가 이미 설정된 프리팹을 인스턴스화하는 것이 더 성능이 좋습니다, 따라서 이를 객체 풀과 결합하여 사용하세요.

관련하여, 변환을 이동할 때는 Transform.SetPositionAndRotation을 사용하여 위치와 회전을 한 번에 업데이트하세요. 이것은 변환을 두 번 수정하는 오버헤드를 피합니다.

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

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

Unity에서 간단한 객체 풀링 시스템을 만드는 방법을 여기에서 배우세요.

스크립터블 객체 풀

ScriptableObjects의 힘을 활용하세요.

MonoBehaviour 대신 ScriptableObject에 변경되지 않는 값이나 설정을 저장하세요. ScriptableObject는 프로젝트 내에 존재하는 자산입니다. 한 번만 설정하면 되며, GameObject에 직접 연결할 수 없습니다.

ScriptableObject에서 값을 저장할 필드를 생성한 다음, MonoBehaviour에서 ScriptableObject를 참조하세요. ScriptableObject의 필드를 사용하면 MonoBehaviour로 객체를 인스턴스화할 때마다 데이터의 불필요한 중복을 방지할 수 있습니다.

ScriptableObjects 소개 튜토리얼을 시청하고 관련 문서를 여기에서 찾아보세요.

unity 키 아트 21 11
무료 전자책 받기

우리의 가장 포괄적인 가이드 중 하나는 PC와 콘솔을 위한 게임 최적화 방법에 대한 80개 이상의 실행 가능한 팁을 모았습니다. 전문가인 Success 및 Accelerate Solutions 엔지니어가 만든 이 심층 팁은 Unity를 최대한 활용하고 게임 성능을 향상시키는 데 도움이 될 것입니다.