Советы по оптимизации Unity проекта

Рано или поздно практически любой разработчик на Unity сталкивается с проблемами производительности в своем проекте. И не важно, что это за проект: очередной AAA проект от известной студии, или Match 3 в новом сеттинге от новичков в игровой индустрии. Оптимизировать проект придется так или иначе. Также не важно, под какую платформу (платформы) написан проект: PC, консоль или мобильный. Кажется, что уж в 2019 году-то с гигагерцами не только на компьютерах, но и на телефонах, необходимость в оптимизации для не AAA проектов должна была отпасть. Но нет, приходится экономить спички практически как на заре игростроения. Непонятно, почему так происходит; возможно, это плата за скорость разработки в Unity? Гоу в комментарии, обсудим.

 

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

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

Совет №1. Прежде чем бросаться в омут с головой в оптимизацию и применять нижеперечисленные советы в своем проекте, стоит определиться с тем, что конкретно тормозит и что нужно оптимизировать. Для этой цели обычно используется профайлер. В Unity есть встроенный профайлер:

Профайлер в Unity включает в себя несколько подпрофайлеров, специализирующихся на определенном функционале вашего приложения. Например: CPU профайлер, профайлер памяти, аудио профайлер, UI профайлер и так далее. Более подробно о функционале каждого из подпрофайлеров можно почитать в документации. Само собой, только на профайлере Unity свет клином не сошелся, и можно пользоваться профайлерами для соответствующих платформ.

 

Обычно начинают с Unity профайлера как самого доступного решения. И переключаются на более узкоспециализированные вещи по мере необходимости. Стоит также отметить, что профайлер в Unity поддерживает два режима работы: обычный и глубокий (deep). Глубокий режим профилирования показывает гораздо больше полезной информации, хотя и гораздо сильнее подтормаживает выполнение вашего кода. Для более точного определения проблемных участков в коде стоит использовать глубокий профайлинг. К сожалению, глубокий профайлинг доступен не на всех платформах. Можно использовать глубокий профайлинг в редакторе и, согласно этой статье, на Android и Windows с применением бэкенда Mono начиная с Unity 2017.3. 

 

Перво-наперво, нужно определиться с узким местом.  Обычно его называют горлышком. Т.е. нужно найти то место, которое у вас больше всего тормозит — выполняется дольше. И в первую очередь оптимизировать именно его. Горлышком в вашем проекте может быть: отрисовка (rendering) или скрипты (собственно код). Довольно редко — и то, и другое вместе.

 

Совет №2. В корректности каждого из описанных в статье советов необходимо удостовериться лично, а не пытаться сиюминутно применить каждый из них в своем проекте. Дело в том, что часто некоторый оптимизированный код может отрабатывать быстро в одних условиях (на одной кодовой базе + на конкретном железе) и плохо в других.  Также нужно помнить, что сам движок Unity постоянно обновляется и улучшается, и то, что тормозило в одной версии, совершенно не обязательно будет тормозить в новых. Как следствие, код также необходимо проверять.

 

Совет №3. Если у вас Android приложение и вы еще не перешли на IL2CPP бекэнд, то самое время это сделать. Этот переход даст небольшой, но практически бесплатный (особенно если у вас мультиплатформенный проект, который уже собирается под iOS) прирост производительности, за счет того, что код не будет интепретироваться во время выполнения на устройстве, а сразу скомпилируется в нативный формат. Кроме того, исчезнут проседания, которые могут возникать во время запуска JIT компиляции, т.к. уже все скомплировано, что так же не может не радовать.

 

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

 

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

 

Совет №6. Старайтесь избегать boxing-а и unboxing-а.  Boxing —  это процесс преобразования значимого типа в ссылочный тип. Для этого создается временный объект ссылочного типа, в который записывается значение значимого типа. Пример:

Unboxing — это обратный процесс, т.е. процесс преобразования ссылочного типа в значимый. Пример:

Проблема с этим кодом в том, что происходит неявное выделение памяти для нового объекта (и довольно часто), практически сиюминутное (рано или поздно освобождение произойдет) освобождение занимаемой памяти. Т.е. наш горячо нелюбимый вызов GC. Поэтому следует избегать подобного кода. Для упрощения поиска таких проблем существует плагин Heap allocation viewer для Resharper-а и Rider-а.

 

Совет №7. Не стоит использовать API класса  Enum Дело в том, что все его методы работают через преобразование к объекту, т.е. через boxing, что, как мы уже знаем, мягко говоря, плохо. И порой простое получение списка значений может съедать приличную долю времени кадра. Мы не призываем отказаться от использования перечисляемых типов; наоборот, они довольно удобны. Но использовать API класса  Enum  стоит как можно реже. Как вариант, можно провести все неоходимые вычисления один раз при старте приложения под прелоадером и дальше пользоваться своими аналогами. Прирост производительности будет обеспечен.

 

Совет №8Unity API постоянно развивается. И теперь для многих методов запроса некоторого списка с информацией существуют аналогичные методы (практически братья-близнецы), которые принимают в себя список для заполнения результатов выполнения метода. Такие методы позволяют передавать в них заранее подготовленные экземпляры класса  List<T> , тем самым улучшая работу приложения с памятью, при этом не тревожа GC лишний раз. Пример:

 

Совет №9. Стоит избегать частого использования ключевого слова  params . Пример:

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

 

Совет №10API бизнес логики вышего приложения лучше сразу разрабатывать с учетом необходимости принимать объекты-буферы. Пример:

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

 

Совет №11. Стоит уделять особое внимание ассетам с asset-store. Особенно платным. Дело в том, что не все ассеты (в том числе и довольно популярные) реализованы должным образом. Зачастую бывает так, что цена шкалит, чего не скажешь о качестве ассета. Хорошо, если ассет поставляется с исходным кодом. В некоторых случаях его можно подправить. Но если посмотреть на это с другой стороны, то получается, что приходится тратить деньги на не вполне качественный ассет. Тем более, что попробовать ассет перед покупкой нельзя, а вернуть кровно потраченные не всегда возможно. Из собственного опыта: был куплен довольно популярный ассет для работы с мобильными оповещениями. Стоимостью аж $65. Не бог какой сложности ассет,  куплен для сокращения времени разработки. Ассет справлялся со своими обязанностями более или менее неплохо. Но вот с точки зрения производительности был не ахти. Вот такую картину можно было наблюдать при профалинге ассета на мобильном устройстве в режиме Idle (фактически в игре ничего не проиcходило):


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

 

Совет №12. Не стоит выполнять одну и ту же работу дважды. Вроде бы всем известная прописная истина, но не всегда это очевидно. Иногда приходится видеть код наподобии этого:

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

Думаем, теперь проблема налицо. Фактически, поиск значения происходит дважды. Сначала при вызове метода  Contains , а затем при обращении по индексу. Отсюда и двойная работа.

 

Совет №13. Бывают такие случаи, когда из движка необходимо выжать максимум производительности. Как известно, Unity не относится к числу наиболее быстрых движков (по многим причинам). Но все-таки некоторая работа в этом плане ведется. Кардинальным решением проблемы производительности должа стать подсистема DOTSDOTS — это решение, которое состоит из из 3-х частей:

  • система задач для эффективной работы с многопоточным кодом;
  • Entity Component System (не путать с Entity Component) — компонентный подход к написанию производительного кода по умолчанию;
  • компилятор Burst, компилирующий некоторое подмножество C# кода в глубоко оптимизированный нативный код.

И, хотя это решение еще находится в разработке, многие разработчики уже используют его, получая значительный прирост производительности. Чего стоит демка Megacity — проект футуристического города с летающими машинами, сотнями тысяч высокодетализированных игровых объектов и уникальных источников звука! К проблемам использования решения DOTS стоит отнести значительно более сложный подход в сравнении с EC. А, также, текущее состояние:  это решение еще не ушло в релиз, по этому велика вероятность кардинальго изменения API. 

 

Совет №14. Интерфейсный полиморфизм хотя и является удобной возможностью языка, более того, практически одним из столпов ООП, дается он нам не бесплатно. Рассмотрим пример:

Довольно часто можно встретить подобный код. И если дальше по коду нигде не используется возможность назначить объект именно типа  IList<string> , то это — пустое расходование производительности. И этот код стоит заменить на следующий:

Дело в том, что в первом случае при каждом обращении к  _myList происходит своего рода кастинг к  IList<string> и поиск необходимого виртуального метода, что само по себе негативно сказывается на производительности. Более подробно об этом можно прочитать здесь. Таким образом, всегда, по возможности, необходимо использовать классы, нежели интерфейсы.

 

Совет №15. В некоторых случаях необходимо использовать массив нулевой длины. Например, для начальной инициализации поля; для предотвращения ошибок нападобие  NullPointerException ; для упрощения логики или избавления от разного рода проверок. Живут подобные объекты сравнительно недолго, что приводит к проблемам с GC. Чтобы решить эту проблему, стоит обратить внимание на то, что массив нулевой длины — это неизменяемая коллекция. И, как следствие, нет необходимости каждый раз создавать новый пустой массив. Можно создать обобщенный класс, объявить статическое поле и инициализировать его единожды массивом нулевой длины. После чего переиспользовать уже созданный массив без каких-либо проблем с GC. Но можно сделать еще лучше: воспользоваться уже имеющейся возможностью в .NET Framework. А именно, методом  Array.Empty<T> .

 

Совет №16. Пожалуй одним из главных советов по оптимизации в языках с GC (да и не только) является совет по использованию пулов (Pool). В случаях, когда требуется большое количество объектов одного типа, например, пуль: пулы позволяют значительно увеличить производительность. Пул — это класс, позволяющий создать некоторое количество однотипных объектов заранее (например, под прелоадером) и переиспользовать эти объекты по мере необходимости.  Пулы позволяют решить множество проблем с GC в частности, и с производительностью в целом. Во-первых, не тратится время на создание объектов и их первоначальную инициализацию, т.к. все это происходит только единожды. Во-вторых, не требуется время на вызовы GC, т.к. мы переиспользуем объекты вместо освобождения занимаемой ими памяти, что просто замечательно сказывается на проблеме остановки игрового мира. Стоит также отметить, что при работе с пулами есть некоторые сложности. А именно: необходимо определиться с количеством объектов, которым будет инициализирован пул. Дело в том, что, если инициализировать пул недостаточным количеством объектов, то во время игры будет происходить выделение и создание новых экземпляров объектов и их инициализации. Т.е. именно то, чего мы хотим избежать. А ежели инициализировать пул слишком большим значением, то будет использоваться слишком много драгоценной памяти. Другой проблемой при работе с пулом является момент возвращения объекта обратно в пул. За этим моментом необходимо строго следить, чтобы на каждое получение объекта из пула обязательно происходило возвращение этого же объекта в пул. Иначе толку от пула не будет, скорее только вред. Урок по работе с пулами для начинающих можно посмотреть здесь.

 

Совет №17. При написании кода стоит пользоваться дополнительными утилитами и плагинами, которые, кроме того, что улучшают код, позволяют править проблемы с производительностью в Unity на этапе его написания. К таким плагинам можно отнести плагин Unity Support для JetBrains Rider и ReSharper. 

 

Совет №18. Стоит ознакомиться с документацией и уроками по оптимизации Unity проектов от разработчиков движка.

 

Совет №19. Для увеличения производительности отрисовки стоит использовать статический и динамический батчинг, а так же спрайт листы. Батчинг и спрайт листы позволяют значительно снизить количество вызов отрисовки (draw calls). При должной сноровке так же можно завести и свои виды «батчинга». Например, с помощью  Mesh.CombineMeshes . Кроме прочего, спрайт-листы также экономят видеопамять, которой не бывает много.

 

Совет №20. Другим способом улучшения производительности отрисовки является Culling. Culling позволяет не отрисовывать объекты, которые можно не отрисовывать. По умолчанию, в Unity задействован Frustum Cullingкоторый использует видимую область камеры (Frustum) для определения объектов, которые нет нужды отрисовывать. Если объект не попадает в область видимости камеры, то он не будет отрисован. Этот вид Culling-а работает по умолчанию. Но проблема в том, что если, например, мы будем видеть закрытую дверь, то отрисовываться будет и дверь и все объекты за ней, несмотря на то, что мы их не видим. Для решения этой проблемы существует Occlusion Culling.

 

Совет №21. Проблемы с памятью и скоростью отрисовки на слабых устройствах может решить использование ресурсов по вариантам. Т.е. для разных групп устройств (например в зависимости от разрешения экрана) можно подготовить ресурсы в разном разрешении. И использовать наиболее подходящие.  В Unity существуют средства для изменения размеров текстур, но также можно поспользоваться специализированными средствами, например, TexturePackerом. Для упрощения работы с подобными ресурсами можно также воспользоваться другой возможностью Unity, а именно варианты префабов.

 

Совет №22. Не стоит использовать  FindObject . Тем более не стоит использовать этот метод часто. При вызове этого метода происходит перебор ВСЕХ объектов на сцене, что само по себе очень медленно. Как вариант, лучше назначить необходимые ссылки в редакторе. А если необходимо иметь ссылки на несколько объектов, чтобы их можно было находить или не находить, то можно сохранить все необходимые ссылки в список, и включать или исключать объекты из списка по  OnEnable или  OnDisable .

 

Совет №23. Практически по тойже самой причине не стоит использовать метод  Camera.main . Дело в том, что при вызове этого метода на самом деле происходит вызов метода  FindObjectWithTag("MainCamera") , что по своей сути аналогично поиску по ВСЕМ объектам с тегом основной камеры. Лучше закэшировать найденное значение и использовать его. Еще лучше сразу сохранить ссылку на камеру в редакторе.

 

Совет №24. Класс  System.Text.StringBuilder позволяет объединять строки быстрее, нежели обычная конкатенация finalString = string1 + string2 + string3 + string4 + string 5 , за счет меньшего количества создаваемых временных подстрок.

 

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

 

Совет №26. Используйте структуры вместо классов. Потому что это производительней.

 

Совет №27. Используйте корутины (сопроцедуры), если вам не требуется обновлять каждый кадр. Также стоит ограничить количество скриптов, использующих метод  Update() . Чем меньше скриптов его используют, тем лучше. Как вариант, можно сделать свою обертку-менеджер для этого.

 

Совет №28OnBecameVisible и  OnBecameInvisible позволяют не вылнять некоторые вычисления, если объект невидимый.

 

Совет №29. Задание родителя гораздо эффективнее выполнять сразу, вместе с созданием объекта newObj = Instantiate(go, parent) , нежели сразу после  newObj.transform.parent = parent.

 

Совет №30. Сравнение тегов лучше производить с помощью метода  go.CompareTag("Enemy") , т.к. это производительнее варианта  go.tag == "Enemy" . Дело в том, что геттер  go.tag на каждое обращение создает новую строку, чего метод для сравнения тегов не делает.

 

Совет №31. Следует избегать ключевого слова  foreach .

 

Совет №32. Векторная математика  Vector3() * 5f * 4f  гораздо медленее скалярной Vector3() * (5f * 4f) . Это происходит из-за того, что при использовании векторной математики дополнительное время тратится на создание векторов, вызов конструтора и методов сложения, умножения и т.п.. в то время, как скалярная математика без всего вышеперечисленного обходится. В некоторых особо нагруженный частях проекта, возможно, вообще стоит отказаться от векторной математики. Практически по тойже причине следующий код  var v = default; v.x = 1; v.y = 2; v.z = 3;  быстрее, чем  var v = new Vector(x, y, z); , и   pos += 5f медленнее, чем  pos.x += 5f, pos.y += 5f, pos.z += 5f .

 

Совет №33. Использование геттера  localPosition быстрее использования  position , т.к. не проиходит преобразования координат из локальных в глобальные. Это так же справедливо для геттера  localRotation .

 

Совет №34. Стоит избегать использования  Mathf.Sqrt() и  Vector3.magnitude , т.к. эти операции включают в себя извлечение квадратного корня. Лучше задействовать соответствующую версию последней операции без извлечения квадратного корня. А именно,  Vector3.sqrMagnitude . По той же причине стоит избегать операции  Mathf.Pow() , т.к., если второй параметр равен  0.5 , это аналогично извлечению квадратного корня.

 

Совет №35. Стоит уменьшить количество вызовов  GetComponent . Часто использование методов наподобие  GetComponent или встроенных геттеров компонентов могут значительно снизить производительность. Гораздо выгоднее кэшировать значения. Это также относится и к использованию другого API. Например: кэшированное значение трансформации быстрее, нежели некэшированное;  var cachedTime = Time.deltaTime; быстрее некэшированного значения.

 

Совет №36. Стоит избегать  OnGUI функцию и  GUI класс.

Функцию  OnGUI  и класс  GUI можно отнести, пожалуй, к основным пожирателям производительности, особенно в мобильных играх. Основная проблема с функцией  OnGUI  в том, что она вызывается несколько раз за кадр. Другими словами, ни в коем случае ее нельзя использовать для выполнения игровой логики и основного фукнционала игры. Основное ее назначение — это отрисовка пользовательского интерфейса. В свою очередь, класс  GUI можно использовать разве что для маленьких и несложных интефейсов, и не более того. Таким образом, никакой речи не идет об использовании этого класса для сложных и динамических пользовательских интерфейсов с большим количеством интерактивных элементов, к которым можно отнести инвентарь, статистические панели, мини-карты, экраны опций и т.д. На данный момент существует два основных решения: стандратный UI и сторонние решения — NGUI и подобные. Хотя и с ними есть проблемы.

 

Совет №37. Стоит использовать сжатые форматы текстур, когда это возможно. И если такой возможности нет, то стоит предпочесть 16-битные текстуры 32-битным.

 

Совет №38. Не стоит использовать динамическое освещение, когда оно не требуется. Учитывая тот факт, что мы разрабатываем мобильные игры, не стоит его использовать практически всегда, т.к. мобильные устройства все еще недостаточны мощны для него. Вместо этого лучше использовать запеченный свет (baked lighting).

 

Совет №39. Необходимо выставлять статическое свойство игровым объектам, если эти объекты не двигаются для активации внутренних оптимизаций, таких как static batching.

 

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

 

Совет №41. Минимизируйте количество сложных математических функций в пиксельных шейдерах.

 

Совет №42. Не используйте туман, когда он не требуется. Можно даже рассширить несколько этот совет: не используйте ничего, если это вам не нужно.

 

Совет №43. Используйте скай-боксы для эмуляции удаленных объектов.

 

Совет №44. Новая UI подсистема тоже несовершенна. И для корректной работы с ней стоит обратить внимание на официальную документацию. Также нужно помнить, что в некоторых случаях и этого не достаточно. И как советуют некоторые разработчики (возможно и из Unity), вы можете взять все в свои руки и переписать медленные куски кода под себя, ибо UI имеет открытый исходный код. Хотя, конечно, это так себе совет, но как говорится, за неимением лучшего…

 

Совет №45. Банальный, но полезный во всех смыслах совет. Не наследуйтесь от класса  MonoBehaviour где только можно и нельзя. Нужно помнить, что этот класс создает нативный объект, что само собой негативно сказывается на производительности.

 

Совет №46. Чем выше иерархия наследования, тем выше и цена ее. Результаты некоторого простого теста говорят сами за себя:

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

 

Совет №47. Избегайте использования  Linq  функций. Они действительно слишком медленные. Пожалуй, стоит завести правило, запрещающее их использование.

 

Совет №48. Никогда, никогда не используйте метод  SendMessage . Он катастрофически медленнен. Более того, он красноречиво говорит, что в вашем коде и архитектуре проекта есть некоторые проблемы.

 

Совет №49. Хорошим шагом будет запрет на использование любых  Find  методов, несмотря на то, что эти методы очень популярны у новичков (в основном, конечно же, виноваты плохого качества обучающие материалы). Кроме того, эти методы небезопасны, не элегантны и неэффективны. Вместо этого стоит связывать ссылками все свои игровые сущности, используя менеджеры и прямые ссылки в редакторе.

 

Совет №50. Стоит всегда кэшировать компонеты в  Awake ,  Start для последующего их использования в  Update .

 

Совет №51.  Следует использовать наиболее быстрый Unity API. Например, вместо  Input.Touches лучше использовать  Input.GetTouch и  Input.touchCount (если требуется); неаллоцирующий физический API, вместо аллоцирующего ( Physics.RaycastAll и все остальные All физические методы).

 

Совет №52. Если вы используете методы  GetFloat ,  SetFloat ,  GetTexture ,  SetTexture на материалах и шейдерах, то эти свойства сначала будут прохэшированы (т.е. переведены из строкового значения в численное) и только затем использованы. Отсюда — потеря в производительности. Зачем что-то делать много раз, если можно один раз:

 

Совет №53. Если у вас есть динамические элементы пользовательского интерфейса (например, Scroll Rect) с большим количеством отображаемых элементов, то очень выгодно выставить свойство  Canvas.pixelPerfect .

 

Совет №54. Если у вас есть неинтерактивные визуальные компоненты, то не забудьте отключить свойство  Raycast target .

 

Совет №55. Всегда назначайте мировую камеру канвасу, иначе канвас будет обращаться к свойству  Camera.main каждый кадр.

 

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

 

Совет №57. В Unity, кроме пользовательского интерфейса, нет простого способа обработать касание на игровом объекте. Вы можете использовать  OnMouseDown , но на мобильных устройствах это работает плохо. Даже Unity предостерегает от использования подобных методов на мобильных устройствах. А решение простое: можно использовать Raycasts. Выпускаем луч из камеры в точку касания и, если происходит пересечение луча и проверяемого объекта, то вызываем обработчик касания.

 

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

 

Совет №59. Текстовые компоненты могут быть пожирателями ресурсов мобильного устройства. Первое, что нужно сделать, это отключить свойство Rich Text, там где без него можно обойтись. Сюда относится и свойство Best Fit. Да и вместо стандартного текста лучше использовать TextMeshPro.

 

Совет №60. Ознакомьтесь с уроками документации по отмизации от Unity Technologies:

Также стоит обратить внимание на соответствующий видеоконтент:

Кроме вышеперечисленного, много полезной информации можно найти на следующих сайтах:

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

Добавить комментарий

1 комментарий

  1. Nic:

    — Совет №10. API бизнес логики вышего приложения лучше сразу разрабатывать с учетом необходимости принимать объекты-буферы.

    К черту такую оптимизацию. Нарушается сразу два важных принципа:
    1 — метод не должен принимать null.
    2 — метод не должен менять переданные в него объекты.
    В дополнение, чтение такого кода — сущий кошмар. Что программист должен думать, увидев в коде вызов приведенного в примере метода:
    var users = GetUsers(list);
    Вероятно получение неких определенных пользователей из переданного списка? Или возможно метод возвращает всех пользователей из более общего списка сущностей? И только открыв реализацию можно понять, что оказывается, заполняется переданный список и он же возвращается.
    Пусть лучше будет тысячу раз не оптимизированный код, чем с такими методами в API бизнес логики.