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

Не смотрите что статья от 2021 года, она даже старее 😁 На старой версии сайта она была одной из первых, начал я её в 2016 году. К слову, с тех пор многие советы до сих пор актуальны, поэтому я решил её оставить и в новой версии сайта.

Этот пост будет периодически дополняться и обновляться как и прежде, последнее обновление: 24.10.2025.

Обновляя эту заметку я подумал, что сейчас в 2025 году, во времена расцвета ИИ помощников, эти советы пригодятся начинающим разработчикам игр. Ведь зачастую, код сгенерированный ИИ далеко не самый производительный. Обученый на примерах, статьях и тех же исходниках с гитхаба — он выдаёт рабочие (и то не всегда) примеры которые в проде будут выделять кучу мусора, занимать память, просаживать FPS и т.п.

Ниже немного актуальных до сих пор рекомендаций и ссылки на статьи, они подойдут и для 2D и для 3D игр:

  1. Кешируйте всё. Всё что используется больше 1-2 раз — лучше закешировать. Операции типа GameObject.Find(), GetComponent(), FindObjectOfType() достаточно ресурсозатратны, а если они вызываются где-нибудь в цикле или в Update(), то производительность наверняка упадёт.

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

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

QualitySettings.vSyncCount = 0;
Application.targetFrameRate = XX;

Если не отключить вертикальную синхронизацию, то изменение фреймрейта игнорируется!

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

  2. Ещё про атласы. Если упаковываете в Unity, убедитесь что атлас влезает на одну страницу! Это видно в инспекторе. Иначе каждая страница по сути новая текстура.

  3. Старайтесь по возможности делать меньше вызовов xxx.ToList() и xxx.ToArray() — они создают новые коллекции при каждом вызове. Лучше построить свой код так, чтобы был прямой доступ к массивам, спискам и использовать ранее созданные коллекции повторно.

  4. Уделите внимание работе со строками, особенно если это частая операция. Используйте StringBuilder и специальный форматный метод TMP — tmp.SetText("Data: {0}", data), он не создаёт промежуточные строки.

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

  6. Используйте события. Во многих уроках и видео до сих пор используют метод Update(). Он вызывается каждый кадр, а FixedUpdate() по умолчанию вызывается 50 раз в секунду! Если у вас в этих методах лежит какой-то тяжелый код, то производительность приложения упадёт. В качестве альтернативы можно использовать: 1) событийный подход 2) корутины 3) централизованный Update 4) перейти на ECS

/// <summary>
/// Пример с событиями (event-driven)
/// </summary>
public class Player : MonoBehaviour {

    public Action OnDamaged;

    public void Damage(int value) {
        OnDamaged?.Invoke();
    }
}

/// <summary>
/// Интерфейс для вызова обновления
/// </summary>
public interface IUpdatable {
    void OnUpdate();
}

/// <summary>
/// Менеджер для централизованного Update
/// </summary>
public class Updater : MonoBehaviour {
    
    List<IUpdatable> updatables = new List<IUpdatable>();

    public void Register(IUpdatable item) => updatables.Add(item);

    void Update() {
        foreach (var item in updatables) {
            item.OnUpdate();
        }
    }
}
  1. Уменьшайте размер текстур. Если не знаете как оптимизировать картинки при сохранении в редакторе, прогоните их через какой-нибудь онлайн оптимизатор. В некоторых случаях можно уменьшить размер на 20-40% от оригинала.

  2. Ещё про текстуры. Если используйте спрайты вне атласов — старайтесь делать картинки (да и сами атласы тоже) размером кратным степени 2, т.е. 1024×1024, 2048×2048. Особенно актуально для WebGL билдов.

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

  4. Старайтесь не использовать скрипты для автоматического расположения элементов UI: ContentSizeFitter, XXXLayoutGroup. Они удобные, но ресурсозатратные. Особенно если внутри большое количество элементов. Если количество и расположение элементов в процессе работы не будет меняться, можно отключить их в редакторе, после установки на нужные позиции.

  5. Ещё про UI. Используйте разные канвасы (Canvas). При изменении почти любого параметра у любого объекта, весь канвас перерисовывается полностью! Поэтому при построении сложного UI имеет смысл статичные элементы располагать на отдельном канвасе. И конечно не изменять UI в Update, а только по событиям, когда действительно что-то поменялось.

  6. Оптимизируйте прокручивающиеся списки. Родной ScrollView плохо справляется с большим количеством элементов. Используйте data-driven списки, например UIS 🙄 Также есть смысл заменить Mask на RectMask2D в списках, потому что обычная маска работает через шейдер с обрезкой по альфе с помощью дополнительного материала (больше DC), а прямоугольная маска работает только с прямоугольниками, отсеивает пиксели и вершины за пределами области, не трогая шейдеры и не создавая лишние материалы, соответственно меньше DC и нагрузки на GPU.

И списком статьи и видео по оптимизации:

Нет комментариев

    Ваш комментарий