В первой статье я уже говорил о своей любви к компьютерным играм — что, в общем‑то, неудивительно. Но всерьёз, вплоть до создания собственных небольших прототипов (так называемых демок), я занялся этим только в довольно зрелом возрасте — где‑то с 2021 года и до сих пор. Всё началось с того, что я открыл для себя мир Java: для моих программистских навыков он оказался на удивление простым. Ну и да, про 2 млн устройств — это, конечно, избитая шутка. Больше всего в разработке приложений на JavaFX меня впечатлила простота реализации: приложения получаются легко и быстро. Конечно, можно допустить ошибки, и тогда производительность окажется низкой. Но я по‑прежнему считаю, что это очень удобная технология для создания настольных приложений.

Забавно, но именно работа над промышленными системами моделирования научила меня ценить структурированность и надёжность кода — качества, которые я теперь применяю и в игровых проектах.

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

  • Создавать масштабируемые системы

  • Оптимизировать производительность

  • Работать с большими объёмами данных

  • Обеспечивать стабильность работы

Эти навыки оказались невероятно полезными при разработке игр на FXGL. Интересно наблюдать, как принципы, отработанные на серьёзных промышленных проектах, находят применение в создании игровых механик.

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

Возможно, именно такой разносторонний подход и делает разработку по-настоящему интересной — когда ты можешь применять накопленные знания в самых разных областях, от моделирования энергосистем до создания игровых миров.

И вот, внезапно, мне попадается вот эта ссылка на страничку игрового движка https://almasb.github.io/FXGL/.  Собственно, FXGL — это игровой фреймворк (движок) для разработки игр на Java и JavaFX, позволяющий легко создавать 2D-игры (платформеры, аркады, RPG) и приложения с сложной анимацией. Поддерживает Windows, Mac, Linux, Android, iOS и Web, включает физику, анимации, работу с частицами, систему Entity-Component и встроенную панель отладки, а игры можно упаковывать в единый .jar-файл. Не откладывая в долгий ящик, я решил сделать, пару демок - игрушек. 

Подробнее об архитектуре и workflow можно прочитать в wiki проекта, мне кажется здесь все довольно понятно https://github.com/AlmasB/FXGL/wiki/FXGL-11 .

 

Башни, башни и еще раз башни!

Выбор прототипа в жанре Tower Defense для первого проекта оказался неслучайным. Простота реализации базовых механик этого жанра идеально подошла для знакомства с возможностями движка. После того как я открыл для себя Java и начал работать с JavaFX, мне хотелось создать что-то более интерактивное, и Tower Defense показался оптимальным вариантом.

Как игра выглядит сейчас
Как игра выглядит сейчас

Модульная структура проекта позволила постепенно внедрять различные компоненты без необходимости сразу погружаться в сложные механики. Можно было начать с простейших элементов — системы волн врагов и базовых башен — и постепенно усложнять проект. Даже небольшой магазин и прокачку башен удалось добавить.

Всплывающее меню магазина
Всплывающее меню магазина

Кроме того, жанр дал возможность поэкспериментировать с ключевыми аспектами разработки: системами коллизий, анимацией, экономической моделью и визуальными эффектами. Всё это было крайне полезно для понимания возможностей FXGL и его ограничений.

Важным фактором стало то, что даже простая реализация даёт ощутимый результат — работающую игру, пусть и с базовыми механиками. Это мотивировало продолжать работу и постепенно добавлять новые функции.

В итоге выбор оказался удачным решением для первого проекта — он позволил мне освоить основные возможности FXGL, не теряя времени на решение слишком сложных технических задач, при этом получив полноценное игровое приложение с базовым набором механик. Основные компоненты были реализованы за пару дней:

  1. Система волн врагов

  2. Башни с различными характеристиками

  3. Экономическая система

  4. Анимации атак и повреждений

А вот остальные доработки, пришли как аппетит во время еды времени требовали уже гораздо больше. Но, обо всем по порядку..

Общая организация проекта

Проектная структура является ключевым элементом разработки любой игры. Рассмотрим, как организован проект на базе FXGL.

Иерархия каталогов проекта
Иерархия каталогов проекта

Основные компоненты проекта

Пакеты и их назначение

  • component — содержит классы, описывающие поведение игровых объектов

  • constant — хранит константы и конфигурационные параметры

  • data — отвечает за хранение игровых данных

  • ui — реализует пользовательский интерфейс

Ключевые классы и их роль

Игровая логика

  • Entity — базовый класс для всех игровых объектов

  • SpawnData — управление созданием сущностей

  • CollisionHandler — обработка столкновений

  • ParticleEmitter — работа с визуальными эффектами

Игровые механики

  • component.* — реализация поведения объектов

  • TowerData — параметры башен

  • ExperienceData — система опыта

  • LevelData — информация об уровнях

Пользовательский интерфейс

  • BasicInfoView — отображение базовой информации

  • PerksView — управление перками

  • UI компоненты — кнопки, панели, лейблы

В качестве примера кода я бы выделил метод по размещению башен на карте

public void buildTowerOnPlace(TowerType towerType, double placeX, double placeY) {

    TowerData towerData = getTowerData(towerType);

    if (towerData == null) {

        return;

    }

 

    // Расчет позиции для размещения башни

    int w = towerData.getWidth();

    int h = towerData.getHeight();

    Point2D p = new Point2D(placeX + 80/2, placeY + 10);

    double x = p.getX() - w / 2.0;

    double y = p.getY() - h / 2.0;

 

    // Проверка стоимости и доступности

    if (towerData.getCost() > exp || towerData.getTowerCount() == 0) {

        FXGL.set("towerType", TowerType.NONE);

        return;

    }

 

    // Размещение башни

    if (canGenerate) {

        FXGL.play("placed.wav"); //Звуковое сопровождение

        FXGL.spawn(towerData.getName(), x, y);

        FXGL.set("towerType", TowerType.NONE);

 

        // Обновление счетчиков и интерфейса

        List<Entity> placedbuttons = FXGL.getGameWorld().getEntitiesByComponent(PlacedButtonComponent.class);

        for (int i = 0; i < placedbuttons.size(); i++) {

            if (placedbuttons.get(i).getComponent(PlacedButtonComponent.class)

                .getTowerType() == towerType) {

                towerData.decreaseTowerCount();

                placedbuttons.get(i).getComponent(PlacedButtonComponent.class)

                    .setTowerCountTextLabel(

                        towerData.getTowerCount() + "/" + towerData.getTowercount_const()

                    );

            }

        }

 

        // Обновление ресурсов

        exp -= towerData.getCost();

        ExperienceData expo = FXGL.geto("exp");

        expo.setExp(exp);

    }

}

Этот метод демонстрирует комплексную механику размещения башни, включая:

  • Проверку допустимости размещения

  • Расчет позиции

  • Проверку столкновений

  • Управление ресурсами

  • Обновление интерфейса

  • Работу с компонентами игры

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

Если возникнет желание ознакомится с исходниками проекта, напишите мне или оставьте комментарий.

Результаты проделанной работы превзошли мои первоначальные ожидания. Игровой процесс получился, на мой взгляд, довольно увлекательным. Отдельно хочу поделиться впечатлениями от процесса тестирования. Самотестирование оказалось не просто технической необходимостью, а настоящим удовольствием. Чуть не забыл - про технические характеристики: фреймворк стабильно выдает 60 FPS для вот таких вот проектов, и не требует десятки гигабайт оперативной памяти, все довольно скромно.

Пока это только тянет на пре-альфу — до настоящей игры ещё работать и работать. Но если хорошенько подкрутить баланс, сделать дизайн посимпатичнее и накинуть пару десятков карт с башнями, может выйти что‑то стоящее!

Несъедобный рогалик

Вдохновлённый успехом с Tower Defense, я взялся за новый проект — Rogue-like игру. Выбор жанра был неслучайным: меня всегда привлекала его непредсказуемость и глубина механик.

Для создания уровней использовал Tiled — это позволило сосредоточиться на ключевых аспектах: боевой системе, анимации и базовой ролевой механике. Проект стал для меня возможностью воплотить идеи, почерпнутые из любимой серии Gothic, особенно после повторного прохождения Gothic 3.

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

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

Заниматься разработкой игр в свободное от основной работы время — это настоящее испытание на прочность. Когда ты пытаешься уместить в редкие часы досуга работу со спрайтами, проработку игровых механик, написание кода и тщательное тестирование — каждая минута на счету.

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

И вот тут я особенно восхищаюсь теми разработчиками, которые создают полноценные проекты в одиночку. Это требует не только технических навыков, но и невероятной организованности, силы воли и терпения. Каждый раз, когда я вижу завершенный инди-проект, созданный одним человеком, я невольно снимаю шляпу — это настоящий подвиг!

При этом модульность и разделение ответственности, заложенные в основу как TowerDefense, так и RogueLike, оказались настоящим спасением. Они позволили мне:

  • Работать над отдельными компонентами в удобное время

  • Легче возвращаться к проекту после длительного перерыва

  • Постепенно наращивать функционал без риска сломать всё

Давайте рассмотрим несколько методов класса — отвечающих за анимацию атак героя

public void atack() {

    // Устанавливаем флаг атаки

    attack = true;

 

    // Если таймер атаки ещё не истёк — выходим из метода

    if (!weaponTimer.elapsed(Duration.seconds(ATTACK_DURATION))) {

        return;

    }

 

    // Проверяем выносливость: если меньше 10 — переходим в режим ожидания

    if (endurance.get() < 10) {

        setupIdleAnimation();  // Настраиваем анимацию простоя после завершения текущей

        return;  // Прерываем выполнение метода

    }

 

    // Уменьшаем выносливость на 10 единиц

    endurance.set(endurance.get() - 10);

    // Устанавливаем флаг, что атака выполняется

    isAttack = true;

    // Запускаем соответствующую анимацию атаки

    playAttackAnimation();

    // Обновляем счётчик ударов (чередуем 0 и 1)

    updateAttackHitCounter();

    // Сбрасываем таймер оружия для отсчёта следующей атаки

    weaponTimer.capture();

}

 

/**

 * Настраивает обработчик завершения анимации — при завершении переходит в режим простоя (idle)

 */

private void setupIdleAnimation() {

    texture.setOnCycleFinished(() -> idle());

}

 

/**

 * Выбирает и запускает анимацию атаки в зависимости от направления и номера удара

 * Если персонаж смотрит влево — использует left_attack1

 * Если вправо — чередует attack1 (первый удар) и attack2 (второй удар)

 */

private void playAttackAnimation() {

    AnimationChannel targetAnimation = left

        ? left_attack1  // Если персонаж смотрит влево — анимация левой атаки

        : (attackhit == 0 ? attack1 : attack2);  // Если вправо — выбираем атаку по счётчику

 

    // Запускаем анимацию только если она ещё не активна

    if (texture.getAnimationChannel() != targetAnimation) {

        texture.loopNoOverride(targetAnimation);

    }

}

 

/**

 * Обновляет счётчик ударов: чередует значения 0 и 1

 * Используем операцию остатка от деления для компактности:

 * 0 → 1, 1 → 0

 */

private void updateAttackHitCounter() {

    attackhit = (attackhit + 1) % 2;

}

Этот метод демонстрирует комплексную механику атаки персонажа, включая:

·         Проверку условий атаки:

  • Доступность выносливости (не менее 10 единиц)

  • Таймер между атаками

·         Анимационную логику:

  • Разные анимации для левой и правой стороны

  • Две разные анимации атаки (attack1 и attack2)

  • Автоматическое переключение между анимациями

·         Управление состоянием:

  • Откат к состоянию idle после завершения анимации

  • Корректное обновление таймера

  • Учет направления атаки

·         Система выносливости:

  • Расход выносливости при атаке

  • Проверка доступных ресурсов

А показательным этот код является, так как демонстрирует:

1.     Как реализуется боевая механика в игре

2.     Работу с анимацией в зависимости от состояния персонажа

3.     Управление ресурсами персонажа

4.     Временные ограничения и таймеры

5.     Логику переключения между различными состояниями

 

Адаптация этих принципов под разные игровые механики — исследование в Roguelike или оборонительные стратегии в Tower Defense — показала их универсальность и практичность в условиях разработки “в свободное время”.

 

Послесловие

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

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

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

  1. Созданием виртуальных миров

  2. Реализацией физических законов

  3. Построением логических связей между элементами

  4. Оптимизацией производительности

Сейчас, глядя на свои проекты в области промышленного моделирования, я вижу в них отголоски тех самых игровых экспериментов. Цифровой двойник — это, по сути, тоже своего рода «игра» (симулятор), только правила здесь диктует реальная физика, а ставки намного выше.

И знаете что? Я ни разу не пожалел о выбранном пути. Возможно, мои игры так и остались в статусе хобби-проектов, но то, что я создаю сейчас, приносит реальную пользу. А любовь к компьютерным играм? Она никуда не делась — теперь я просто смотрю на них под другим углом, понимая, сколько труда и инженерных решений стоит за каждой, даже самой простой игрой.

Инженерное дело и игровая разработка оказались двумя гранями одного и того же — создания виртуальных миров по определённым правилам. И кто знает, может быть, когда-нибудь эти два направления снова встретятся в одном проекте.

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