Большая часть моей профессиональной карьеры связана с Java и бэкенд‑разработкой: сервисами, базами данных, интеграциями, многопоточностью и проектированием систем. Но в какой‑то момент мне снова захотелось делать продукт, результат работы которого можно увидеть не только в логах и ответах API, но и непосредственно на экране.

Так я вернулся в геймдев и начал разрабатывать собственные игровые проекты. Мне был нужен инструмент для 2D‑игры с версиями для Android и браузера, который позволил бы сохранить общий код, не прятал бы происходящее за визуальным редактором и не заставлял бы отказываться от привычной Java‑экосистемы.

В итоге я выбрал libGDX.

Не потому, что считаю его лучшим движком для любой игры. И не потому, что Java идеально подходит для всех задач геймдева. Просто в моём случае требования проекта и возможности фреймворка совпали достаточно хорошо.

В этой статье расскажу, что именно мне дал libGDX, какую часть кода действительно удалось сделать общей и где кроссплатформенность закончилась.

libGDX — это не игровой движок в привычном смысле

libGDX — кроссплатформенный фреймворк с открытым исходным кодом для разработки 2D‑ и 3D‑игр на Java. Он поддерживает Windows, Linux, macOS, Android, iOS и браузеры.

В отличие от Unity, Godot и других движков со встроенным редактором, libGDX не предлагает центральную визуальную среду, внутри которой собирается вся игра. Здесь нет обязательной модели сцены, навязанной архитектуры проекта или единственного правильного способа организовать игровые объекты.

Фреймворк предоставляет строительные блоки:

  • игровой цикл и жизненный цикл приложения;

  • графику через OpenGL/OpenGL ES и соответствующие платформенные реализации;

  • обработку ввода;

  • аудио;

  • работу с файлами;

  • камеры и математические типы;

  • SpriteBatch, TextureAtlas, BitmapFont;

  • UI на основе Scene2D;

  • частицы;

  • базовые сетевые API;

  • утилиты для работы с коллекциями, пулами и сериализацией.

А то, как всё это собрать в игру, решает разработчик.

Для кого‑то отсутствие большого визуального редактора будет недостатком. Для меня это оказалось преимуществом: я привык мыслить кодом, интерфейсами, состояниями и потоками данных. libGDX позволил перенести этот подход в разработку игр.

Почему я остался на Java

Java — мой основной рабочий инструмент, поэтому на старте я уже умел пользоваться большей частью необходимой инфраструктуры:

  • IntelliJ IDEA и Android Studio;

  • Gradle;

  • Git;

  • профилировщиками и отладчиком;

  • многомодульными проектами;

  • привычными подходами к декомпозиции и тестированию.

Это не означает, что разработка игры стала похожа на создание очередного REST‑сервиса. У игрового приложения другой жизненный цикл, другие требования к времени кадра, ресурсам и задержкам. Но мне не пришлось одновременно осваивать новый язык, новую IDE и новый способ управления зависимостями.

При этом фразу «можно использовать всю Java‑экосистему» стоит воспринимать с оговоркой. Доступность библиотеки зависит от целевой платформы. То, что без проблем работает на десктопе, может оказаться несовместимым с Android или GWT. Особенно это касается рефлексии, нативного кода, потоков и библиотек, поставляемых без исходников.

Как выглядит игровой экран

Для разных состояний игры libGDX предлагает интерфейс Screen: отдельными экранами могут быть главное меню, игровой процесс, магазин и настройки.

Упрощённый экран в моём проекте можно представить так:

public final class GameScreen extends ScreenAdapter {

    private final GameWorld world;
    private final GameRenderer renderer;

    public GameScreen(GameWorld world, GameRenderer renderer) {
        this.world = world;
        this.renderer = renderer;
    }

    @Override
    public void render(float delta) {
        world.update(delta);
        renderer.render(world);
    }

    @Override
    public void dispose() {
        renderer.dispose();
    }
}

ScreenAdapter предоставляет пустые реализации методов жизненного цикла, поэтому экран переопределяет только необходимые методы. Игровое состояние и отрисовка разделены: GameWorld изменяет состояние, а GameRenderer отображает его.

В реальном проекте сюда добавляются обработка паузы, изменение размеров окна, управление ресурсами и переходы между экранами. Если используется физика, одного update(delta) обычно недостаточно: для стабильности симуляции нужен фиксированный шаг времени с аккумулятором.

libGDX не требует именно такой архитектуры. Можно использовать ECS, конечные автоматы, событийную модель или собственную систему компонентов. Это одновременно свобода и ответственность: если проект со временем превратится в набор связанных друг с другом глобальных объектов, фреймворк не остановит разработчика.

Один core, несколько платформ

Стандартный проект libGDX состоит из общего модуля и модулей целевых платформ:

                         core
              ┌───────────┼───────────┐
           android       lwjgl3       html
              │             │           │
gdx-backend-android  gdx-backend-lwjgl3  gdx-backend-gwt

В core размещается код игры: экраны, игровая модель, интерфейс, экономика, сохранение состояния и большая часть работы с ресурсами. Платформенные модули содержат точки входа и интеграции, которые невозможно сделать общими.

На десктопе используется LWJGL3, Android запускает игру через AndroidApplication, а браузерная версия — через GwtApplication.

Главное практическое преимущество для меня — возможность разрабатывать и отлаживать игру на десктопе. Запуск занимает меньше времени, профилирование удобнее, а для проверки большинства игровых механик не нужен эмулятор или физическое устройство. После этого тот же core подключается к Android‑ и Web‑сборкам.

Но «один код для всех платформ» не означает, что весь проект становится общим.

Где заканчивается кроссплатформенность

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

Поэтому в общем модуле я использую интерфейс платформенных сервисов:

public interface PlatformServices {

    TargetPlatform platform();

    BillingService billing();

    AdsService ads();

    CloudSaveService cloud();

    void alert(String message);
}

Игровой код зависит только от этого контракта:

public final class GameApplication extends Game {

    private final PlatformServices services;

    public GameApplication(PlatformServices services) {
        this.services = services;
    }

    @Override
    public void create() {
        services.billing().init();
        services.ads().init();
        setScreen(new MainMenuScreen(services));
    }
}

Android‑модуль передаёт реализацию, работающую с мобильными SDK:

initialize(
    new GameApplication(new AndroidPlatformServices(this)),
    configuration
);

Web‑модуль создаёт собственную реализацию. Для десктопной разработки можно использовать заглушки, которые не показывают рекламу и имитируют успешную покупку или сохранение.

Такое разделение оказалось полезно не только для сборки. Платёжный или рекламный SDK можно заменить, не изменяя игровую логику. Кроме того, для платформенных функций проще писать тестовые реализации.

Есть и ещё одна деталь: callbacks внешних SDK могут приходить не из потока отрисовки. Если обработчик должен изменить сцену или игровое состояние, я возвращаю выполнение в основной поток через Gdx.app.postRunnable(...).

Web/GWT — действительно отдельная платформа

Поначалу Web выглядит как ещё один модуль Gradle. На практике GWT добавляет больше ограничений, чем Android или desktop.

GWT компилирует Java‑код в JavaScript и должен видеть исходный код используемых классов. Если зависимость поставляется только в виде скомпилированного JAR‑файла без подходящего GWT‑модуля и исходников, браузерная сборка её не примет. Именно отсюда появляются ошибки вида:

No source code is available for type ...
did you forget to inherit a required module?

Кроме того, GWT поддерживает не всю стандартную библиотеку Java. Есть ограничения на рефлексию, многопоточность, нативные зависимости и некоторые способы сериализации. Даже подключение библиотеки, которая формально написана на Java, не гарантирует её работу в браузере.

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

У libGDX развивается и вариант Web‑сборки на TeaVM, в том числе более удобный для Kotlin, но стандартный HTML5-бэкенд по‑прежнему основан на GWT. Его нельзя воспринимать как обычный запуск JVM‑приложения в браузере.

Android тоже не становится «бесплатным»

libGDX скрывает значительную часть различий между платформами, но не освобождает от знания Android.

Для публикации игры всё равно приходится разбираться с:

  • Android SDK;

  • версиями JDK, Gradle и Android Gradle Plugin;

  • compileSdk и targetSdk;

  • AndroidManifest.xml и разрешениями;

  • жизненным циклом Activity;

  • разными разрешениями, плотностью и вырезами экранов;

  • сборкой APK и AAB;

  • R8 и правилами ProGuard;

  • требованиями Google Play;

  • интеграциями платежей, рекламы и аналитики;

  • поведением игры при сворачивании и восстановлении приложения.

При обновлении Android Gradle Plugin или targetSdk игровую механику переписывать обычно не нужно. Но платформенный модуль, манифест, зависимости и интеграции SDK всё равно требуют отдельного внимания.

То же относится к памяти и жизненному циклу. Текстуры, звуки, шрифты и другие ресурсы занимают нативную память и не освобождаются только потому, что Java‑объект стал недоступен. Ресурсы, реализующие Disposable, необходимо освобождать явно или передавать под управление AssetManager.

Производительность: вместо времени запроса — время кадра

Бэкенд‑разработчик обычно думает о задержке запроса, пропускной способности и нагрузке на сервер. В игре появляется ещё одна величина — бюджет кадра.

При 60 FPS на полный цикл остаётся около 16,7 мс. За это время нужно обработать ввод, обновить состояние, выполнить расчёты и сформировать кадр. Если какая‑то операция регулярно выходит за этот бюджет, пользователь видит снижение FPS или рывки.

Упрощённо цикл выглядит так:

Input → Update game state → Physics → Render → Next frame

Особенно неприятны случайные пики времени кадра. Их могут вызывать загрузка ресурса в неподходящий момент, слишком большое число draw calls, сложная обработка UI или создание множества временных объектов с последующей работой сборщика мусора.

В чувствительном коде я стараюсь:

  • не создавать объекты на каждом кадре без необходимости;

  • переиспользовать временные структуры и применять пулы;

  • объединять текстуры в атласы;

  • группировать отрисовку через SpriteBatch;

  • загружать тяжёлые ресурсы заранее;

  • явно управлять временем жизни ресурсов;

  • измерять проблему профилировщиком, а не оптимизировать вслепую.

В libGDX есть собственные коллекции, например Array и ObjectMap, а также Pool и Pools. Они полезны там, где необходимо уменьшить количество временных объектов. Использовать их повсюду не требуется: сначала стоит найти место, которое действительно влияет на время кадра.

Мне нравится, что фреймворк позволяет начинать с высокоуровневых инструментов, а при необходимости спускаться до работы с Mesh, ShaderProgram и графическими вызовами через Gdx.gl. Контроль остаётся у разработчика, но вместе с ним остаётся и возможность допустить ошибку.

Почему не Unity или Godot

Я не считаю libGDX универсально лучшим выбором.

Если проекту нужны сложные 3D‑сцены, удобная работа художников внутри редактора, визуальное программирование или большая экосистема готовых ресурсов, полноценный игровой движок может оказаться рациональнее.

Мои исходные условия были другими:

  • основной язык — Java;

  • игра преимущественно двухмерная;

  • значительная часть сложности находится в логике и интеграциях;

  • нужны Android‑ и Web‑версии;

  • архитектуру удобнее контролировать из кода;

  • отсутствие обязательного визуального редактора не является проблемой.

При таких вводных libGDX оказался естественным выбором. Но у свободы есть цена: структуру проекта, управление зависимостями, навигацию между экранами, сохранения и многие другие механизмы приходится проектировать самостоятельно.

Не заброшен ли libGDX

Для технологии, на которой планируется поддерживать игру несколько лет, состояние проекта имеет значение.

На момент написания статьи актуальная стабильная версия — libGDX 1.14.2, опубликованная в июне 2026 года. Предыдущая версия, 1.14.1, вышла в мае. Проект продолжает получать исправления и новые возможности, а вокруг него сохраняется экосистема расширений и инструментов.

libGDX распространяется под лицензией Apache 2.0. Для независимого проекта это означает отсутствие лицензионных платежей за использование фреймворка и достаточно понятные условия распространения.

Частота релизов сама по себе не является главным критерием. Для меня важнее, что проект поддерживается, сохраняет обратную совместимость и не требует регулярно переписывать игру из‑за изменения основной модели разработки.

Где я использую ИИ

ИИ‑инструменты помогают мне быстрее исследовать незнакомые API, разбирать ошибки Gradle и GWT, создавать черновые реализации и проверять варианты архитектуры.

Но в геймдеве особенно хорошо видна граница такой помощи. Сгенерировать отдельный класс несложно. Значительно труднее проверить, как он ведёт себя внутри игрового цикла, не создаёт ли объекты каждый кадр, корректно ли освобождает ресурсы и работает ли одинаково на Android и в браузере.

Поэтому сгенерированный код я воспринимаю как черновик. Ответственность за архитектуру, измерения, тестирование на устройствах и конечное поведение игры остаётся на разработчике.

Что в итоге

После практической работы с libGDX я бы сформулировал его главное преимущество так: он позволяет Java‑разработчику достаточно быстро перейти от идеи к работающей игре, сохранив контроль над кодом и архитектурой.

Я могу разрабатывать игровую механику на десктопе, использовать общий core для Android и Web, изолировать платформенные SDK за интерфейсами и при необходимости разбираться с графикой на более низком уровне.

При этом libGDX не отменяет сложность платформ. Android остаётся Android, а GWT — отдельной средой со своими ограничениями. Фреймворк не проектирует архитектуру за разработчика и не спасает от неправильного управления ресурсами.

Для моей 2D‑игры этот баланс свободы и готовой инфраструктуры оказался подходящим. Я остаюсь прежде всего Java‑разработчиком, но результат моей работы теперь можно не только увидеть на экране — с ним можно взаимодействовать.

В следующей статье я хотел бы подробнее разобрать практическую часть: структуру модулей, реализацию PlatformServices, подключение платежей и рекламы, а также проблемы, которые возникли при сборке одного core для Android и GWT.

Если вы используете Java или Kotlin в геймдеве, будет интересно узнать, какой архитектурный подход выбрали вы и где для вас проходит граница между фреймворком и полноценным игровым движком.

Полезные ссылки