Увлекательное путешествие в поисках лучшей производительности на мобильных устройствах

Введение

Я делаю ремейк своей старой игры Loca Deserta: Sloboda Первая версия была написана на Flutter, но сейчас в качестве движка для игры я выбрал Unity.

Я начал работу с нуля и реализовал множество новых функций, но заметил, что даже мой Galaxy S21 Ultra подлагивал во время игры. FPS был ровным, но иногда мне казалось, что он падает с 60 до 30.

Я взял очень старую Нокию 6.1 с андроидом, запустил свою игру и был в шоке. Это был полный трэш! FPS колебался в диапазоне от 1 до 15. Играть не возможно.

TLDR Что было не так

  • Слои ландшафта.

  • Слишком много тяжелой логики в MonoBehaviour:Update.

  • Вызовы Instantiate.

  • Вызовы Destroy.

Как я искал узкие места

Конечно, я сразу же запустил профайлер и начал искать, что заставляло так тормозить мою Nokia 6.1/Galaxy Note 8.

Вы можете видеть, что игра выдавала максимум 30 FPS, но зачастую и того меньше.

Кроме того, когда мне нужно было показать требования к строительству/производству с помощью 3D-моделей ресурсов:

скорость резко падала до 40-60 мс на кадр - узким местом была моя логика OnTriggerEnter (подробнее об этом позже). Также обратите внимание на синие тайминги, которые появляются, когда на экране отображаются новые ресурсы. С моим кодом определенно было что-то не так.

Первый подозреваемый — тени?

Сначала я думал, что все дело в тенях. Я отключил их... и ничего не изменилось. Игра также тормозила.

Второй подозреваемый — настройки качества?

Я пытался запустить ее на низких/очень низких настройках. Стало лучше, но все же 15-30 FPS для такой простой игры — ну нет!

Разделяй и властвуй

Я решил использовать научный подход «разделяй, властвуй и гадай, что не так». Что-то в сцене определенно нагружало GPU (поскольку 80% моего процессорного времени было потрачено на ожидание результатов GPU).

Удаление ландшафта

Начал я с самого большого ассета: я удалил слой Terrain (мой ландшафт).

БУМ! Стабильные 60 кадров в секунду на Galaxy Note 8! Ха-ха! Вот оно:

Я вернул все обратно — снова 15-30... Определенно ландшафт вызвал у меня проблемы с производительностью.

Как исправить ландшафт на мобильных устройствах

Итак, я прочитал официальную документацию по TerrainLayer и заметил следующее:

вы можете использовать четыре слоя Terrain на проход текстуры, без ограничения на количество проходов.

Это означает, что хоть вам разрешено использовать столько слоев Terrain, сколько вы хотите, каждый проход увеличивает время затрачиваемое на рендеринг ландшафта.

В целях максимальной производительности, ограничьте каждый из ваших ландшафтных тайлов до четырех слоев Terrain.

А у меня было около 8 слоев, смешанных с разными альфа-каналами и масками!

И действительно, когда я сделал 4 слоя, как на этом скрине:

Игра стала очень выдавать очень плавную картинку даже на древнем Android на Nokia 6.1:

Как только я добавляю еще один слой (даже не используя его для рисования!), то производительность сразу падает.

Результат для Terrain

Не делайте более 4 слоев ландшафта, иначе Unity будет очень сильно тормозить на их обработке.

Всплески в MonoBehaviour:Update

А это было легко исправить. Оказалось, что я использовал DateTime.now, чтобы получить текущее время для обработки таймеров задач в игре. Похоже, что его использование может быть не так оптимально на мобильных телефонах, поэтому я переписал логику с использованием Time.deltaTime, чтобы проверить, сколько времени прошло с момента запуска задачи Upgrade/Production:

void Update()
{
    if (actionStarted)
    {
        elapsedActionTime += Time.deltaTime;
        if (elapsedActionTime > actionDuration)
        {
            actionStarted = false;
            elapsedActionTime = 0.0f;
            finalizeAction();
        }
    }
}

Советую вам посмотреть это замечательное видео о том, как реализовать таймеры в Unity:

После исправления обратите внимание, что синие тайминги стали намного короче!

Избавьтесь от Instantiate и Destroy

Когда мне нужно показать материалы для сборки/производства, я интсанцировал префабы и визуализировал их на экране. Затем, когда игрока не было рядом со зданием, я их уничтожил.

Оказывается... Instantiate и Destroy — очень ресурсоемкие процедуры.

Видите эти всплески? Это вызовы Instantiate/Destroy. Некоторые из них занимают 40-70 мс:

Я знал, как исправить это с помощью пулинга объектов: пулинг объектов (подробно) — Шаблоны игрового программирования в Unity и C# — Джейсон Вейманн

Я заново реализовал логику для заблаговременного создания всех объектов, когда загружается сцена. А затем просто вытаскивал их из пула объектов и размещал там, где нужно.

Посмотрим на тот же сценарий игры, чтобы отобразить новые объекты ресурсов на экране, но теперь с пулингом объектов:

Заключение

  • Не используйте более 4 слоев ландшафта.

  • Не используйте Instantiate/Destroy слишком часто.

  • Используйте объединение пулы объектов, где это возможно.

  • Не выполняйте тяжелые задачи в Update.

Моя игра теперь выдает 60 FPS на Samsung S21 Ultra и 30-40 FPS на Galaxy Note 8 и Nokia 6.1.

Наконец-то я могу продолжить добавлять новые здания в игру :)

Это моя игра на Nokia 6.1: https://twitter.com/DmytroGladkyi/status/1481540410098991104

Узнать больше и скачать мою старую игру Sloboda можно здесь: Loca Deserta: Sloboda

Вы также можете присоединиться к моему Telegram-каналу: https://t.me/locadesertachumaki. Или https://t.me/locadesertachumaki/467.


Скоро в OTUS пройдет открытое занятие «Проектирование игровых систем», на котором участники научатся проектировать архитектуру игры, а также узнают, из каких компонентов и событий состоит игровая система, и как написать ее таким образом, чтобы это было гибко и масштабируемо. Регистрация для всех заинтересованных по ссылке.

Комментарии (3)


  1. supremestranger
    11.03.2022 00:45

    Еще стоит включить инстансинг у террейна, если не включен.


    1. Leopotam
      11.03.2022 01:29

      Который поможет только если лоды будут совпадать.


  1. RalphMirebs
    11.03.2022 12:09
    -2

    Так вот почему, при создании куба размером 100х100х100 во вложенном цикле из примитивов Cube (1000000 примитивов) через Instantiate Юнити задумывался на длительное время.