Это пятая статья из серии, в которой рассказывается о советах по оптимизации для ваших проектов Unity. Используйте их как руководство для работы с более высокой частотой кадров при меньших ресурсах. Попробовав эти лучшие практики, обязательно ознакомьтесь с другими страницами этой серии:
- Настройка проекта Unity для повышения производительности
- Оптимизация производительности для высококлассной графики
- Управление использованием графического процессора в играх для ПК и консолей
- Продвинутое программирование и архитектура кода
Физика позволяет создать сложный игровой процесс, но за это приходится платить производительностью. Узнав эти расходы, вы сможете настроить симуляцию так, чтобы управлять ими соответствующим образом. Используйте эти советы, чтобы не превысить заданную частоту кадров и создать плавное воспроизведение с помощью встроенной в Unity физики (NVIDIA PhysX).
Меши, используемые в физике, проходят через процесс, называемый Cooking. Это подготавливает сетку, чтобы она могла работать с физическими запросами, такими как лучи, контакты и так далее.
MeshCollider имеет несколько CookingOptions, чтобы помочь вам проверить сетку на физику. Если вы уверены, что вашей сетке не нужны эти проверки, вы можете отключить их, чтобы ускорить время приготовления.
В CookingOptions для каждого MeshCollider снимите флажки EnableMeshCleaning, WeldColocatedVertices и CookForFasterSimulation. Эти опции важны для процедурно генерируемых сеток во время выполнения, но их можно отключить, если в ваших сетках уже есть нужные треугольники.
Если вы нацелены именно на ПК, убедитесь, что включена опция Use Fast Midphase. На средней стадии симуляции он переключается на более быстрый алгоритм из PhysX 4.1 (который помогает сузить небольшой набор потенциально пересекающихся треугольников для запросов к физике). Для недесктопных платформ по-прежнему используется более медленный алгоритм, генерирующий R-деревья.
Коллайдеры сетки, как правило, дороги, поэтому для приближения к исходной форме замените более сложные коллайдеры сетки на примитивные или упрощенные.
Подробнее в документации по CookingOptions.
Если вы генерируете сетки процедурно во время игры, вы можете создать Mesh Collider во время выполнения. Однако добавление компонента Mesh Collider непосредственно к сетке приводит к тому, что физика выполняется в основном потоке. Это может потребовать значительного времени процессора.
Используйте Physics.BakeMesh, чтобы подготовить сетку для использования с Mesh Collider и сохранить запеченные данные вместе с самой сеткой. Новый Mesh Collider, ссылающийся на эту сетку, будет использовать эти предварительно запеченные данные, а не запекать сетку заново. Это поможет сократить время загрузки сцены или время инстанцирования в дальнейшем. Чтобы оптимизировать производительность, вы можете переложить приготовление сетки на другой поток с помощью Система заданий C#.
Подробнее о том, как запекать сетки в нескольких потоках, см. в этом примере.
В настройках игрока установите флажок Prebake Collision Meshes, когда это возможно. Мы также рекомендуем просмотреть настройку матрицы столкновений, чтобы убедиться, что объекты игрока и игровой механики находятся в правильных слоях.
Удаление обратных вызовов из триггеров для ненужных слоев может принести большую пользу, поэтому постарайтесь упростить свою матрицу столкновений слоев. Вы можете редактировать настройки физики через Настройки проекта > Физика.
Более подробную информацию можно найти в документации по матрице столкновений.
Физические движки работают с фиксированным временным интервалом. Чтобы увидеть фиксированную ставку, по которой работает ваш проект, перейдите в меню Редактировать > Настройки проекта > Время.
Поле Fixed Timestep определяет временную дельту, используемую каждым шагом физики. Например, значение по умолчанию 0,02 секунды (20 мс) эквивалентно 50 кадрам в секунду (fps), или 50 Гц.
Поскольку каждый кадр в Unity занимает разное количество времени, он не идеально синхронизирован с симуляцией физики. Двигатель отсчитывает время до следующего физического таймстепа. Если кадр проходит немного медленнее или быстрее, Unity использует прошедшее время, чтобы знать, когда запускать симуляцию физики на нужном Timestep.
Если подготовка кадра занимает много времени, это может привести к проблемам с производительностью. Таким образом, если в вашей игре происходит скачок (например, инстанцирование множества GameObject'ов или загрузка файла с диска), на выполнение кадра может уйти 40 мс или больше. При установленном по умолчанию фиксированном временном шаге 20 мс это приведет к тому, что две симуляции физики будут запущены на следующем кадре, чтобы "догнать" переменный временной шаг.
Дополнительные симуляции физики, в свою очередь, увеличивают время обработки кадра. На младших платформах это может привести к снижению производительности.
Последующий кадр, требующий больше времени на подготовку, также увеличивает отставание в подготовке симуляторов физики. Это приводит к еще более медленным кадрам и большему количеству симуляций, выполняемых на один кадр. Результат - ослабление производительности.
В конечном итоге время между обновлениями физики может превысить максимально допустимый временной интервал. После этого Unity начинает отказываться от обновления физики, и игра замирает.
Чтобы избежать проблем с производительностью при работе с физикой:
- Уменьшите частоту моделирования: Для низкопроизводительных платформ увеличьте значениеFixed Timestep до значения, немного превышающего целевую частоту кадров. Например, используйте 0,035 секунды для 30 кадров в секунду на мобильных устройствах. Это поможет предотвратить падение производительности.
- Уменьшите максимально допустимый временной интервал: При использовании меньшего значения (например, 0,1 секунды) можно пожертвовать некоторой точностью моделирования физики, но также ограничить количество обновлений физики, которые могут происходить в одном кадре. Экспериментируйте со значениями, чтобы найти то, что подходит для требований вашего проекта.
- При необходимости смоделируйте шаг физики вручную: Отключите опцию Auto Simulation в настройках физики и напрямую вызывайте Physics.Simulate на этапе обновления кадра. Это позволяет вам активно определять время выполнения шага "Физика". Передайте Time.deltaTime в Physics.Simulate, чтобы физика синхронизировалась со временем симуляции.
- Примечание: Такой подход может привести к нестабильности в симуляции физики - особенно в сценах со сложной физикой или сильно меняющимся временем кадров. Используйте его с осторожностью.
Более подробную информацию можно найти в документации Physics.Simulate.
Физический движок Unity работает в два этапа:
- Широкая фаза: Собирает потенциальные столкновения с помощью алгоритма Sweep and Prune.
- Узкая фаза: Когда движок действительно вычисляет столкновения
Широкая фаза, установленная по умолчанию в Sweep and Prune Broadphase(Edit > Project Settings > Physics > Broadphase Type), может генерировать ложные срабатывания для миров, которые в целом плоские и имеют много коллайдеров. Если ваша сцена большая и в основном плоская, избегайте этой проблемы и перейдите на автоматическую обрезку ящиков или многоярусную обрезку Broadphase. Эти опции делят мир на сетку, где каждая ячейка сетки выполняет Sweep и Prune.
Multibox Pruning Broadphase позволяет указать границы мира и количество ячеек сетки вручную, а Automatic Box Pruning рассчитает все за вас.
Полный список объектов физики смотрите здесь.
Если вы хотите более точно смоделировать конкретное физическое тело, увеличьте его Rigidbody.solverIterations. Это переопределяет значение Physics.defaultSolverIterations, которое можно найти через Edit > Project Settings > Physics > Default Solver Iterations.
Чтобы оптимизировать моделирование физики, установите относительно низкое значение параметра defaultSolveIterations в проекте. Затем примените более высокие пользовательские значения Rigidbody.solverIterations к отдельным экземплярам, нуждающимся в более подробном описании.
Получите дополнительную информацию о Rigidbody.solverIterations.
Когда вы обновляете трансформацию, Unity не синхронизирует ее с физическим движком автоматически. Unity накапливает трансформации и ждет либо выполнения обновления физики, либо вызова пользователем функции Physics.SyncTransforms.
Если вы хотите синхронизировать физику с вашими трансформациями чаще, вы можете установить Physics.autoSyncTransform в True (также находится в Project Settings > Physics > Auto Sync Transforms). Если эта опция включена, все жесткие тела и коллайдеры на этом трансформировании, а также их дочерние элементы автоматически обновляются вместе с трансформированием.
Однако если в этом нет крайней необходимости, отключите его. Серия последовательных запросов к физике (например, к лучам) может привести к снижению производительности.
Узнайте больше о Physics.SyncTransforms.
События столкновения и триггера (OnCollisionEnter, OnCollisionStay, OnCollisionExit, OnTriggerEnter, OnTriggerStay, OnTriggerExit) сработает для любого MonoBehaviour, реализующего эти функции и удовлетворяющего критериям взаимодействия. Эти события также будут отправлены отключенным MonoBehaviours.
Поэтому рекомендуется реализовывать эти функции только в случае необходимости, так как пустые, несущественные функции будут вызываться. Особое внимание следует уделить OnCollisionStay и OnTriggerStay, поскольку они могут быть вызваны несколько раз в зависимости от количества задействованных коллайдеров.
Обратные вызовыMonoBehaviour.OnCollisionEnter, MonoBehaviour.OnCollisionStayи MonoBehaviour.OnCollisionExit также принимают в качестве параметра экземпляр столкновения. Этот экземпляр коллизии выделяется в управляемой куче и должен быть собран в мусор.
Чтобы уменьшить количество генерируемого мусора, включите Physics.reuseCollisionCallbacks(находится в меню Project Settings > Physics > Reuse Collision Callbacks). При этом Unity назначает каждому обратному вызову только один экземпляр пары столкновений. Это снижает нагрузку на сборщик мусора (GC) и повышает общую производительность.
Примечание: Если вы ссылаетесь на экземпляр столкновения вне Collision Callbacks для постобработки, вы должны отключитьReuse Collision Callbacks.
Подробнее о Physics.reuseCollisionCallbacks.
Статические коллайдеры - это объекты GameObject с компонентом Collider, но без Rigidbody. Вопреки своему названию, вы можете перемещать статический коллайдер.
Просто измените положение физического тела, накопите изменения положения и синхронизируйте их перед обновлением физики. Вам не нужно добавлять компонент Rigidbody к статическому коллайдеру, чтобы просто переместить его. Но если вы хотите, чтобы статический коллайдер взаимодействовал с другими физическими телами более сложным образом, создайте для него кинематическое жесткое тело. Используйте Rigidbody.position и Rigidbody.rotation для перемещения вместо обращения к компоненту Transform. Это гарантирует более предсказуемое поведение физического движка.
Примечание: В 2D-физике не перемещайте статические коллайдеры, потому что перестройка дерева занимает много времени.
Получите дополнительную информацию о жестких телах.
Чтобы обнаружить и собрать коллайдеры на определенном расстоянии, в определенном направлении, используйте лучевые передачи и другие физические запросы, например BoxCast.
Запросы по физике, возвращающие несколько коллайдеров в виде массива, например OverlapSphere или OverlapBoxнеобходимо выделять эти объекты в управляемой куче. Это означает, что сборщик мусора в конечном итоге должен собрать выделенные объекты, что может снизить производительность, если это произойдет в неподходящий момент.
Чтобы уменьшить эти накладные расходы, используйте версии этих запросов с NonAlloc. Например, если вы используете OverlapSphere для сбора всех потенциальных коллайдеров вокруг точки, обязательно используйте OverlapSphereNonAlloc. Это позволяет передавать массив коллайдеров (параметр resultsparameter) для работы в качестве буфера.
Метод NonAlloc работает без генерации мусора. В противном случае он функционирует как соответствующий метод выделения. Только не забудьте определить буфер результатов достаточного размера при использовании метода NonAlloc. Буфер не увеличивается, если в нем закончилось место.
Более подробную информацию можно найти в документации по методу NonAlloc.
Хотя вы можете выполнять запросы к лучам с помощью Physics.Raycastэто может занять значительное количество процессорного времени. Это особенно актуально при большом количестве операций с лучами (например, при расчете линии видимости для 10 000 агентов).
Используйте RaycastCommand для пакетного выполнения запроса с помощью системы заданий C#. Это снимает нагрузку с главного потока, так что лучи могут передаваться асинхронно и параллельно.
Смотрите пример в документации RaycastCommands.
Используйте окно Physics Debugger(Window > Analysis > Physics Debugger) для устранения проблемных коллайдеров или несоответствий. В этом окне отображается цветовой индикатор игровых объектов, которые могут столкнуться друг с другом.
Дополнительные сведения см. на странице визуализации Physics Debug.
Одно из самых полных наших руководств, в котором собрано более 80 действенных советов по оптимизации игр для ПК и консолей. Эти подробные советы, созданные нашими экспертами в области Success и Accelerate Solutions, помогут вам получить максимальную отдачу от Unity и повысить производительность вашей игры.