Реализация LOD-системы для мобильной игры

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Реализация LOD-системы для мобильной игры
Средний
~2-3 дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    743
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1160
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    562

Персонаж с 80 000 полигонов в двух метрах от камеры выглядит отлично. Тот же персонаж на расстоянии 50 метров занимает 40×60 пикселей на экране — а GPU всё ещё рендерит 80 000 треугольников. LOD (Level of Detail) — ключевой инструмент управления GPU-бюджетом в мобильных играх. Мы обладаем 5+ лет опыта в мобильной разработке и реализовали более 10 проектов с LOD-оптимизацией. Правильно настроенная LOD-система незаметна для игрока и хорошо видна в профайлере. Хотите внедрить LOD? Закажите аудит производительности — получите консультацию наших инженеров.

Реализация LOD-системы в Unity и Unreal Engine для мобильных проектов

Почему LOD критичен для мобильных игр?

Мобильные GPU имеют ограниченную память и вычислительную мощность. Без LOD каждый объект рендерится с максимальным числом полигонов независимо от расстояния. Это ведёт к падению FPS и перегреву устройства. LOD позволяет на лету выбирать упрощённую версию объекта, сокращая нагрузку без потери качества на ближних планах. Встроенные механизмы в Unity и Unreal Engine дают готовые решения, но требуют тонкой настройки под целевое железо.

LOD в Unity (URP/Built-in)

Unity LOD Group — базовый компонент. Добавляем LOD Group к объекту, назначаем mesh-рендереры для каждого уровня:

Уровень Screen Space % Полигонов Назначение
LOD 0 >30% 80 000 Крупный план, кат-сцены
LOD 1 15–30% 20 000 Активные NPC средней дистанции
LOD 2 5–15% 5 000 Фоновые персонажи
LOD 3 1–5% 800 Дальние объекты
Culled <1% Объект не рендерится

Ключевой параметр: LOD Bias в QualitySettings. На мобильных платформах — 0.5–0.7 против 1.0 на PC. Это смещает переходы между LOD-уровнями ближе к камере:

// Устанавливаем в зависимости от производительности устройства
QualitySettings.lodBias = SystemInfo.graphicsMemorySize > 4096 ? 0.75f : 0.5f;

Формула: LOD Bias = Screen Space % / Actual Distance. Для мобильных устройств с низкой памятью используем 0.5, для флагманов — 0.75. Это обеспечивает плавный переход.

Cross-fade transitions

Резкое переключение между LOD вызывает неприятный артефакт — «поппинг». LOD Group → Fade Mode → Cross Fade включает плавный переход через дизеринг. Это стоит дополнительного GPU-времени, поэтому используем только для перехода LOD 0→1 как самого заметного. Для LOD 2→3 поппинг практически незаметен. Согласно Unity Manual, cross-fade обязателен для mobile-проектов с плотной геометрией.

lodGroup.fadeMode = LODFadeMode.CrossFade;
lodGroup.animateCrossFading = true;

На URP в шейдере добавьте #pragma multi_compile _ LOD_FADE_CROSSFADE и UNITY_APPLY_DITHER_CROSSFADE(i.pos).

Как LOD настраивается в Unreal Engine для мобильных игр?

В Unreal Engine StaticMeshComponent и SkeletalMeshComponent поддерживают LOD из коробки. Настройка в Static Mesh Editor: вкладка LOD Settings. Auto LOD generation доступна начиная с версии 4.20:

// В Static Mesh Editor → LOD Settings
Number of LODs: 4
LOD 1: Reduction Settings → Triangle Percent = 50%
LOD 2: Triangle Percent = 20%
LOD 3: Triangle Percent = 8%

Для мобильных проектов полезно задать r.StaticMeshLODDistanceScale 0.5 в DefaultEngine.ini — LOD переходы будут происходить вдвое ближе к камере. Для Skeletal Mesh в LOD 2+ удаляйте кости, которые не видны на дистанции, через LOD Reduction Settings → Remove Bones Below. Например, убираем пальцы и мелкие кости лица.

HLOD (Hierarchical LOD) для открытых миров

В сценах с сотнями объектов HLOD объединяет несколько статических мешей в один прокси-меш на дальних дистанциях. В Unity для этого используется пакет HLOD System (com.unity.hlod). В Unreal HLOD встроен в World Settings → HLOD. Принцип: 100 отдельных деревьев на дистанции 200 метров объединяются в один меш с одним draw call. Это снижает количество draw calls с 100 до 1 для всего кластера. HLOD в 4 раза эффективнее обычного LOD для таких сцен.

Кейс из нашей практики: открытый мир на Galaxy A34

Изометрическая RPG: при входе в город (более 400 объектов) частота кадров падала с 60 до 22. Draw calls достигали 680 в кадре. Статика не имела LOD и батчинга.

Шаги оптимизации:

  1. Назначили LOD Group на все здания и деревья — четыре уровня, Culled при 2% Screen Space.
  2. Включили Static Batching для объектов без LOD (камни, бочки) — draw calls снизились с 680 до 220.
  3. Настроили HLOD для дальних кварталов города — прокси-меш с 15К полигонов вместо 400 отдельных объектов.
  4. Установили LOD Bias 0.6 для Android через Quality Settings.

Результат: FPS восстановился до 54 в том же районе карты. Таким образом, LOD увеличил FPS в 2.5 раза по сравнению с исходной конфигурацией. Экономия затрат на разработку составила до $10,000 за счет снижения времени на профайлинг. Аналогичный проект позволил сэкономить $8,000 на QA благодаря стабильному FPS.

Программный LOD для UI и эффектов

LOD применим не только к геометрии. Для частиц в Unity используйте Particle System → LOD Level — уменьшайте Max Particles и emission rate для источников на удалении. Взрыв на расстоянии 100 метров вполне реалистично выглядит с 10 частицами вместо 200.

Качество теней также можно регулировать: на мобильных платформах установите ShadowDistance в пределах 30–50 метров:

QualitySettings.shadowDistance = 40f;
QualitySettings.shadowCascades = 2;   // хватит двух каскадов

Инструменты верификации LOD

Для визуальной проверки в Unity используйте LOD Group Visualizer (Scene View → Debug Mode → LOD). Цветовая индикация: зелёный — LOD 0, жёлтый — LOD 1, красный — LOD 2+. Программно текущий уровень можно получить так:

var lodGroup = GetComponent<LODGroup>();
var lods = lodGroup.GetLODs();
// Camera.CalculateLODDistanceFactor помогает предварительно рассчитать расстояние

Сравнение подходов: Unity LOD vs Unreal LOD

Критерий Unity Unreal Engine
Настройка LOD Group Через компонент, проценты Screen Space В редакторе Static Mesh, вручную или авто
HLOD Пакет HLOD System Встроенный в World Settings
Cross Fade Поддерживается через шейдеры Встроенный механизм
Инструменты отладки LOD Group Visualizer LOD колоризация в редакторе

В обеих платформах LOD — основа оптимизации, но Unreal предлагает более полное решение для открытых миров, тогда как Unity гибче в настройках под конкретное устройство.

Что входит в работу по внедрению LOD-системы?

Мы предлагаем:

  • Аудит текущей сцены: анализ draw calls, полигонов, узких мест.
  • Проектирование LOD-групп для ключевых объектов.
  • Настройка LOD Bias, Fade Mode, Cross Fade под целевую платформу.
  • Интеграция HLOD для больших уровней.
  • Тестирование на реальных устройствах с профайлингом.
  • Оптимизация шейдеров и теней для LOD-переходов.
  • Предоставление документации и руководства по поддержке.

Сроки и стоимость

Настройка LOD для 20–30 объектов занимает 2–3 дня. Полная LOD-система с HLOD для всей игры — от одной до двух недель. Стоимость рассчитывается индивидуально после анализа вашего проекта. Получите консультацию по внедрению LOD. Мы гарантируем рост FPS и стабильную производительность. Закажите аудит уже сегодня.

Unity Manual: LOD Group Unreal Engine Documentation: LOD

Оптимизация мобильных приложений: cold start, память, батарея, FPS, профилирование

Приложение с временем холодного старта 4+ секунды теряет пользователей ещё до первого экрана. Android Vitals в Google Play Console прямо влияют на ранжирование в поиске: приложения с плохими метриками получают меньший organic reach. Apple аналогично мониторит crash rate и время запуска через MetricKit. Оптимизация — это не «сделать быстрее», а понять где именно теряется время и что с этим делать.

Cold Start: где убивается время до первого кадра

Cold start — запуск приложения, когда процесс не существует в памяти. На Android это время от нажатия на иконку до Activity.onWindowFocusChanged(hasFocus = true). На iOS — от tap до viewDidAppear первого экрана.

Android: main thread перегружен при инициализации

Application.onCreate() — главный враг быстрого старта на Android. Разработчики инициализируют здесь всё подряд: Firebase, Analytics, базу данных, HTTP-клиент, DI-контейнер. Каждый SDK добавляет 20–200 мс на main thread.

Инструмент для диагностики: Android Studio Profiler → App Startup. Показывает граф инициализации с временем каждого компонента. Альтернатива — Tracing.beginSection("MyInitTag") в коде + systrace.

Решение: App Startup Library (Jetpack) с явным графом зависимостей инициализаторов. Компоненты, нужные только в конкретных сценариях, инициализируются лениво — by lazy {} или initializer с флагом lazyInit. Firebase Analytics, например, не нужен до первого пользовательского действия — его инициализацию можно отложить.

ContentProvider-ы, автоматически добавляемые SDK через AndroidManifest merge, тоже запускаются при старте. tools:node="remove" в манифесте позволяет отключить конкретный провайдер и инициализировать SDK вручную в нужный момент.

Ещё одна грабля: Room.databaseBuilder().build() на main thread. Это синхронная операция создания/открытия файла БД — на медленных устройствах занимает 50–300 мс. Переносим в coroutine с Dispatchers.IO, в ViewModel через viewModelScope.launch.

iOS: Dyld linking и +load

На iOS cold start делится на pre-main (до вызова main()) и post-main. Pre-main — время загрузки dylib, rebase/binding, Objective-C runtime initialization и выполнения +load методов.

Xcode Instruments → App Launch template показывает время pre-main и post-main раздельно. DYLD_PRINT_STATISTICS=1 в схеме запуска выводит детальное время загрузки в консоль.

Что убивает pre-main:

  • Много динамических библиотек (каждая dylib — накладные расходы на линковку). CocoaPods добавляет отдельную dylib на каждый pod. Решение: Swift Package Manager со статической линковкой (type: .static) или use_frameworks! :linkage => :static в CocoaPods.
  • +load методы в Objective-C — выполняются синхронно при загрузке класса, до main(). Сторонние SDK могут злоупотреблять этим. +initialize — ленивый аналог, вызывается при первом обращении к классу.

Post-main — application(_:didFinishLaunchingWithOptions:). Та же история что на Android: синхронная инициализация всего. lazy var для сервисов, которые не нужны немедленно. SwiftUI @StateObject инициализирует объект только когда View появляется — это уже встроенная ленивость.

Целевые метрики (App Store рекомендации): cold start < 400 мс для простых приложений, < 2 секунды для сложных. Warm start (процесс в памяти, но Activity/Scene пересоздаётся) — < 1 секунда.

Память: утечки, OOM, excessive pressure

Утечка памяти в iOS — retention cycle: объект A держит ссылку на B, B держит на A, ни один не освобождается. Классика: Timer с self в замыкании без [weak self]. Timer удерживает замыкание, замыкание удерживает self (ViewController), ViewController не освобождается при закрытии. Instruments → Leaks или Memory Graph Debugger в Xcode — находит живые объекты, которых не должно быть.

На Android garbage collector управляет памятью, но утечки всё равно случаются. Activity или Fragment, удерживаемые через статическую ссылку, singleton, или Handler/Runnable после onDestroy — классика. LeakCanary — обязательный инструмент в debug-сборке. Добавляется одной зависимостью debugImplementation "com.squareup.leakcanary:leakcanary-android" и автоматически детектирует утечки с полным стектрейсом.

OutOfMemoryError чаще всего происходит из-за загрузки изображений. Bitmap в памяти занимает ширина × высота × 4 байта. Изображение 4000×3000 px — 48 МБ в памяти, независимо от размера файла на диске. Glide / Coil правильно обрабатывают это: загружают с даунсемплингом под размер View, кешируют в LRU-кеш. Загружать в ImageView без Glide/Coil через BitmapFactory.decodeFile — путь к OOM на устройствах с 2 ГБ RAM.

На Flutter Dart VM имеет свой GC, но нативные ресурсы (изображения, текстуры) не управляются Dart GC. Image.network кеширует изображения в памяти без автоматического освобождения при выходе из дерева виджетов — при длинных списках с картинками используем cached_network_image с правильным memCacheWidth/memCacheHeight.

FPS и UI Performance

60 FPS — 16.67 мс на кадр. 120 FPS (ProMotion) — 8.33 мс. Всё что занимает больше на main thread — джанк.

Типичные причины просадок FPS:

На iOS: синхронная декодировка изображений в cellForRowAt. Когда ячейка таблицы появляется, UIImage(contentsOfFile:) декодирует JPEG/PNG на main thread — видно как заторможенный скролл на длинных списках. Решение: UIImage.preparingForDisplay() (iOS 15+) или ImageIO с kCGImageSourceCreateThumbnailWithTransform в background queue, результат через DispatchQueue.main.async.

На Android: RecyclerView.Adapter.onBindViewHolder с синхронными операциями. Базы данных, файловая система, синхронные сетевые запросы на main thread — StrictMode.ThreadPolicy с detectAll().penaltyLog() в debug-сборке покажет все нарушения.

На Flutter: build() метод вызывается часто, он должен быть дешёвым. setState() на верхнем виджете пересобирует всё дерево. const конструкторы, RepaintBoundary, разбиение на мелкие виджеты с локальным стейтом — основные инструменты. Flutter DevTools → Performance показывает janky frames (красные) с причинами.

Профилирование Compose: Recomposition Highlighter и трассировка через Trace.beginSection в @Composable. remember для дорогих вычислений, derivedStateOf для computed values, LazyColumn вместо Column + forEach для длинных списков.

Батарея: Wake locks, WorkManager, сетевые запросы

Приложение в топе по расходу батареи — пользователь видит это в настройках и удаляет. Android Battery Historian (из ADB bug report) показывает детальный timeline: wake locks, wakeups, network activity, sensor usage.

Основные потребители энергии:

  • Постоянный GPS (разбираем в maps-geo)
  • Polling сети каждые N секунд вместо push
  • Holding wake lock дольше необходимого
  • Excessive AlarmManager wakeups

WorkManager с Constraints — правильный способ планировать фоновые задачи: setRequiredNetworkType, setRequiresBatteryNotLow, setRequiresCharging. ОС батчирует задачи и выполняет в удобное время.

На iOS BGTaskScheduler с BGProcessingTaskRequest (для тяжёлых задач при зарядке) и BGAppRefreshTaskRequest (для лёгких обновлений) — система решает когда выполнять, разработчик только регистрирует и реализует логику.

Батчинг сетевых запросов: вместо 10 отдельных запросов в течение минуты — один батч запрос. Меньше радио-активностей (LTE radio потребляет много при инициализации соединения), меньше wakeups.

Инструменты профилирования

Платформа Инструмент Что показывает
iOS Xcode Instruments (Time Profiler) CPU, call stack, горячие методы
iOS Allocations Живые объекты, пики памяти
iOS Leaks Retention cycles
iOS MetricKit Производственные метрики (crash rate, hang rate, launch time)
Android Android Profiler CPU, Memory, Network, Energy
Android Systrace / Perfetto System-level трейсы
Android LeakCanary Утечки памяти
Android Battery Historian Энергопотребление
Flutter Flutter DevTools Recomposition, frame rendering, memory
Flutter Dart Observatory Dart VM profiling

MetricKit на iOS — особенно ценен: реальные данные с устройств пользователей, а не симулятора. MXMetricManager получает агрегированные метрики раз в сутки: MXAppLaunchMetric, MXHangDiagnostic, MXCPUExceptionDiagnostic. Диагностики по hang и CPU-exceptions содержат стектрейс с реального устройства — золото для диагностики production-проблем.

Процесс оптимизации

Начинаем с измерения, не с предположений. Инструменты выше дают цифры: конкретное время cold start, конкретный объём памяти, конкретные кадры с просадкой. Потом — приоритизация по impact: что больше всего влияет на пользовательский опыт именно в этом приложении.

Аудит производительности существующего приложения: 3–5 рабочих дней. Реализация оптимизаций — от недели до двух месяцев в зависимости от запущенности проблем и архитектуры кода.