Избранное Все заметки GitHub icon Мой Github Обо мне

Избранное

Позднее Ctrl + ↑

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

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

На 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, и профайлеры, и основные ошибки, в общем, стоит ознакомиться.

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

Unity iOS plugin

Так как тема мобильной разработки на Unity для меня сейчас актуальна, то продолжу про создание плагинов. Как сделать свой плагин для iOS? Что можно делать с помощью нативных плагинов? Например, показать стандартный ActionSheet или отобразить HTML, или синхронизироваться с iCloud, или получить доступ к галереи картинок опять же... Чтобы сделать простой плагин и xCode не обязателен, на самом деле.

Для примера, сделаем плагин который отображает браузер и загружает какую-нибудь страницу внутри Unity приложения. Плагин будет состоять всего из одного файла, с расширением .mm. В документации есть список форматов и описание сборки под iOS.

Для начала, объявим главный контроллер Unity приложения:

extern UIViewController *UnityGetGLViewController();

Для примера будет достаточно трех функций: показа браузера, загрузки страницы, скрытия браузера.

- (void)showBrowser {
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)];
    [UnityGetGLViewController().view addSubview:webView];
}

- (void)loadUrl:(NSString *)url {
    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}

- (void)close {
    [webView removeFromSuperview];
    webView = NULL;
}

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

Ну и последнее, интерфейс доступа на C:

static Browser *browser = NULL;

extern "C" {
    
    void browserLaunch () {
        if (browser == NULL)
            browser = [[Browser alloc] init];
        [browser showBrowser];
    }
    
    void browserLoadUrl (const char* url) {
        [browser loadUrl:[NSString stringWithUTF8String:url]];
    }
    
    void browserClose () {
        [browser close];
    }
    
}

Это все, плагин готов! Теперь осталось вызывать эти функции из Unity. По аналогии с Android плагином, делаем класс для браузера. Все функции подключаем через [DllImport («__Internal»)], должно получиться что-то такое:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class Browser : MonoBehaviour {

	#if UNITY_IPHONE
		[DllImport ("__Internal")]
		private static extern void browserLaunch (int left, int right, int top, int bottom);

		[DllImport ("__Internal")]
		private static extern void browserLoadUrl (string url);

		[DllImport ("__Internal")]
		private static extern void browserClose ();
	#endif 

	// Показываем браузер
	public static void Launch (RectOffset offset) {
		#if UNITY_IPHONE
			browserLaunch (offset.left, offset.right, offset.top, offset.bottom);
		#endif
	}

	// Открыть страницу
	public static void LoadUrl (string url) {
		#if UNITY_IPHONE
			browserLoadUrl (url);
		#endif
	}

	// Убрать браузер
	public static void Close () {
		#if UNITY_IPHONE
			browserClose ();
		#endif
	}
}

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

Исходники на GitHub

2016   ios   plugin   unity

Unity Android plugin

Как часто можно встретить на stackoverflow вопросы, про доступ к галереи картинок, или как получить снимок с камеры, или как показать браузер, или как работать с Play Services... Да что уж, я и сам задавал такие вопросы. А всё потому, что к сожалению, в Unity нет (пока) многих специфичных функций, которые зависят от платформы. Но есть возможность подключать нативные плагины, что очень хорошо. С помощью плагинов, можно организовать любое взаимодействие которое поддерживается платформой.

Раньше, плагины можно было компилировать в JAR файлы, закидывать в папку Assets/Plugins/Android и использовать. В общем-то, так можно делать и сейчас, но это подойдет для самых простых плагинов, не использующих ресурсы, которые нужно сохранять в папку res. А на такие вещи Unity стала ругаться и выдавать сообщение в консоль:

OBSOLETE — Providing Android resources in Assets/Plugins/Android/res is deprecated, please move your resources to an Android Library. See «Building Plugins for Android» section of the Manual.

Короче, надо делать Android Library. Если интересует, как сделать плагин по «старой технологии», могу рассказать, а пока — как надо.

Шаг 1.

Скачиваем Android Studio и Android SDK. Тут особых проблем возникнуть не должно.

Шаг 2.

Создаем новый Android Project. Далее, создаем новый модуль: File → New → New Module → Android Library

Шаг 3.

Скопируем JAR файл с классами Unity в папку libs нового модуля. И добавим в раздел с зависимостями Gradle файла строку с классами Unity. Где искать файл classes.jar можно прочитать в документации.

dependencies {
    provided files('libs/classes.jar')
    ...
}

Шаг 4.

Для примера сделаем плагин который показывает всплывающие сообщения, тосты (Toast). Создадим новый класс Plugin и добавим туда один метод show с параметром message. Не самый удачный пример, потому что не используются ресурсы, но самый простой.

package com.mycompany.toast;

import com.unity3d.player.UnityPlayer;
import android.widget.Toast;

public class Plugin {
	// Показать сообщение
	static void show(final String message) {
		UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Toast.makeText(UnityPlayer.currentActivity, message, Toast.LENGTH_SHORT).show();
			}
		});
	}	
}

Шаг 5.

В Android Studio, справа в меню Gradle, ищем название модуля которое задали при создании и далее: Tasks → build → assembleRelease. Модуль скомпилируется в AAR файл, то что и нужно.

Шаг 6.

Копируем AAR плагин в папку Assets/Plugins/Android с Unity проектом. И пишем небольшой интерфейс для вызова функции show.

using UnityEngine;
using System.Collections;

public class Toast : MonoBehaviour {

	// Показываем всплывающее сообщение
	public static void Show (string message) {
		#if UNITY_ANDROID
			using (var plugin = new AndroidJavaClass("com.mycompany.toast.Plugin")) {
				plugin.CallStatic("show", message);
			}
		#endif
	}

Шаг 7.

Закидываем приложение на Android устройство и проверяем. Все должно работать.

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

2016   android   plugin   unity