Оптимизация использования оперативной памяти в играх
Краш без предупреждения на устройствах с 3 ГБ RAM — это не случайность. Это результат накопленного memory pressure, который iOS и Android тихо копят, пока не убивают процесс. SIGKILL приходит без лога, без стека, без объяснений. И разработчики часто списывают это на «нестабильность движка», хотя реальная причина — неуправляемый рост heap в Mono/IL2CPP и несвоевременная выгрузка ассетов.
Проблема в том, что Unity Memory Profiler часто показывает «всё нормально» — 400 МБ, казалось бы, некритично. Но native-память, которую удерживают Texture2D объекты без явных ссылок, там не видна. Нужен Total System Memory из Profiler window, а не только Managed Heap.
Три источника утечек, которые мы находим в каждом втором проекте
Незакрытые AssetBundle-ссылки. Разработчик загрузил AssetBundle, достал из него спрайт, но не вызвал bundle.Unload(false). Спрайт в памяти. Потом спрайт уничтожен, но нативный объект Texture2D всё ещё удерживается через WeakReference в ResourceManager. Через 10 загрузок/выгрузок локаций — память не возвращается. Классическая фрагментация Unity native heap. Решение: переход на Addressables с явным управлением временем жизни через AsyncOperationHandle.Release().
Дублирование текстур при смене сцен. При переходе между сценами через SceneManager.LoadScene с параметром LoadSceneMode.Single старая сцена выгружается, но если в новой сцене есть текстуры с теми же именами, загруженные через Resources.Load в коде — они могут оказаться в памяти дважды до вызова Resources.UnloadUnusedAssets(). На проектах с тяжёлыми сценами (100+ МБ текстур) это приводит к пику потребления памяти в момент перехода — именно тогда происходят крэши.
AudioClip с неправильными настройками Load Type. AudioClip с Load Type = Decompress On Load распаковывает PCM в память при загрузке и держит там. Для длинной музыкальной темы это может быть 50–80 МБ только для одного клипа. Правило: музыка → Streaming, короткие SFX → Compressed In Memory, критичные SFX с минимальной задержкой → Decompress On Load только если длина < 2 секунд.
Как работаем с памятью
Memory Profiler (com.unity.memoryprofiler) — основной инструмент. Делаем snapshot в нескольких точках игрового сессии: после старта, после загрузки первой сцены, в середине геймплея, после смены сцены. Сравниваем через Compare Snapshots. Ищем объекты, которые растут от снимка к снимку и не должны.
Особое внимание — Texture2D в списке Objects. Сортируем по Memory Size, открываем References для подозрительных объектов — смотрим, кто их держит.
Addressables как архитектурное решение. Переход с Resources на Addressables даёт явный контроль над временем жизни ассетов. AssetReference + LoadAssetAsync + Release — полный цикл без «магии». Настраиваем профили памяти через Addressables Analyze: Check Duplicate Bundle Dependencies находит ассеты, упакованные в несколько бандлов одновременно (типичная причина дублирования в памяти).
Из практики: мобильный экшен, 9 уровней. После прохождения 3 уровней подряд — краш на iPhone 8. Memory Profiler показал 847 МБ при старте 4 уровня. Источник — 12 уникальных UI-атласов, загруженных через Resources.Load в Lobby-сцене, не выгружались между уровнями. После переноса на Addressables с явным Release при входе в игровую сцену и Resources.UnloadUnusedAssets в coroutine — пик снизился до 480 МБ.
Пул объектов вместо Instantiate/Destroy. Каждый Instantiate выделяет новую память, каждый Destroy не возвращает её мгновенно — GC Alloc накапливается. Object Pool через Unity ObjectPool<T> (встроен с 2021 LTS) полностью устраняет эту категорию аллокаций для снарядов, врагов, VFX.
Этапы работы
- Снятие baseline-метрик через Profiler на целевом устройстве (не Editor)
- Серия Memory Profiler snapshots по игровому циклу
- Анализ топ-10 объектов по потреблению памяти
- Выявление источников утечек через Compare Snapshots
- Приоритизация по impact: текстуры → AudioClip → Managed Heap → пулинг
- Реализация исправлений с промежуточными замерами
- Нагрузочное тестирование: 1 час игровой сессии без рестарта
| Масштаб задачи | Ориентировочные сроки |
|---|---|
| Аудит памяти + отчёт | 2–4 дня |
| Устранение 2–3 конкретных источников утечек | 1–2 недели |
| Переход Resources → Addressables + оптимизация | 3–6 недель |
| Полная архитектурная переработка управления ассетами | 6–12 недель |
Стоимость — после аудита текущего состояния проекта.





