Код мобильной игры — это не одна программа, а несколько слоёв, которые работают с разной частотой: рендер рисует кадры, логика считает правила мира, физика догоняет их с фиксированным шагом, а платформенная обвязка молча держит всё это в рамках бюджета батареи и памяти.
Ниже — разбор того, из чего складывается код мобильной игры: архитектурные слои, движки и языки, игровой цикл, примеры реализации, бюджеты производительности и типичные ошибки, из-за которых проект начинает «тормозить» уже на среднем устройстве.
Из чего состоит код мобильной игры
Мобильная игра отличается от десктопной не только управлением. У неё жёстче ограничения по теплу, памяти и заряду, а пользователь может свернуть приложение в любой момент — в том числе посреди боя. Поэтому код мобильной игры строится так, чтобы логика была отделена от отрисовки, а любое состояние можно было сохранить и восстановить за доли секунды.
Практически любой проект — от гиперказуальной головоломки до 3D-шутера — раскладывается на одни и те же уровни ответственности.
Игровой цикл: сердце любой мобильной игры
Игровой цикл — это бесконечный повтор одной и той же последовательности: принять ввод, обновить состояние, отрисовать кадр. На мобильных платформах цикл не принадлежит разработчику целиком: операционная система сама решает, когда вызвать следующий кадр, и может прервать работу в любой момент.
// Упрощённый цикл: Unity, C#
void Update()
{
float dt = Time.deltaTime; // сколько прошло с прошлого кадра
input.Tick(dt); // 1. ввод и UI
world.Step(dt); // 2. логика и анимация
renderer.Draw(world); // 3. отрисовка
}
Проблема такой схемы — переменный шаг. На слабом устройстве кадр может занять 40 мс вместо 16, и физика «прыгнет» вперёд, ломая коллизии. Надёжнее разделить два ритма: логику считать фиксированными шагами, а отрисовку — как получится, с интерполяцией между состояниями.
accumulator += frameTime;
while (accumulator >= FIXED_DT) { // FIXED_DT = 1/60 с
physics.Step(FIXED_DT); // детерминированный шаг
accumulator -= FIXED_DT;
}
alpha = accumulator / FIXED_DT; // доля «недоигранного» шага
renderer.Interpolate(alpha); // плавная картинка без рывков
Такой цикл даёт предсказуемое поведение: одинаковый вход при одном и том же наборе шагов приводит к одинаковому результату. Это база для реплеев, сетевой синхронизации и воспроизводимых тестов.
Движки и языки: чем пишут код мобильных игр
Выбор движка определяет не только синтаксис, но и то, сколько времени уйдёт на сборку, какой будет размер приложения и насколько легко будет починить падение в чужом коде.
| Движок | Язык | Сильная сторона | Ограничение |
|---|---|---|---|
| Unity | C# | Огромная экосистема, готовые плагины, быстрый прототип | Сборка мусора и лишний вес пустого проекта |
| Unreal Engine | C++, Blueprints | Качественная графика из коробки, сильный инструментарий | Требователен к железу и к размеру дистрибутива |
| Godot | GDScript, C# | Лёгкий, открытый, простой в изучении | Меньше готовых интеграций и специалистов на рынке |
| Cocos2d-x | C++, Lua, JS | Минимальный оверхед для 2D и гиперказуальных жанров | Инструменты и редактор скромнее, чем у лидеров |
| Собственный движок | C++, Rust, Swift/Kotlin, C# | Полный контроль над производительностью и размером | Дорого: месяцы на то, что уже сделано за других |
Отдельная ветка — нативная разработка на Swift и Kotlin с графикой через Metal, Vulkan или OpenGL ES. Она оправдана, когда нужен предельно маленький вес, мгновенный запуск и нестандартный рендер: например, аркада с процедурной графикой или утилита-анимация.
Как организовать код мобильной игры внутри движка
Движок даёт инструменты, но не диктует структуру. Три подхода встречаются чаще всего, и у каждого своя зона комфорта.
Классы вроде Enemy наследуются от Character, а тот — от Entity.
Сущность — пустой контейнер, поведение добавляется блоками: здоровье, движение, стрельба.
Данные лежат в плотных массивах, логика — в отдельных системах, которые эти массивы обходят.
На практике чистых схем почти не бывает. Разумный компромисс — держать в компонентах то, что различается между объектами, а тяжёлые вычисления выносить в системы с линейным обходом массивов.
struct Position { float x, y; };
struct Velocity { float x, y; };
// Данные — рядом друг с другом, обход — линейный
void MovementSystem(float dt)
{
for (int i = 0; i < activeCount; ++i) {
positions[i].x += velocities[i].x * dt;
positions[i].y += velocities[i].y * dt;
}
}
Такой блок легко заменить на SIMD-версию или перенести в отдельный поток — именно поэтому крупные проекты с тысячами объектов переходят на систему из компонентов и систем.
// Плохо: вызов каждые 100 мс порождает мусор в куче
Instantiate(bulletPrefab, muzzle.position, rotation);
// Хорошо: объект берётся из заранее созданного пула
Bullet b = pool.Rent();
b.Reset(muzzle.position, rotation);
// ... и возвращается обратно
pool.Return(b);
Сборка мусора — главный источник микрорывков на Android. Пул объектов убирает регулярные аллокации и делает время кадра ровнее.
Бюджет кадра: куда уходит время на среднем смартфоне
При 60 кадрах в секунду на всё про всё отводится около 16,6 миллисекунды. Распределение ниже — усреднённая картина для 2D-проекта среднего масштаба.
| Ресурс | Комфортный уровень | Причина деградации |
|---|---|---|
| Память | до 1,5 ГБ на среднем устройстве | Атласы текстур без сжатия, утечки подписок на события |
| Аллокации | не более 1–2 КБ на кадр | Создание объектов в цикле обновления, строковые операции |
| Вызовы отрисовки | до 150–200 на кадр | Отдельный материал на каждый объект вместо общего атласа |
| Накопитель | рост приложения до 150–200 МБ | Аудио в несжатом виде, дубликаты ресурсов в сборке |
Размер приложения как часть кода
Вес пустой сборки — это плата за инструмент. Чем выше столбик, тем больше пользователей отвалится на этапе загрузки из магазина.
iOS и Android: где код ведёт себя по-разному
| Аспект | iOS | Android |
|---|---|---|
| Управление памятью | Предсказуемое, единые правила для всех устройств | Сильно зависит от производителя и оболочки |
| Графический интерфейс | Metal, единый набор расширений | Vulkan и OpenGL ES, десятки разных чипсетов |
| Потоки | Стабильное поведение, строгие требования к главному потоку для UI | Агрессивные энергосберегающие ограничения на ядрах |
| Фон и сворачивание | Быстрая заморозка процесса | Процесс может быть завершён без предупреждения |
Отсюда практическое правило: любое сохранение должно переживать принудительное завершение процесса. Если прогресс держится только в оперативной памяти, часть игроков потеряет его уже в первый вечер.
Что проверяют перед выпуском
| Типичная ошибка | Как проявляется | Как исправляют |
|---|---|---|
| Логика внутри обработчиков интерфейса | Правила расходятся между экранами, баги воспроизводятся редко | Единая модель состояния, интерфейс только читает её |
| Обработка касаний в каждом кадре | Задержка управления, «плавающие» нажатия | Событийная обработка, опрос только активных жестов |
| Физика на переменном шаге | Объекты проскакивают препятствия на просадках кадров | Фиксированный шаг с накоплением времени |
| Строковые ключи в горячем коде | Лишние аллокации, рост времени кадра | Числовые идентификаторы, кэширование результатов |
Код мобильной игры держится на трёх вещах: предсказуемом игровом цикле с фиксированным шагом, чётком разделении логики и представления и уважении к бюджету кадра на среднем устройстве.
Движок, язык и архитектурный подход — вторичны по отношению к этим принципам. Проект, где они соблюдены, переносится на другой движок за считанные недели. Проект, где они нарушены, приходится переписывать целиком.
История развивается так, как решаете вы ✍️
Это не книга с готовым финалом и не игра с фиксированными ответами. В нашем телеграм-боте вы сами ведёте диалог с ИИ-персонажем: задаёте тон, принимаете решения, меняете ход событий. Захотели романтики — будет романтика. Захотели интриги — сюжет повернёт туда. Каждая партия — уникальная.
Попробовать прямо сейчас 👉 https://clck.ru/3Ta8kQ

