Unity передовые методы программирования
Существуют некоторые уникальные особенности среды программирования Unity, которые требуют дополнительного внимания при написании кода по сравнению со стандартными C#/.NET проектами. Ниже приведено резюме ключевых вопросов, которые следует учитывать при написании кода для Unity приложений, вместе с лучшими практиками, чтобы помочь вам избежать общих ловушек.
Unity Жизненный цикл объекта и ссылки
При написании C# в Unity, будьте осторожны при сравнении объектов на равенство с другими объектами или с нулем. Для типов, наследующих от UnityEngine.Object, Unity использует пользовательскую версию C# операторы равенства и неравенства. Это означает проверку нуля myGameObject == null может оценить true (и наоборот myGameObject != null может оценить false) даже если myGameObject технически обладает действительным C# Дополнительные сведения о особенностях этого поведения см. в разделе «Сравнение объектов». Операторы равенства.
Настраиваемое поведение равенства и жизненный цикл объекта Unity имеют несколько последствий для вашего кода:
- В обстоятельствах, когда вы хотите быть уверены в исключении уничтоженных объектов из вашей проверки, убедитесь, что вы используете
if (obj == null), а неReferenceEqualsдля объектов Unity. - В тех случаях, когда вы хотите проверить наличие нулевых ссылок на C#, используйте
ReferenceEqualsили сначала перенесите вSystem.Object. - При сравнении двух объектов Unity на равенство, помните, что
obj1 == obj2может возвращатьtrue, даже если обе ссылки являются разными объектами C#, если один или оба были уничтожены и воссозданы, например черезUndo. - Пользовательский оператор равенства медленнее, чем стандартный C#. Обычно это не проблема, но помните об этом при масштабировании и в горячих путях.
- Не кэшируйте компоненты через разгрузки сцен без охраны, потому что они могут быть уничтожены, но остаются как объекты оболочки C# без неуправляемого аналога.
- Не храните сильные ссылки на крупные ассеты в статических полях: такие ссылки сохраняются между сценами и мешают выгрузке.
- При уничтожении объектов,
Object.DestroyImmediateдоступны только Редактору, так что используйтеObject.Destroyво время выполнения и оставьте Unity запланировать уничтожение.
Избегайте C# финализаторов
Не используйте C# финализаторы в коде времени выполнения, по следующим причинам:
- Они работают на отдельных потоках финализатора, и Unity APIs обычно требуют главного потока.
- Они работают недетерминистически, что приводит к непредсказуемому поведению.
- Они могут вообще не запускаться, если только приложение не собирает мусор и не ждет.
- Исключения, возвращаемые финализаторами, могут привести к остановке приложения, если они не обрабатываются специально.
- Само их существование увеличивает накладные расходы мусорщиков.
Накладные расходы и ассигнования на сбор мусора
Одним из наиболее важных рисков производительности, о которых следует помнить в Unity приложениях, является код выполнения, который выделяет память и увеличивает нагрузку сборщика мусора, особенно в горячих путях.
Чтобы избежать этого, примените следующие методы кодирования:
- Избегайте выделения по каждому кадру путем кэширования и повторного использования списков и использования не выделяющих версий методов, где это возможно. Это включает в себя кэширование
WaitForSecondsи других инструкций вывода при использовании сопутствующих процедур. - По возможности, используйте не выделяющие версии методов и выполняйте дорогостоящие операции, такие как
GameObject.GetComponentвAwakeи кэшируйте ссылки на возвращаемые объекты, вместо того, чтобы повторять вызов изUpdate. - Избегайте использования LINQ во время выполнения кода, особенно в контексте
UpdateилиFixedUpdateи других горячих путей. Методы из пространства именSystem.Linqмогут создавать ненужные распределения и включать boxing и closure. - Избегайте повторяющихся строковых операций, таких как конкатенация.
- Выявление и предотвращение случаев отражения. Дополнительную информацию см. в Предотвращение C# отражения над головой.
Более подробные инструкции и примеры этих проблем см. в Оптимизация кода для управляемой памяти.
Сведения о отслеживании и сокращении нагрузки сборщика мусора см. в Управляемая память.
MonoBehaviour Оптимизация цикла обновления
Традиционный подход во многих проектах Unity — использовать компоненты-скрипты MonoBehaviour, чтобы регулярно обновлять состояние игры через встроенные обратные вызовы, такие как MonoBehaviour.Update, MonoBehaviour.FixedUpdateи MonoBehaviour.LateUpdate, которые обычно запускаются много раз в секунду.
Это простая модель, которая может хорошо работать при правильном использовании, но у нее есть некоторые ключевые риски производительности, которые обычно поражают неопытных разработчиков:
- Реализация по умолчанию Unity на кадр или других регулярных функций событий может плохо масштабироваться.
Updateфункции влекут небольшие накладные расходы на внутреннее управление Unity и взаимодействие с нативным слоем. Когда таких скриптов MonoBehaviour много, суммарные издержки могут заметно сказаться на производительности. - Тот факт, что встроенные обновления запускаются очень часто, делает их горячим кодовым путем и увеличивает эффект любых неэффективных, памятоемких операций, которые вы размещаете в них. Общей неправильной схемой неопытных пользователей является наличие многих MonoBehaviour скриптов с
Updateфункциями, которые запускаются без необходимости большую часть времени, или которые ненужны памятоемкими, когда они запускаются.
Для уменьшения этих рисков рассмотрите следующие варианты:
- Для лучшей масштабируемости при большом числе сущностей рассмотрите возможность перевода проекта на архитектуру, ориентированную на данные, с использованием Entity Component System (ECS) Unity.
- Если вы используете архитектуру на основе MonoBehaviour:
- Чтобы убедиться, что вы минимизируете воздействие управляемой памяти в горячих путях, см. Оптимизация кода для управляемой памяти.
- Сократите количество активных функций
Updateс помощью централизованного менеджера обновлений или настройки цикла проигрывателя. Дополнительные сведения см. в разделах Использование пользовательского менеджера обновлений и Настройка цикла проигрывателя. - Имейте в виду, что даже в проекте, основанном на MonoBehaviour, вы часто можете использовать специфические особенности данных-ориентированных систем Unity. Вы можете использовать Jobs, Burst-компилировать секции вашего кода, использовать более эффективные структуры данных, такие как
NativeArrayв критически важных для производительности секциях вашего кода, и выбирать неуправляемые альтернативы управляемым APIs, такие как для операций преобразования.
Безопасность потока
В то время как Unity имеет многопоточность, основное время выполнения является однопоточной и большинство APIs в пространствах имен UnityEngine и UnityEditor могут быть вызваны только из главного потока. Не ссылайтесь на GameObjects, Transforms, Components или asset APIs из фоновых потоков. Никогда не используйте await с Task.Result или Task.Wait на главном потоке, так как это приводит к тупикам.
При работе с по сути асинхронными и долговременными операциями, Unity обеспечивает Awaitable класс как специфичную для Unity альтернативу .NET Task. Awaitable использует группирование объектов для сокращения распределений и знает о специфических для Unity концепциях, таких как Update и FixedUpdate, что позволяет вам await задачи и запланировать их возобновление в определенных точках в цикле Player. Дополнительные сведения см. в Асинхронное программирование с помощью класса Awaitable.
Для более короткой, но более вычислительно интенсивной параллельной работы, Unity обеспечивает система работы, которые могут быть Burst Составлено. Для получения дополнительной информации см. Написать многопоточное программное обеспечение с помощью системы заданий.
Соображения, касающиеся компиляции
Для оптимальной производительности важно думать не только о том, как вы пишете код, но и о том, как он компилируется. Наивная компиляция кода вместо активного определения того, для каких контекстов определенные исходные файлы или области вашего кода актуальны, налагает следующие издержки:
- Увеличение размера сборки, если в нее включен ненужный код.
- Время, затраченное на компиляцию и перекомпиляцию для применения изменений. Это особенно может повлиять на время итерации в Редакторе.
- Потенциальные ошибки во время выполнения, если включен неподходящий код для данной платформы или контекста.
Unity предоставляет несколько механизмов, помогающих вам контролировать, какие части вашего кода компилируются для различных платформ и контекстов:
- Вы можете использовать определения сборок для группировки исходных файлов в сборки, которые могут быть скомпилированы отдельно. Это позволяет изолировать код только для редактора от кода времени выполнения и код для конкретной платформы от межплатформенного кода. Дополнительные сведения см. в Организация скриптов в сборки.
- Вы можете использовать
#ifдирективы с скриптовые символы исключить определенные области кода из компиляции в зависимости от целевой платформы или контекста. Например, защищайте код только для редактора за#if UNITY_EDITORдля исключения его из сборок в режиме выполнения. Дополнительные сведения о различных методах Unity предложения для условного включения или исключения кода, см. Условная компиляция. - Ключевая концепция, специфическая для Unity, заключается в следующем: домен перезагрузка в редакторе при входе в режим Play и выходе из него, а также при перекомпиляции скриптов. Перезагрузка домена занимает время и увеличивает длительность итераций при написании и проверке кода в редакторе. Чтобы ускорить итерации, перезагрузку домена при входе в Play можно отключить, но тогда сбрасывать статическое состояние придётся вручную. Подробнее см. Перезагрузка кода и сцены при входе в режим воспроизведения.
Инструменты анализа Unity
Unity предоставляет множество инструментов, которые помогут вам выявить узкие места и написать более производительный код. Project Ревизор инструмент может проанализировать код вашего проекта, чтобы выявить общие проблемы производительности и предложить исправления. Profiler может помочь вам выявить узкие места производительности во время выполнения в вашем коде, предоставляя подробную информацию об использовании CPU и GPU, выделении памяти и многом другом. Вы также можете создать анализаторы Roslyn для обеспечения стандартов кодирования и выявления проблем производительности, специфичных для вашего проекта.
Дополнительные сведения о создании пользовательских анализаторов Roslyn и генераторов источников см. в Анализаторы Roslyn и генераторы источников.
Для получения дополнительной информации о набор инструментов анализа Unity, см. Оптимизация.