Как оптимизировать игру в unity под андроид

от admin

Практическое руководство по оптимизации для мобильных

Это руководство предназначено для новичков в мобильном геймдеве. Для тех, кто испытывает трудности при планировании и прототипировании новой мобильной игры (или портировании уже существующего проекта). Также этот раздел будет полезен в качестве справки для каждого, кто делает мобильные или браузерные игры (с целевой платформой — старые ПК или нетбуки).

Оптимизация вообще широкая тема, и то как вы ее сделаете, целиком зависит от вашей игры, поэтому данное руководство следует рассматривать как некое введение или ссылку, а не пошаговое руководство.

Мобильные устройства не созданы одинаковыми

Информация здесь предполагает аппаратное обеспечение на уровне чипсета Apple A4, который используется в оригинальных iPad, iPhone 3GS и третьем поколении iPod Touch. Из Android предполагается устройство подобное Nexus One, или большинства устройств, работающих на Android 2.3 Gingerbread. В основном, эти устройства были выпущены в начале 2010 года. Эти устройства старее, медленнее современных, но так как они составляют большую часть рынка, их также следует поддерживать.

Есть очень быстрые и очень медленные телефоны. Вычислительные мощности мобильных устройств растут с потрясающей скоростью. Для нового поколения мобильной GPU, быть в 5 раз быстрее своего предшественника — обычное дело. Скорость мобильных устройств уже сравнима со скоростью ПК.

Для обзора технических характеристик мобильных устройств от Apple, см. Hardware.

Если вы хотите разрабатывать под мобильные устройсва, которые станут известными в будущем, или эксклюзивные high end устройства прямо сейчас, вы можете это сделать. См. Мобильные устройства будущего.

Очень низкая производительность (например, iPhone 3G или первое и второе поколение iPod touches) требует особого внимания к оптимизации. В противном случае могут возникнуть проблемы когда покупатели, не обновившие устройства, будут покупать ваши приложения. Если же вы делаете бесплатное приложение, можно не беспокоится о поддержке старых устройств.

Оптимизацию не следует считать последней стадией разработки проекта

Британский ученый Майкл А. Джексон часто цитируется своими Правилами оптимизации программ:

_Первое правило оптимизации программы: не делаете ее. Второе правило оптимизации программы (только для экспертов!): не делайте ее пока что.

Он обосновал это тем, что учитывая рост скорости компьютеров, ваша программа будет достаточно быстрой. Кроме того, если вы попытаетесь слишком много оптимизировать, то сильно усложните код, ограничите себя и создадите много ошибок.

Однако, если вы разрабатываете мобильные игры, есть еще одно мнение: аппаратное обеспечение, представленное сейчас на рынке, сильно ограничено по сравнению с компьютерами, которые мы используем для работы. Поэтому высок риск того, что ваша ваша игра не будет работать на большинстве устройств и оптимизацию рекомендуют делать с самого начала разработки.

В данном руководстве мы постараемся указать ситуации, когда оптимизация сыграет большую роль в производительности, по сравнению с обратными ситуациями, когда оптимизация большого значения не имеет.

Оптимизация: Не только для программистов

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

  • На художника ложится большая ответственность. Если дизайн игры предполагает атмосферность и освещение, их можно нарисовать в текстурах вместо запекания.
  • Каждый раз, когда что-либо может быть запечено, художники могут готовить контент для выпекания, вместо рендеринга в реальном времени. Это позволяет им игнорировать технические ограничения и работать свободно.

Планируйте игру так, чтобы во время исполнения она работала “плавно”.

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

Профилируйте на ранней стадии и почаще

Профилирование важно, потому что оно поможет выяснить, какие оптимизации действительно приведут к большому приросту производительности, а какие являются пустой тратой вашего времени. Благодаря тому, что рендеринг обрабатывается на отдельном чипе (GPU), отрисовка одного кадра занимает в два раза меньше времени (только GPU, а не CPU + GPU). Это означает, что если CPU замедляет работу, оптимизация ваших шейдеров вообще не повысит частоту кадров, и если GPU замедляет работу, не помогут оптимизация физики и скриптов.

Часто бывает так, что разные части игры и разные ситуации работают по разному, так что одна часть игры может привести к 100 миллисекундным кадрам полностью из скрипта, а другая может привести в замедлению игры, потому что в данный момент что нибудь рендерится. Поэтому, если вы собираетесь оптимизировать свою игру, нужно по крайней мере выявить узкие места.

Внутренний Профайлер

Профайлер в Unity в основном используется при ориентации на iOS и Android. См. Руководство по профайлеру для основных инструкций по его использованию.

Внутренний Профайлер

Внутренний профайлер выкидывает текст каждые 30 кадров. Это поможет вам выяснить, какие аспекты вашей игры замедляют ее, будь то физика, скрипты, визуализация, но без множества деталей (например, только название скрипта или визуализации).

См. Встроенный Профайлер для подробной информации о том, как это работает и включается.

Оптимизация 2d-приложений для мобильных устройств в Unity3d

Недавно наша студия завершила разработку большого обновления — Captain Antarctica: Endless Run — для устройств на iOs. Кропотливая работа над обновлением затронула производительность, которая оказалась очень низкой на слабых устройствах. Я боролся с этим целую неделю и добился как минимум 30 FPS, а также значительного сокращения размера приложения. Хочу рассказать, как я это сделал, ну и как делать не стоит.
Статья пригодится любым разработчикам на Unity (причем не только менеджерам проектов и техническим специалистам, но и просто программистам, художникам и дизайнерам), потому что она затрагивает как оптимизацию на Unity в целом, так и конкретно оптимизацию 2d-приложений для мобильных устройств.

Возможно все!

Начну с того, что каждый раз, когда я приступаю к оптимизации, я поначалу не верю, что можно что-то еще оптимизировать, особенно если проект уже прошел несколько циклов оптимизации до этого. Но просмотр официальной документации Unity, тем на форумах, статей в Интернете наводит меня на мысль о новых возможных улучшениях. Таким образом я веду специальный список, в котором записаны основные идеи по тому, что можно оптимизировать в проекте на Unity, постоянно обновляю его и первым делом обращаюсь к нему, когда речь заходит об оптимизации. Этими идеями я хочу с Вами поделиться. Надеюсь, статья поможет Вам сделать Ваш проект намного более шустрым.
Сразу обозначу, что разработка велась на Unity 3.5.6, целевая платформа — устройства Apple от iPhone 3GS и новее.

Базовые правила

Для начала приведу несколько правил, которыми я пользуюсь при разработке и оптимизации.
1. Не оптимизируйте заранее.
Это золотое правило должно быть знакомо всем, кто когда-либо занимался оптимизацией. Вспомним правило 80/20: 80% пользы получается от 20% работы.

Цифры довольно условны, но я имел ввиду вот что: наиболее вероятно, что большая часть оптимизаций, которые Вы собираетесь сделать на начальном этапе проекта, скорее всего вообще никак не повлияет на конечный проект в целом.

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

  • Допустим, есть код, или конструкция, которую вы уже сейчас знаете, как написать лучше, с меньшей затратой по памяти/процессора и тд. Вы об этом знаете по своему опыту, потому что не раз оптимизировали компоненты подобного рода, и знаете, что это приводит к увеличению производительности. Так почему бы уже сейчас не написать ее правильно? Обычно такие вещи складываются в свод правил типа «как правильно писать код, и как писать не надо», и грамотный программист постоянно им пользуется во избежание ошибок в будущем. Нечто подобное имеет место для художников, дизайнеров и тд.
  • Если есть действие, которое будет повторяться много раз, и с самого начала можно его оптимизировать, почему бы это не сделать сразу. Дальше все будет идти автоматом, и не нужно будет исправлять одну и ту же вещь несколько раз. Сюда относятся, например, вспомогательные скрипты, помогающие дизайнерам ускорить однообразную работу с группой объектов.
Что мне помогло решить проблему с производительностью?

Еще до прогона через профайлер было очевидно, что слабое место — в кол-ве Draw Calls. В среднем сцена выдавала порядка 70 DrawCalls, что для устройств уровня iPad1 и ниже является фатальным. Нормально для них — 30-40 Draw Calls. Посмотреть кол-во Draw Calls можно прямо в редакторе в окне Game->Stats:.

Кол-во Draw Calls, показываемых в редакторе, совпадает с таковым на конечных устройствах. Профайлер это подтвердил. Вообще, очень полезно смотреть эту статистику, и не только программистам, но и дизайнерам, для нахождения «тугих» мест в игре.
В наших сценах плохо работало группирование нескольких Draw Calls для одного и того же материала в один Draw Call. Это называется Dynamic Batching. Я начал рыть на тему «как понизить кол-во Draw Calls и улучшить их группирование». Ниже перечислены основные правила, придерживаясь которых, можно получить приемлемое количество Draw Calls. Вот те, которые мне очень сильно помогли:

1. Использовать атласы для комбинирования нескольких текстур в одну большую.
На самом деле важнее даже, чтобы спрайты/модели использовали не то что бы одну общую текстуру, а скорее один общий материал. Именно в кол-ве различных материалов измеряется кол-во Draw Calls (в идеальном случае). Поэтому у нас в проекте используемые изображения всегда объединены в атласы, разбитые на категории: объекты, используемые на всех сценах, объекты GUI, задний фон и тд. Вот пример такого атласа:

Такое разбиение так же будет полезно в будущем для применения к текстурам различных настроек. Но об этом позже.

2. Не стоит изменять Transform->Scale.
Объекты с измененным Scale попадают в отдельную категорию, увеличивающую кол-во Draw Calls. Я заметил это, когда еще раз проходился по документу Draw Call Batching. Вот что значит перечитывать;) Пройдясь по сцене, я обнаружил огромное количество таких объектов:

  • Оказалось, что дизайнеры уже давно увеличили некоторые часто используемые объекты в 1.2 раза через Scale прямо в прифабе объекта. В итоге, мы пришли к решению увеличить их размер прямо в текстуре. Это к тому же соблюдало условие пиксель-в-пиксель, что очень важно для 2d-игры.
  • Были объекты, которые имели одно и то же изображение, но разный Scale. Для таких объектов был написан специальный скрипт, который переводил нужный Scale с Transform прямо на меш, используемый для спрайта, т.е. менял размер меша и оставлял Scale = (1, 1, 1).
  • Также Scale часто использовался у нас для отражения объекта, например, Scale.x = -1 отражает объект слева направо. Все такие скейлы были заменены на соответствующие им повороты.
  • Еще у некоторых объектов Scale был изменен в анимации, пару раз неоправданно. Не забывайте проверять анимации, часто изменения в них — неявные, и могут быть обнаружены только после запуска.

Еще несколько подсказок (взятых в том числе из документа Unity), как сократить количество Draw Calls:

  • Статические объекты могут быть помечены как Static. Тогда будет использоваться Static Batching (только в Pro-версии), который тоже поможет сократить кол-во Draw Calls.
  • Старайтесь использовать объекты с одним и тем же материалом на одном и том же расстоянии от камеры. Пример: у нас различия по расстоянию в 10 юнитов уже давали 1-2 дополнительных Draw Call. При этом какая-то особая закономерность выявлена не была, но я подозреваю, что есть связь между размерами камеры, размерами объектов, их расстоянием до камеры и количеством Draw Calls. Экспериментируйте!
  • Старайтесь, чтобы объекты с разными материалами не перекрывали друг друга. Это тоже увеличивает кол-во Draw Call, особенно для полупрозрачных объектов.
  • Многопроходные (multi-pass) шейдеры увеличивают количество Draw Call. У нас таких не было, но полезно будет учесть это в будущем.
  • Каждая система частиц дает 1 Draw Call. (Имеется ввиду старая система частиц Unity 3.5.6, используемая нами по сей день. Как обстоят дела в Unity 4, я не знаю). Поэтому если на экране одновременно N систем частиц — это автоматически как минимум N Draw Calls. Обычно, одного и того же эффекта можно достигнуть разными способами, в том числе меньшим числом как систем частиц, так и частиц в системе. Часто видел, как начинающие дизайнеры эффектов используют огромное кол-во частиц (и огромный размер самих частиц), чтобы создать вау-эффект. При этом они не думают о производительности (особенно учитывая, что все это делается в редакторе на PC) и обычно достаточно меньшего количества частиц, чтобы достичь того же эффекта.
Скачки производительности

Второй фактор, влияющий на производительность — так называемые скачки производительности. Порою в игре были «зависания» на 0,5-1 секунду, что конечно же было неприемлемо и напрямую влияло на геймплей. Причем такое наблюдалось даже на самых последних устройствах.
И в этом случае помог профайлер! Вот список правил для уменьшения скачков производительности:

1. Старайтесь не использовать Instantiate(), особенно для сложных объектов.
Скачки производительности приходились в основном на вызовы Instantiate(), которые создавали новые объекты из прифабов, или клонировали существующие. Причем некоторые объекты были очень громоздкими, что и повлияло на время их создания. Вместо этого пришлось переписать систему так, чтобы объекты использовались заново. Т.е. состояние объекта после окончания использования (или перед использованием) приводилось к начальному. Это также помогло сократить объем используемой памяти (так как на новые объекты больше не нужно было новой памяти) и количество вызовов Destroy().

2. Минимизируйте количество вызовов Destroy().
Destroy (особенно для больших объектов) почти всегда приводит к манипуляциям с памятью. А это обычно плачевно сказывается на производительности. Это правило напрямую связано с правилом выше, ибо вызовы Instantiate()/Destroy() обычно связаны. Таким образом, использование объектов заново лишило необходимости уничтожать их.

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

4. Минимизируйте вызовы Object.Find().
Думаю, не стоит объяснять, что время этой операции зависит от кол-ва объектов на сцене. Сюда же относятся функции типа GetComponent().

5. Минимизируйте вызовы Resources.UnloadUnusedAssets() и GC.Collect().
Unity иногда сама прибегает к ним, если недостаточно памяти для загрузки нового ресурса или пришел запрос от ОС освободить неиспользуемую память. Таким образом, первые 2 правила автоматически сокращают кол-во таких вызовов. Лучшее место для вызова Resources.UnloadUnusedAssets вручную — перед загрузкой сцены или непосредственно сразу после ее запуска. Это также поможет освободить дополнительную память для сцены, что иногда бывает критично. Соответствующий скачок производительности можно скрыть, например, экраном загрузки;)

Использование правил выше привело к устранению скачков производительности и намного более плавным геймплею и картинке.

Другие оптимизации

Далее привожу другие правила, которые могут помочь Вам. Большинством из них я сам пользовался на предыдущих этапах оптимизации.

Скрипты
  • Не используйте GetComponent<>() в Update, FixedUpdate и других подобных функциях. Вместо этого лучше кэшировать компонент в Awake() или Start(). Если объектов, использующих скрипт, очень много, такое кэширование может значительно сократить время работы скрипта.
  • Кэшируйте встроенные компоненты типа transform, renderer и тд. Особенно если они используются в функциях типа Update(). Ибо каждый такой вызов делается через GetComponent(). См. правило выше. Это можно сделать, например, так:
  • Используйте Vector2(3,4).sqrMagnitude вместо magnitude.
  • Используйте Color32 и Mesh.colors32 вместо Color и Mesh.colors при доступе к цветам меша.
  • Можно использовать OnBecameVisible/OnBecameInvisible для скриптов, которые могут быть отключены, когда камера больше не видит объект.
  • Используйте встроенные массивы. Имеются ввиду массивы типа T[]. Они намного быстрее всех остальных коллекций.
  • Отключите логи при работе приложения на устройстве. Обычно печать лога требует доступа к файловой системе, а если логов много, это может привести к печальным последствиям для производительности.
  • Отключите исключения. Как следствие — старайтесь не использовать их в коде. Отключение исключений поможет сохранить до 30% производительности. Но не забывайте, что если ошибка произойдет, и она не сможет быть обработана исключением — это падение приложения на устройстве. Поэтому приходится выбирать — либо хорошее тестирование и прирост производительности, либо надежность, но производительность чуть хуже. В случае, когда производительность удовлетворяет, преимущество лучше отдать второму варианту.
Физика
  • Чем меньше одновременно активных Rigidbody, тем лучше. Деактивируйте неиспользуемые.
  • Сокращайте количество FixedUpdate в единицу времени. Fixed Time = 0.03333 гарантирует физику со скоростью 30 вычислений в секунду, что обычно очень неплохо. Даже 20 может быть приемлемым, что соответствует Fixed Time = 0.05.
  • Минимизируйте использование Continuous или Dynamic collision detection. Они очень ресурсозатратны. Для 2d-игр они обычно не нужны.
Анимации
  • Следите за количеством анимированных объектов на экране. Чем меньше — тем лучше.
  • Вместо нескольких простых анимаций на сложном объекте, можно сделать одну сложную, делающую то же самое.
  • Можно попытаться уменьшить Sample Rate у клипа. Особенно если анимаций очень много.
  • Используйте Culling Type = Based On Renderers или Based On User Bounds. Тогда анимация будет проигрываться только тогда, когда объект виден на экране.

    Если это, конечно, устраивает.
Система частиц
  • Используйте Vertical Billboard вместо Billboardв Particle Renderer.
  • Используйте на мобильных устройства шейдеры из Mobile->Particles. Это касается не только системы частиц.
  • Используйте как можно меньше систем частиц и самих частиц в системе для достижения нужного эффекта.
  • Для слабых систем можно отключить некоторые малозаметные или несущественные системы частиц. Что позволит сохранить несколько Draw Calls и поднять на них производительность в ущерб эффектности. Уверяю, зачастую приятнее получать удовольствие от процесса игры, нежели от мегакрутых эффектов.
  • Не используйте OnGUI(). Каждый такой вызов — несколько дополнительных Draw Calls. Тоже самое относится к GUILayout. И вообще, поддержка GUI в Unity сделана очень плохо. У нас, например, своя система GUI, основанная на спрайтах. В Asset Store есть несколько других очень полезных плагинов для GUI.
  • Размер текстуры, генерируемой для шрифта, можно уменьшить, добавив только используемые символы. В Font Settings установить Character = Custom Set, а в Custom Chars включить используемые символы:
  • Можно попробовать использовать отдельные камеры для объектов сцены и GUI. Это позволит сделать GUI zoom-независимым, что освобождает от использования на нем Scale при увеличении/уменьшении. Иногда это может увеличить производительность.
Другое
  • Отключить акселерометр, если он не используется. Можно также понизить частоту измерений в Player Settings.
  • Можно ограничить максимальный FPS на старых устройствах. Например, установив его в: Application.targetFrameRate = 30 . Обычно это приводит к более гладкой картинке. Также, это уменьшает просадку аккумулятора, т.к. за то же время требуется меньше процессорной мощности. Вообще, на устройствах Apple FPS > 60 не имеет смысла, т.к. частота обновления экрана у них — 60.
  • Иногда периодическая частая сборка мусора сглаживает производительность. Потому что сама система делает это редко и когда уже все совсем плохо, и может накопиться большое кол-во объектов для уничтожения, что приводит к скачку производительности. Если делать это чаще, объектов для уничтожения будет меньше, и освобождение памяти будет более гладким. С другой стороны, за все время работы сцены сборка мусора может и не понадобиться.
Уменьшение размера приложения

Для чего это может понадобиться? Раньше это делалось потому, что приложения размером <20Mb можно было загружать на iOS через 3g-сеть. Что в принципе должно увеличить кол-во закачек, хотя конкретной статистики я не видел. В связи с выпуском iPad3 приложения стали «жирнее», и порог был поднят до 50Mb. Не стоит также забывать, что после заливки приложения в AppStore оно будет увеличено в размере в среднем на 4Mb. Для проверки, сколько приложение будет весить после заливки в AppStore в xCode в Organizer->Archives даже появилась специальная кнопочка Estimate Size:

1. Используйте правильный формат текстуры.
Это также позволит уменьшить объем используемой текстурной памяти, а соответственно и повысить производительность. Иногда достаточно использовать формат текстуры 16 bits, особенно если вся графика нарисована всего в нескольких цветах. Сравните:

Для монотонных текстур можно использовать только ее серую и альфа-компоненту и лепить из них готовый объект, используя специально написанный для этого шейдер и умножение на цвет:

Для задников и нечетких объектов можно использовать компрессию. Сейчас PVRTC-компрессия на iOS довольно продвинутая. Стоит помнить, что чем больше текстура — тем лучше ее качество после компрессии. На маленьких текстурах использование компрессии может быть неприемлемым.
Чтобы это все имело смысл, нужно разделять объекты на группы типа «задний фон», GUI, игровые объекты, о чем я уже писал. Тогда на каждый тип можно завести свой атлас и использовать различные настройки формата текстуры.

Unity: Android Optimization Guide

Everything’s fine, until you realize your lack of optimization is killing your game!

INTRODUCTION

As everyone working with Unity, on Android or iOS project, you are, at some point confronted to performances issues, FPS drops, latency, lags and so on, that disturb your game or even ruin it…

You can be sure that the time has come to dig into your project and optimize it!
Here is a summary of this guide to help you navigate better:

PROFILE YOUR GAME

Unity Profiler

In order to watch and analyze your project performances you must use the Unity Profiler (Window > Profiler). It allows you see graphically how your game is doing, what takes time to compute, how long is the rendering per frame, what is sucking all your performances.

It is really easy to use, you just have to click on the graphic, on spikes for example, to get details on the frame computing. Unity shows you the processes that take the most time. In order to understand those processes don’t hesitate to just google their names, you’ll find a forum post talking about it.

The Unity Profiler works in the editor and shows you how your game is doing in it, but be aware that it doesn’t represent your game’s real performances. The Unity Editor is dealing with other processes than just the game, thus it runs slower than a simple build of your game.

Remote Profiler

That’s why you must build your game and profile it from your android device!

Once you’ve configured the Android SDK and JDK to build you project, you can use the remote profiler.
In the build settings (File > Build Settings…), you just have to check “Development Build” and “Autoconnect Profiler” and be sure that in the Editor Settings (Edit > Project Settings > Editor) that “Device” is put on “Any Android Device”.

Now, you have to launch your game on your device, while plugged to the computer, and normally you’ll see in Unity the Profiler window show up with your device’s stats.

If not, open the Profiler window and in the “Active Profiler” dropdown button, select “AndroidPlayer(ADB…)” something. That’s your device.

Profiling directly your device will give you so much more accurate information on your game performances, so prefer this method.

Editor log

One other great tool to get information on your build, is to open the Editor Log. Just after building your game open the Console window (Window > Console), and on the top right you have a button that you can click on to select “Open Editor Log”.

It will open you a document with many informations on your build, especially information on your build size and the assets that take space. Just scroll down the document until you find something that looks like this :

Читать:
Fps и герцы в чем разница

Test regularly on different devices

The plurality of android devices is a big difficulty for android development, as one problem or performance issue can appear on one device but not on the other.

That’s why you must test your game on different devices to prevent problems and to be sure that it is compatible with a large number of phones.

Of course, it’s tempting to do the optimization at the end of the project, but you’ll find yourself sinking under many performances issues. It’s preferable to regularly profile your game, at big milestones for example.
Indeed, with lesser modifications on the game you’ll find what is sucking your performances quicker.

SCRIPTS

Before getting into the big stuff with rendering optimization, we want our scripts to be beyond reproach. Indeed, if like me you’re a programmer and you’re working with artists, it would be preferable to check your scripts performances before blaming the artists (Even if everything is surely their fault!).

Don’t put everything in Update()

One of the biggest mistake of Unity beginners is to rely too much on the Update() function. It’s quite easy to just put everything in it, check this or that state and to react accordingly, but if every GameObject in your scene has an Update() function checking many things and realizing complex actions this could be heavy on android performances.

So, before using anything in the Update() function, reflect on your needs. Does it really need to be called every frame?

Use Coroutines

If it doesn’t, then you have several options to be more efficient.

You can use Coroutines to call a method only every second for example, using the instruction: “yield return new WaitForSeconds (1);
It could be used to refresh some UI display:

Or you can wait every two frames and call your method, with:
yield return new WaitForEndOfFrame ();
for example to process a complex calculations, but not every frame.

Use Events, Actions, Reactive Programming

What could be really efficient is to call your methods only when you need it, for example when this variable changes, this method is called, or when that event occurs display and UI pops up …

This is the principle of Reactive Programming, to work with Events, where an action calls reactions.

They allow you to create events, that you have to call when they occur, and those events call all the methods that have subscribed to it.

For example you create an event “OnPlayerJump”, that you call every time in your code you make your player jump, and you subscribe to this event a method creating some dust fx or an event playing a jump sound. That’s just a very little introduction and you should look into it if you don’t know about it.

if you’re interested in Reactive Programming, go check this Unity Asset :

UniRx — Reactive Extensions for Unity by neuecc

Reactive Extensions for Unity that allows LINQ to asynchronous and LINQ to multithreading and LINQ to events and more…

Use Raycasts to detect your touchable GameObjects

In Unity, except for the UI, there aren’t any easy way to detect a Tap touch on a GameObject on the screen. You can use the OnMouseDown() functions, but Unity prevents its use on smartphones.

So what you can do, is to use Raycasts, starting from the camera and going where the finger touched the screen, and if you touch a certain object type you call a method on it.

That’s what I do, I have a Touchable Class, a Touchable Layer, and when a Touch occurs on the screen I use the method :

It detects if the touch is on a Touchable GameObject and if it’s the case I then call the OnTouchDown () method on the GameObject.

PHYSICS

Now that you checked your scripts, you’ll surely want to check how you manage the physics interactions if you have some. On mobile it would be better to have none but it you have to, you must be careful on certain simple things.

Try to have the least dynamic Rigibodies as possible, because they demand a lot of performance to compute.

Use simple primitive colliders instead of mesh colliders that are so much more complex to process.

Try to keep the “Collision Detection Mode” on “Discrete” if possible, as “Dynamic” demands more performance.

And lastly, you can go to the TimeManager window (Edit > Project Settings > Time), and tweak the “Fixed Timestep” value. This value represents the duration between two calls of the FixedUpdate() method.

So if you lower the Fixed Timestep the physics calculation will be called more often, leading to a more accurate simulation but taking many ressources. On the other hand, rising it up could maybe reduce your physics simulation time, especially if you don’t need a very accurate simulation.

LIGHTING

Now let’s get to the rendering optimization part! First of all, the lighting!

If possible don’t use dynamic lighting and prefer unlit textures, as lighting calculations take much resources, especially with many objects to render.

Baked Lighting

You can use Baked Lighting, if it suits your project, as it calculates and “paints” the lighting directly onto the textures once and so it won’t process it any more during the game.

It allows you to put many lights, to create complex lighting scenes without thinking about the performances as everything will be baked onto the textures.

But of course you’ll lose all the advantages of a dynamic lighting, like the dynamic shadows and your moving objects could feel out of place when progressing through the level without light changement.

Light Probes

You can somewhat counter this problem by using Light Probes : https://docs.unity3d.com/Manual/LightProbes.html

Those probes also store light information during the light baking, not on textures but on empty spaces. This way when objects move around light probes areas there are falsely lit by the light previously baked.

Shadow Projector

Another thing you can do is to fake the dynamic shadows under the main characters.
You could just put a round dark shape texture moving around with your character to represent his shadow, like old animated movies, if it suits your game.

Or, you can use a shadow projector, that uses the object mesh to project under it a fake shadow that isn’t as realistic as one with dynamic lighting but maybe it would be enough for your project and it takes less resources!

Here’s an Unity asset doing this shadow projection :

Fast Shadow Receiver by Nyahoon Games Pte. Ltd.

Shadows are very important aspects in 3D space. However, shadow rendering is GPU intensive process. Fast Shadow…

SHADOWS

If you have to use a dynamic light, be sure to use them as least at possible and to use only one Directional Light for your whole game.

Of course what is important when using dynamic lighting, especially in a 3D world, are the realistic shadows, but that’s what takes the most resources to render in the lighting process.
Nevertheless there are some values we can play with to get the shadows we want, without sacrificing performances.

Directional Light Settings

On your only Directional Light, you can begin by setting the culling mask with only the layer of GameObjects you want to project light upon, as we don’t want unnecessary calculations.

Play with the Shadows Resolution setting to see which one is satisfying enough, of course a lower resolution takes lesser resources. But be aware that what you see in the editor doesn’t represent what will be displayed on your device, so for example a Low Resolution Shadow setting could be perceived as really pixelated on your computer but could be enough on your device. Always build and test directly.

Quality Settings

Now, open the Quality Settings (Edit > Project Settings > Quality), and let’s dig into those advanced settings.

Here’s what your window should look like for now :

Those settings really influence your game performance so try to get familiar with it.

Pixel Count:
In order to reduce the light calculations, lower the “Pixel Count” to 1, or 0 if you don’t have any lights.

Antialiasing:
Disable the Antialiasing if possible because it takes many resources on mobile.

Soft particles:
If you don’t have a specific use of Soft Particles, also disable it.

Shadows:
Now the shadows are the real important settings to play with.
As Soft Shadows demand more resources you can select “Hard Shadows Only” option.

But with this option, at a very low resolution the shadows are really pixelated and edgy. So, what you can do is pass the option “Shadow Projection” from “Stable Fit” to “Close Fit” and you’ll see that the shadows are now less pixelated.

Don’t worry if it seems ugly on your computer screen, remember to build and directly test on your device!

Now, set the “Shadow Cascades” to “No Cascades”.

Then finally, tweak the “Shadow Distance” value, reducing it slowly. You’ll see at some point that far shadows are disappearing. Indeed, this value represents the distance at which the shadows are processed. Shadows beyond this distance aren’t calculated. So reduce this value until you’re satisfied with your shadows while having a lower “Shadow Distance”.

So, now your Quality Settings window should look like this :

TEXTURES

When you’ll look into your Editor Log, you’ll surely see that your textures are taking the most memory in your game. Indeed too often they are way too big for their purpose.

So what you can do before you start complaining about your artists, is to go in your textures “Import Settings”, just click on your textures in your “Project” window.

  • Click on the “Advanced” arrow, and now check “Generate Mip Maps”, Unity especially recommends it for faster texture loading time and a lower rendering time.
  • Then you can play with the “Max Size” option, that compresses the texture in order to reduce its memory footprint. Indeed, the textures are often too big.
  • Take in account their purposes (Character texture, UI sprites, Environment texture etc), you should try to reduce the “Max Size” as low as possible, as long as the visual aspect suits you.
  • Finally to reduce the draw calls, you can use textures atlases. Atlases are big textures files containing several or all of your game textures. This can really lead to great performance gains!

Another big chapter in Unity Android Optimization, is the UI optimization. Because as you’ll see, the Unity UI system isn’t really well optimized, but luckily, you can find smart ideas to get the best performances!

Sprites

When using Sprites for your UI, be sure that your Sprites have as less transparent areas as possible, because those create overdraw and reduce performances.

Like the Textures, you can put all your UI Sprites into an atlas while reducing their “Max Size” to a minimum acceptable.

Also, check “Generate Mip Maps” option, it is really important for your UI as it will not only take less time to render but also make it smoother on your screen.

Canvas

In Unity, the UI works with a Canvas, try to have as less Canvases as possible, because a Canvas represents extra calculus for the rendering process.

Maybe you have Canvases that are only used in a display purpose without need for interaction. In this case remove the “Graphic Raycaster” component on it, as it implies that the Canvas will be interacted with and has to check for interactions.

Also, to gain in performance just uncheck the “Pixel Perfect” option! You’ll see great results especially with Scroll Rects.

Texts & Images

The Text components can be really resource-demanding on mobile!

So what you can do, is disable “Rich Text” on it.

On Image and Text components that aren’t interacted with you can uncheck “Raycast Target” on it, as it will remove them from any Raycast calculus.

Unity recommends not to use the “Best Fit” option on to many Texts, but I haven’t tested this yet so I can’t say if this is really a bad habit.

Fonts

With your Texts you’ll surely use custom fonts, but be careful because with some bad quality fonts found on internet you could have performance issues.
Try to keep a small font count, and if you have performance issues try to disable the “Best Fit” option on all your Text components.

UI Material

I saw this tip on a forum and I confirm that when you leave the “Material” empty on Image and Text components your performances are slower.

So what you can do, is create a new Material in your project and set its Shader to “UI/Default”, and put this material on every Image and Text.

Rect Mask 2D

The Rect Mask 2D is a UI component, similar to the Mask Component, but it has the advantage of disabling UI elements when they are outside of the mask area and thus aren’t rendered. This can help improve the performances.

Scroll Rect

Another UI component, the Scroll Rect, is used to display a lot of content, hiding a part of it and scroll through it. It is really cool and nice to interact with it, but try not to use too much of it because it really isn’t optimized for mobile.

You can also use the previous component, the Rect Mask 2D to improve the Scroll Rect performances.

Canvas Group and Menu Animations

When animating the menu elements, naturally you would enable and disable the elements when showing on and off off the screen, but this isn’t the most optimized way.

Indeed, disabling and enabling can restart some processes on certain GameObjects. For example, the previous Scroll Rect component repopulates and reloads its content when enabling, which can cause lag spikes.

The other possibility is to let the GameObjects enabled but to use Canvas Group.

Those components are really useful, they allow to group elements up and control their behavior as a whole.
You can play with the opacity, “Alpha” of the group, and disable their interactivity with the “Interactable” and “Blocks Raycasts” options. So use these options to make sure that hidden menus can’t be interacted with.

SHADERS

Last but not least, your game’s shaders. Those can be pretty magic in terms of rendering but on mobile they can really sacrifice your performances, so you must watch out for them.

I’m really not an expert on shaders, so I can just warn you about how optimizing shaders on mobile is important and recommend you to take a look on the internet how to do so.

I just know that the keyword regarding mobile shaders is to stay simple. Few shaders, that aren’t complex but efficient.

What I can surely say is that post processing shaders are really expensive in resources, so try not to use them on mobile or optimize and test them to be sure that the game doesn’t suffer too much from it.

Conclusion :

That’s about pretty much everything I have learned working on my last android project.

I wanted to put it all in one article, to be able to share it and help other Unity developers.

As the process of searching and testing your optimization takes so much time, I also wanted to make myself some kind tutorial / reminder, for future projects.

Hoping that you’ve learned something from it!

Here’s some links that can lead you towards other good tips and tricks for android optimization:

We hope this guide will be useful!
Get in touch with us if you want more clarity on a point, or would like to request another subject for an article.

Follow us on Twitter and Facebook!

Четыре простых шага, как оптимизировать производительность мобильной игры на Unity

Изучив множество прототипов, я практически не встретил ни одного, который был бы оптимизирован под слабые устройства, даже когда дело касается Hyper Casual. Даже если на мощных устройствах у вас стабильно хорошая производительность, то постобработка, неправильные настройки графики и теней могут уничтожить FPS в пару кликов — а в релизе такие ошибки критичны.

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

Это особенно актуально для ГК-проектов, которые разрабатываются и тестируются быстро на реальной аудитории, но должны плавно запускаться на максимум устройствах.

Очевидная причина проблем с оптимизацией в том, что мощность графического процессора мобильного девайса несравнима с видеокартой ПК, GPU флагманов может быть в десятки раз мощнее, чем у массовых и дешевых девайсов, а постобработка подразумевает дополнительные операции с отрендеренным изображением каждый кадр, то есть 30-60 раз в секунду.

При этом мобильные игры не всегда требуют высокого качества изображения, хотя бы из-за размеров экрана. Но разработчики все равно часто используют приятные глазу эффекты, знакомые пользователям консолей и ПК: блум, цветокоррекция, антиалиасинг и так далее.

В итоге игра начинает тормозить и нужно искать причину.

Причина №1. Антиалиасинг и желание сделать красиво

Антиалиасинг нужен для устранения эффекта «лесенки» на краях объектов. Красиво, и на устройствах выше среднего это практически «бесплатно», но на слабых может сильно испортить игровой опыт.

Выходом могла бы стать настройка под разные девайсы, но по опыту могу сказать, что так делают немногие разработчики Hyper Casual. Поэтому проще и быстрее полностью отказаться от антиалиасинга, разница от наличия которого может быть незаметна на экране мобильного девайса.

Дальше есть два пути, как решить вопрос цветокоррекции.

  1. Это можно сделать на стороне художников, но перерисовка текстур требует много ресурсов, поэтому отбрасываем вариант.
  2. Пройтись по всем шейдерам в проекте и вставить туда пару строчек, которые делают цветокоррекцию. В таком случае цветокоррекция получается практически «бесплатной». Но если шейдеров слишком много, и они, например, покупные, а разработчик не понимает, что куда вставлять — могут возникнуть проблемы.

Фишка в том, что если мы хотим сделать цветокоррекцию, но не хотим вставлять ее в шейдер, то рендер нельзя сделать сразу на экран. Надо сначала нарисовать всю игру куда-то во внутреннюю текстуру, потом пройтись по ней с цветокоррекцией и только потом вывести на экран. Из-за этой многоступенчатости FPS на слабых устройствах сразу падает в несколько раз.

Еще один нюанс: вставить цветокоррекцию в шейдер можно только при небольшом количестве источников света. Для большинства мобильных игр это не актуально, но на всякий случай поясню.

В шейдер нельзя добавить километры кода, так как есть ограничения. Туда можно вписать, например, 10 источников света, чтобы они обрабатывались в этом шейдере. Но если нужно больше, то придется этот объект нарисовать еще раз, а потом приплюсовать освещение от новых источников света. С таким вторым проходом цветокоррекция в шейдере уже не сработает, она будет применяться криво.

Причина №2. Физика и желание всё сделать «честно»

Кейс: на сцене одновременно взрывается 50 объектов, что сильно нагружает устройство.

Решение простое: не обязательно все взрывать по-честному — ближе к камере можно применить качественные и проработанные эффекты, а для всего, что расположено по краям или далеко, сделать более простой эффект в виде дыма или вовсе ничего не проигрывать.

Или другой пример из моей практики:

Есть игра, где бежит 3D-человечек и у него есть два варианта смерти — от разрезания по вертикали циркулярной пилой или по горизонтали крутящимися ножами. Чтобы это реализовать, можно поставить тяжелый плагин, который прямо во время игры берет меш человечка, его правильно разрезает на несколько объектов, зашивает «дыру», которая образовалась, и навешивает на каждую часть физику. Это происходит очень медленно и может тормозить даже на ПК. А что если в игре 20 человечков, которые синхронно попадают под нож?

В этом случае можно просто сделать два префаба: один разрезанный горизонтально, другой — вертикально. Сохранить их, просчитать заранее и использовать в игре уже готовое.

Причина №3. Большое количество независимых объектов

Кейс: окружение сцены состоит из кубов, к которым применены разные материалы с разными шейдерами, или просто из большого количества кубов. При достижении 10 000 кубов в сцене игра начинает заметно тормозить, так как каждый из объектов требует один вызов отрисовки (drawcall) на видеокарте.

Поможет объединение мешей (батчинг) объектов в один большой меш для более быстрой отрисовки в сцене с множеством одинаковых объектов. Это можно сделать инструментами Unity, но лучше вручную.

При этом статический батчинг (Static Batching) хуже склеивания в один меш, если у вас на сцене много маленьких объектов по паре треугольников. Потому что тогда Unity будет рендерить статик батчинг как один и тот же меш, но по кусочку с кучей вызовов. То есть 10 000 кубов будут рендериться в 10 000 drawcall’ов.

Решение: использовать один шейдер со всеми необходимыми параметрами для материалов, убедиться что объекты окружения помечены как статические, и что в проекте используется батчинг, который группирует эти объекты во время рендеринга и отрендерит их все за один вызов отрисовки.

Причина №4. Реалтайм тени и освещение

Основная причина, почему его не стоит использовать: расчеты освещения происходят во время работы приложения и нагружают устройство.

Решение: использование в шейдерах окружения заранее просчитанные карты освещения, которые представляют собой битмап. Целевое устройство, на котором будет запускаться игра, будет загружать карту освещения один раз, вместо того, чтобы пересчитывать её постоянно во время игры.

Полностью запеченный свет (максимальная производительность):

Один каскад теней без сглаживания, подогнанный под размеры сцены (х2 по ресурсам по сравнению с первым вариантом):

Стандартно настроенные тени с несколькими каскадами + сглаживание (х2.5 от первого варианта):

Дополнительно. Tips and tricks

Ко всему вышесказанному рекомендую сделать еще несколько вещей, чтобы ваш прототип стал еще ближе к успешному релизу.

Related Posts