Buffer icon Проекты

Оптимизация 2D игр на Unity

Этот пост будет периодически дополняться и обновляться.
Обновлено 13.10.2021

На Youtube куча уроков по созданию простейших 2D игр на Unity. Реально, сделать неплохой платформер можно за день, при наличии опыта и готовых ассетов. Но многие начинающие игроделы сделав проект и протестировать его на ПК, с ужасом наблюдают как их творение тормозит на мобильном устройстве.

В мануалах, что встречаются в сети, большинство советов собрано к версии Unity 4.6+, кроме того, они почти все на английском, что для некоторых является преградой. В этом посте, я постарался собрать те моменты, которые помогли мне избавится от лагов на iOS и Android. Но важно понимать — не все можно решить лишь настройками, очень важна и архитектура приложения, и подготовленные текстуры, и знание более оптимальных алгоритмов.

Что нужно предпринять, чтобы повысить производительность, поднять FPS, снизить CPU?

1. Кешируем все!

Все, что будет использоваться больше одного раза лучше закешировать. Операции типа GameObject.Find() или GetComponent() достаточно ресурсозатратны, а если они вызываются где-нибудь в цикле или в Update (), то производительность может упасть.

void Awake () {
	_cachedTransform = transform;
}
 
void Update () { 
	_cachedTransform.localPosition = _newPosition;		
}

Не используйте Resources.Load () каждый раз когда нужно в рантайме загрузить что либо, это тоже дорогая операция. Лучше при старте закешировать и работать с кешем. Ну и конечно, объединяйте в префабы.

2. Настройки графики

В Unity есть 6 стандартных пресетов настройки качества графики. Но так как мы говорим про оптимизацию для 2D и для мобильных устройств, то все что выше Simple нет смысла ставить. Конечно, если у вас есть какие-то специфические моменты, частицы, и т. д., то с параметры можно поэкспериментировать найдя оптимальный баланс.

3. Используем FrameRate

По-умолчанию FrameRate равен 30. И зачастую этого бывает достаточно. Но например, при создании UI где есть прокручивающие списки и движущие элементы, может появится дрожание или шлейф. Я это заметил при тестировании на iPhone, поэтому вручную повысил FrameRate. А понижая FrameRate на сценах или игровых меню, где ничего не двигается, можно значительно снизить CPU, а следовательно продлить жизнь батарее устройства. Пользователи не любят когда игра сжирает аккумулятор за час.

public int _frameRate = 60;

void Start () {
	#if UNITY_IOS
		QualitySettings.vSyncCount = 0;
	#endif
}

void Update () {
	#if UNITY_IOS
		if (_frameRate != Application.targetFrameRate)
			Application.targetFrameRate = _frameRate;
	#endif
}

4. Атлас текстур

Определенно, нужно упаковывать все свои спрайты в атласы. Для этого есть как встроенный инструмент Sprite Packer, так и продвинутый TexturePacker. Для работы встроенного упаковщика, достаточно задавать теги в настройках импорта, объединяя текстуры и спрайты по смыслу и месту использования.

Таким образом уменьшается количество вызовов отрисовок ваших спрайтов. Проверить как идет отрисовка можно с помощью встроенного инструмента Frame Debugger.

5. Используем пул объектов

GameObject.Instantiate () — очень дорогая операция! Если есть возможность не использовать ее в процессе игры — не используйте. Для большого количества однотипных объектов надо использовать пул объектов. Один раз инициализировав определенное количество, например пуль, они будут использоваться снова и снова, вместо создания и уничтожения каждый раз. Урок по пулу объектов и готовый шаблон, для начала будет достаточно.

6. Меньше Update — больше событий

Метод Update () вызывается каждый кадр, FixedUpdate () в некоторых случаях еще чаще. Если у вас в этих местах происходит много различных проверок и действий, это можно сказаться на производительности. Я использую событийную модель: вместо проверки условия в Update (), когда происходит какое-либо действие, отправляется событие, а в нужном месте это событие «слушается» и обрабатывается в зависимости от переданных параметров. Для этого можно использовать менеджер событий о котором я писал ранее.

7. Выключаем неиспользуемые компоненты

Деактивируйте объекты которые не попадают в камеру и которые не видно на сцене. Это также повысит производительность. Можно использовать вот такой хак, чтобы автоматически деактивировать объекты которые выходят за определенные границы.

8. Настройки билда

Билд под конкретную платформу, тоже можно оптимизировать. Например, если акселерометр не используется в игре, его лучше вообще отключить. Кроме того, я не использую автовыбор графического API, а задаю его сам убирая все остальные, опять же, если вы не используете какие-то специфические функции из OpenGLES 3.0, а так второй версии вполне хватает, она уже давно протестирована. Включаем статичный и динамический батчинг, а для Android многопоточный рендеринг. Для iOS включаем Script Call Optimization в Fast but no Exceptions, но тут момент — если будет какое-то исключение, то игра крашится.

9. Используем Profiler

Не стоит обделять вниманием профайлер. Обязательно протестируйте свою игру и посмотрите, в какие моменты идет максимальная нагрузка. Эти места нужно оптимизировать в первую очередь. Большинство ответов можно найти на stackoverflow.com или форуме Unity, просто загуглив название метода который тратит больше всего ресурсов.

10. Использование материала для UI Image и SpriteRenderer

Если у вас сложный интерфейс и много компонентов, особенно UI Image, то они существенно будут влиять на FPS. Чтобы повысить производительность, улучшить плавность анимаций, можно применить такой хак: там где вы не используете маски, у картинок нужно задать материал Sprites-Default. Если это сделать вместе с маской, то маска не сработает и получите примерно такой варнинг:

Material Sprites-Default doesn’t have _Stencil property

Чтобы убрать эту ошибку нужно немного изменить стандартный шейдер, сделать новый материал и применить его там где есть маска, тогда все сработает. Ссылка на измененный шейдер.
Цена плавности — повышение CPU :(

11. Уменьшаем размер текстур

Отличная утилита которая позволяет снизить потребления памяти для текстур до 3х раз. Как это работает и ссылка на Github, в статье на Хабре.

12. Практическое руководство по оптимизации Unity игр

Подойдет как для 2D, так и для 3D. Много полезной информации которую в документации вряд ли найдешь. Тут и про инструменты, и про опыт. Рассказывает специалист по эксплуатации из Unity Technologies — очень интересно. Узнал про memory profiler и то, что Camera.main не закеширована О_О. Обязательно смотреть всем.

13. Используем оптимизированный код

Снова хочется посоветовать набор оптимизированных скриптов от Leopotam. Коллекции, сериализация, векторы и многое другое. Настоятельное рекомендую, к изучению и использованию.

14. Используем одинаковые материалы

Если на объектах стоят разные материалы, они не будут батчится и будет больше вызовов отрисовки. Соответственно, нужно по возможности использовать как можно меньше разных шейдеров и материалов. Для понимания, как работает начальная оптимизация графики 2D, на Lynda.com есть небольшой курс.

15. Размеры атласов и спрайтов

Для применения сжатия спрайтов на мобильных устройствах нужно использовать атласы с размерами кратными степени 2, т. е. 1024х1024, 2048х2048.

16. Используйте UI Profiler

В Unity 2017 появился UI Profiler! Крутая штука, теперь можно выяснить почему в интерфейсе много вызовов отрисовки, увидеть какие материалы на объектах и всё это оптимизировать. Особенно актуально, если у вас сложные интерфейсы со множеством элементов, которые например, прокручиваются в ScrollRect.

17. Уголок оптимизатора

На сайте Unity появился специальный раздел посвященный оптимизации — Optimization corner. Там и про UI, и профайлеры, и основные ошибки, в общем, стоит ознакомиться.

18. Несколько советов по оптимизации UI

Раннее уже упоминали про Camera.main, но в новой статье описаны ещё несколько интересных особенностей оптимизации UI.

19. Сборник советов от FunCorp

Хорошая статья про оптимизацию UI. Многое уже описано тут, но есть и замечания по новым компонентам, например TextMeshPro.

20. Оптимизация UI без кода

Статья от Banzai Games про основные способы не делать неправильно Unity UI. Есть и интересные замечания. Зачем только снова про древний текстовый компоненты говорить... Думаю уже все перебрались на TMP.

21. Оптимизация мобильных 3D-проектов

Хорошая статья про оптимизацию в 3D. Многое подойдет и для 2D. Рассмотрены все аспекты и UI, и текстуры, шейдеры, батчинг и т. д.

22. Топ-10 ошибок в оптимизации Unity

Хороший вебинар от юнитеков, рассмотрены частые ошибки совершаемые при оптимизации, обзор инструментов по оптимизации, примеры с пояснениями.

23. Оптимизация игр на Unity: проверенный в деле план

Перевод отличной статьи по оптимизации игр на Unity. Как подготовиться к оптимизации, что лучше делать, что не делать, как правильно использовать профайле и многое другое.

Пока все. Тут не было про физику, потому что пока я ее не использовал. Возможно, в будущем добавлю. Пишите в комментариях ваши проверенные способы оптимизации для 2D игр под Unity.

Поделиться
Отправить
Запинить
Отправить
 13008   2016   android   ios   unity   игры   разработка
7 комментариев
Аркадий 2017

Спасибо за статью, и вопрос, а почему «Не используйте Resources.Load () каждый раз когда нужно в рантайме загрузить что либо, это тоже дорогая операция» вычеркнули?

Игорь Лопатин 2017

Потому что он кешируется.

Артём 2018

Благодарен за статью. Некоторые момент всё ещё актуальны с 2013 года, но всё же есть и достаточно много нового. Хотел бы спросить о нескольких пунктах:
1) Атласы это конечно понятно. Они безусловно нужны. Но что скажите если немного углубится, стоит паковать небольшими порциями для определённого промежутка в проекте, или чисто по тегу? Большие паки скорее всего сразу мимо. Как думаете, какой оптимальный размер для них стоит использовать?
2) Раньше говорили, что стоит обходить стороной скейлинг. Что кардинально увеличивается от этого количество вызовов, поэтому лучше оставлять дефолт (1.1.1). А что скажите по поводу сейчас?
3) Приведу слова одного человека с хабра: «Принять за правило: папка Resources — должна быть пуста в принципе. Это не хранилище контента для всего проекта. Если нужно что-то инстанцировать по имени и не обойтись явным указанием префаба — только тогда туда складывать нужный ассет.» Какого Ваше мнение по данном поводу в текущих реалиях?

Игорь Лопатин 2018

Рад что статья ещё кому-то помогает!
По поводу атласов могу сказать, что многое зависит от конкретной игры и архитектуры. Конечно, не разумно всё упаковывать в один атлас. Например, есть смысл паковать для определенной сцены. Со скейлингом думаю ничего не изменилось — разные размеры не будут батчиться, как объекты с разными материалами — соответственно больше вызовов отрисовки. С папкой Resources, согласен с автором, нет смысла туда класть все ассеты, для «а возможно я что-то захочу загрузить». Хоть Unity и улучшает производительность немного от версии к версии, всё-таки архитектуру нужно думать заранее. Кстати, у автора этой цитаты есть оптимизированный хелпер для загрузки ресурсов :)

Артём 2018

Спасибо за ответ. Оптимизация проектов всегда нужна. Это мы может с треша начинать, но если брать действительно то, что рассчитано на коммерцию, мы должны сразу понимать что надо абы наш клон *любая известная игра* запускался хотя бы на бухгалтерских калькуляторах.
А когда мы это один человек, сразу понимаешь что лучше делать фундамент железобетонный, иначе времени на переделывания может и не быть.

P/S Если будете ещё обновлять статью, и если не трудно конечно, можете указать оптимальные количества отрисованных треугольников/вершин, и количество вызовов? Думаю, иметь информацию на что равняться — было бы неплохо.

Leopotam 2018

@Артём

Как думаете, какой оптимальный размер для них стоит использовать?

На текущий момент 40% устройств — это gles2. По стандарту гарантированно поддерживается размер 2048x2048 (2k x 2k). 4k текстуры — это исключения на малом количестве устройств, так делать не нужно. Если помеченные одним тегом текстуры не влезут в 2к атлас — они поедут на вторую «страницу» атласа с таким именем, по сути новую текстуру. Вот чтобы такого не было — нужно из меню запускать пакер спрайтов руками и прогонять упаковку и смотреть результат — все ли влазит в один лист. Если нет — руками разбивать на несколько атласов-тегов.
По поводу группировки правило простое — грузить одновременно как можно меньше ресурсов. Т.е если нам нужны вот эти 100 спрайтов только на одной сцене — их нужно вынести в отдельный атлас, а не класть в общий. Общий интерфейс (кнопки и всякое такое) обычно всегда выносится в отдельный атлас, в котором ничего больше нет — это ускоряет его загрузку и уменьшает потребляемое количество памяти при переключении сцен (неиспользуемые ресурсы автоматически выгружаются).

Leopotam 2018

@Артём

Раньше говорили, что стоит обходить стороной скейлинг. Что кардинально увеличивается от этого количество вызовов, поэтому лучше оставлять дефолт (1.1.1). А что скажите по поводу сейчас?

С 5.х неуниформный положительный скейлинг не ломает батчинг, отрицательный по прежнему запрещен (т.е нельзя таким способом делать поворот спрайта, для этого в виджетах есть галки переворота).

Leopotam 2018

@Артём

Приведу слова одного человека с хабра

Основной смысл в том, что все ассеты, положенные в папку Resources (любого уровня вложенности) безусловно поедут в билд, увеличивая его размер. Безусловно означает, что используются они или нет — они будут присутствовавть в конечном билде. Поэтому все, что можно навешивать в инспекторе и хранить в префабах — следует делать в инспекторе и хранить в префабах вне папки Resources. Если нужно динамическое создание на лету по какой-то кастомной логике именования префабов — можно использовать Resources.

Кирилл 2020

А где же ссылка на правильный шейдер (п. 10), который можно использовать с маской?

Игорь Лопатин 2020

Ссылка битая стала, действительно, спасибо за коммент. Найду и исправлю.

Популярное