Привет, Хабр!

Я не фронтендер и не профессиональный геймдев-разработчик. Основная моя деятельность ближе к системному анализу и backend-разработке на Go. Но в какой-то момент мне стало интересно проверить не абстрактный тезис “с AI можно сделать все”, а более практичную вещь: насколько далеко можно зайти в solo-разработке браузерной игры, если использовать нейросети как рабочие инструменты для кода, графики, звука и ревью.

В итоге получилась Not So Cute - небольшой top-down шутер на Phaser 4: несколько уровней, боссы, разные виды оружия, Tiled-карты, мобильное управление, звуки, VFX, сохранение прогресса и публикация на Playgama.

В этой статье я хочу разобрать не сам факт релиза, а технический процесс:

  • как был устроен проект на Phaser 4;

  • как основной файл постепенно разросся и почему я начал дробить его на менеджеры;

  • как я использовал AI-ассистентов для кода и почему сильно помогли Phaser skills;

  • как делал ассеты через генерацию, Krita, Pixelorama и референсы;

  • как собирал уровни в Tiled;

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

  • где AI ускорял работу, а где создавал новые задачи.

Что получилось

Игра родилась из классического “сделай Dark Souls, только с котиками”. В моем случае получились не котики, а зайцы, и не Dark Souls, а top-down шутер в духе Crimsonland: игрок бегает по арене, отбивается от волн врагов, подбирает оружие, проходит уровни и сражается с боссами.

Геймплей игры Not So Cute - бой с зайцами-врагами на локации Охотничья ферма
Геймплей игры Not So Cute - бой с зайцами-врагами на локации Охотничья ферма

Технически проект получился таким:

Часть

Что использовалось

Движок

Phaser 4

Сборка

Vite

Язык

TypeScript

Карты

Tiled, экспорт в JSON/TMX

Графика

GPT Image, генеративное видео, Krita, Pixelorama

Звук

ElevenLabs и ручная настройка громкости/событий

Платформа

Playgama Bridge SDK

AI для кода

Codex, Claude Code, Kimi, DeepSeek, Gemini

На момент написания в проекте около 517 файлов ассетов суммарно примерно на 163 МБ. Готовая папка dist после сборки занимает примерно 131 МБ. Это не маленький размер для браузерной игры, и именно поэтому оптимизация ассетов стала отдельной задачей, а не финальным “запустить build и забыть”.

Какие модели и инструменты использовались

Я не пытался выбрать одну “главную” модель на весь проект. На практике удобнее оказалось использовать несколько инструментов под разные типы задач: где-то важнее большой контекст, где-то скорость, где-то цена, где-то способность работать с изображениями или видео.

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

Задача

Что чаще использовал

Где помогало

Основная игровая логика, сцены, менеджеры, оружие

Codex

хорошо подходил для итеративной работы по репозиторию и небольших патчей

Ревью больших кусков проекта и план оптимизации

Claude Code

большой контекст, удобно просить сначала анализ и план, потом дробить работу

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

DeepSeek, Kimi

хорошо подходили для более механических задач и локальных изменений

Сложные VFX/SFX-сценарии и поведение эффектов

Codex, Claude Code

помогали связывать Phaser-объекты, анимации, звуки и cleanup

Резерв, когда упирался в лимиты

Antigravity

использовал как запасной вариант для отдельных задач

Для медиа пайплайн был другим:

Задача

Инструменты

Черновые изображения, тайлы, UI-элементы, отдельные спрайты

GPT Image

Оживление сложных top-down анимаций

Grok Imagine Video, Алиса AI

Звуковые эффекты

ElevenLabs

Чистка фона и ручная обработка

Krita

Выравнивание кадров и сборка анимаций

Pixelorama

Карты и object layers

Tiled

Самый полезный вывод здесь не в том, что одна модель “лучше всех”, а в разделении задач. Например, флагманская модель с большим контекстом удобна для анализа архитектуры, но для аккуратного уменьшения ассета или локальной правки по инструкции часто хватало более простой модели.

Архитектура проекта

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

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

  • несколько видов оружия;

  • разные враги;

  • боссы;

  • Tiled-карты;

  • HUD;

  • мобильное управление;

  • музыка и звуковые эффекты;

  • VFX;

  • сохранение прогресса;

  • интеграция с Playgama.

В какой-то момент я открыл основной файл и понял, что GameScene уже перевалила за тысячу строк и продолжает разрастаться. Внутри одновременно жили игрок, враги, оружие, HUD, звук, VFX, карта, боссы, пауза и платформенная логика. Файл все еще работал, но менять его становилось неприятно: любое исправление требовало держать в голове слишком много соседних систем.

Для AI-ассистентов это тоже стало проблемой. Чем больше файл и чем больше в нем связанных подсистем, тем выше шанс, что модель исправит одно, но заденет другое. Особенно это заметно на задачах вроде “добавь новый эффект”, когда ассистент внезапно начинает менять управление, коллизии или порядок создания объектов.

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

GameScene
  - SfxManager
  - PauseManager
  - TextureGenerator
  - MapBuilder
  - CollisionHelpers
  - CollisionSetup
  - PlayerManager
  - HudCreator
  - VfxSystem
  - RabbitManager
  - BossManager
  - Level2Manager
  - Level3Manager
  - Level5Manager

В самой сцене осталось создание и связывание подсистем, а детали стали жить в отдельных файлах. Например, MapBuilder отвечает за создание карты, слои Tiled, границы мира, коллизии и точки спавна. WeaponManager - за текущее оружие, переключение, патроны, перегрев лазера и поведение непрерывного оружия. SfxManager и VfxSystem отделяют звук и визуальные эффекты от игровой логики.

Такой подход оказался удобен еще и для работы с AI. Вместо задачи “поправь игру” можно давать маленький и проверяемый контекст:

Есть BaseWeapon и несколько наследников.
Нужно добавить новое оружие по существующему паттерну.
Не трогай GameScene без необходимости.
Не меняй соседние классы оружия.
После смены оружия все эффекты должны корректно очищаться.

Чем меньше контекст и четче границы, тем проще потом проверить результат.

Оружие как пример декомпозиции

Оружие в игре устроено через общий базовый класс. У него есть единый жизненный цикл:

  • fire(targetX, targetY);

  • canFire();

  • update(time, delta, targetX, targetY);

  • onEquip();

  • onUnequip();

  • вспомогательный _spawnBullet().

Упрощенно схема выглядит так:

class BaseWeapon {
  fire(targetX, targetY) {
    const now = this.scene.time.now;
    if (now - this.lastFireTime < this.cooldown) return false;

    const origin = this._getFireOrigin();
    const angle = Phaser.Math.Angle.Between(
      origin.x,
      origin.y,
      targetX,
      targetY
    );

    const fired = this._fireImpl(angle, origin);
    if (!fired) return false;

    this.lastFireTime = now;
    this.playFireSfx(now);
    return true;
  }

  _fireImpl() {
    throw new Error('Must override _fireImpl');
  }
}

Дальше конкретные классы реализуют детали:

  • дробовик стреляет обычными снарядами;

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

  • миниган работает как тяжелое автоматическое оружие;

  • лазер имеет удержание и перегрев;

  • огнемет использует непрерывный расход ресурса, но ограниченную дистанцию

WeaponManager при этом не обязан знать внутренности каждого оружия. Он держит конфигурацию слотов, патронов, текущий тип оружия и вызывает общий интерфейс.

Для AI-assisted разработки это один из самых полезных паттернов: модель видит базовый контракт и одну-две соседние реализации, после чего может сгенерировать новый класс в том же стиле. Но тут важна приемка: AI часто забывает про onUnequip, оставляет активные эффекты после смены оружия или не учитывает, что Phaser-объекты надо уничтожать явно.

Почему Phaser skills оказались важны

Одна из неприятных проблем при работе с AI по Phaser 4 - модели уверенно придумывают API. Особенно если в обучающих данных много Phaser 3, старых примеров, StackOverflow-ответов и кусочков кода под другие версии.

Типичные симптомы:

  • модель предлагает метод, которого нет в Phaser 4;

  • путает lifecycle сцен;

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

  • неверно работает с Arcade Physics;

  • создает “красивый” код, который выглядит правдоподобно, но не запускается.

Сильно помогли skills, которые заложены в репозитории Phaser. По сути это не магия, а дополнительный слой контекста для агента: короткие специализированные инструкции по конкретным частям Phaser 4 - сценам, загрузке ассетов, анимациям, input, камерам, Arcade Physics, группам, tilemaps, tweens, particles и так далее.

Я стал формулировать задачи примерно так:

Работаем с Phaser 4.
Перед изменениями используй релевантный Phaser skill:
- scenes, если меняешь lifecycle сцены;
- loading-assets, если добавляешь ассеты;
- animations, если создаешь анимации;
- physics-arcade, если меняешь коллизии;
- tilemaps, если работаешь с Tiled;
- input-keyboard-mouse-touch, если меняешь управление.

Не используй API, если не уверен, что он есть в Phaser 4.
Если нужен новый вызов Phaser API, сначала объясни, почему он подходит.

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

Для меня это был важный вывод: при работе с игровым движком AI-ассистенту недостаточно “знать TypeScript”. Ему нужен контекст именно по текущему движку и версии. Иначе он очень убедительно пишет код для соседней реальности.

Как я ставил задачи AI-ассистентам

Сначала я часто писал слишком широкие запросы. Например:

Добавь босса на уровень.

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

Лучше работал другой формат:

Нужно добавить первую фазу босса.

Контекст:
- босс живет в Level3Boss;
- GameScene вызывает boss.update(time, delta);
- снаряды игрока уже обрабатываются в CollisionSetup;
- VFX создаем через VfxSystem.

Задача:
- добавить атаку "рывок";
- перед рывком показать предупреждение 700 мс;
- во время рывка босс движется по прямой;
- после рывка cooldown 2 секунды.

Ограничения:
- не менять другие уровни;
- не трогать WeaponManager;
- все временные объекты уничтожать при destroy босса.

Главное правило: одна задача - один понятный кусок поведения. Если нужно сделать босса, сначала фаза, потом атака, потом VFX, потом звук, потом баланс.

Отдельно помогала просьба не сразу писать код, а сначала перечислить файлы и точки изменения. Так проще поймать момент, когда модель собирается полезть не туда.

Карты и Tiled

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

Tiled в проекте используется не только как визуальный редактор. Он стал источником данных для Phaser:

  • tile layers отвечают за землю, детали, бордюры и декор;

  • object layers задают коллизии;

  • отдельные object layers хранят точки спавна врагов;

  • именованные точки используются для старта игрока, босса и зон уровня;

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

Например, первый уровень использует слои:

ground
details
board
objects
collision
respawn_white
respawn_red

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

Для более поздних уровней схема усложнилась. MapBuilder читает raw tilemap data, создает слои, вытаскивает object sprites, считает world bounds, создает коллизии из object layers и настраивает камеры.

Упрощенно процесс загрузки уровня выглядит так:

Tiled map
  -> JSON/TMX
  -> Phaser tilemap
  -> tile layers
  -> object layers
  -> collision objects
  -> spawn points
  -> camera/world bounds

AI тут помогал не в самом дизайне уровня, а в интеграционном коде: разобрать слои, создать группы коллизий, аккуратно привязать точки спавна и не забыть про bounds. А вот “сделай интересный уровень” - задача плохо формализуемая. Здесь все равно нужно самому играть, смотреть, где скучно, где тесно, где игрок упирается в стену, где враги ломают темп.

Пайплайн графики

Самая большая зона экспериментов была в графике. Я не художник, поэтому AI здесь был особенно полезен. Но генерация одной красивой картинки и производство ассетов для игры - разные вещи.

Рабочий пайплайн получился таким:

идея ассета
  -> референсы
  -> генерация изображения или видео
  -> чистка фона в Krita
  -> выравнивание кадров в Pixelorama
  -> сборка spritesheet / atlas
  -> подключение в Phaser
  -> настройка анимации
  -> проверка scale, origin, depth и hitbox
Тайлсет деревьев и пней, сгенерированный GPT Image 2 для использования в Tiled
Тайлсет деревьев и пней, сгенерированный GPT Image 2 для использования в Tiled

Главная проблема top-down ассетов: модели очень любят красивую перспективу. Им хочется нарисовать объект под углом, добавить драматичный свет, объем, тень, почти изометрическую камеру. Для иллюстрации это хорошо, для top-down шутера - плохо.

Пришлось явно писать в промптах:

Top-down 2D game asset.
Orthographic view.
No perspective.
No isometric angle.
No side view.
Object centered.
Consistent lighting.
Solid green background for chroma key.

Но даже этого мало. Второй важный прием - референсы.

Если генерировать каждого врага, босса или предмет отдельно “с нуля”, стиль быстро расползается: один спрайт выглядит мультяшно, второй почти реалистично, третий уходит в 3D, четвертый получает другую толщину контура. Поэтому я стал давать модели уже готовые удачные ассеты как референсы и просить новый объект в том же стиле.

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

Пример формулировки:

Use the attached sprite as a style reference.
Create a new top-down enemy for the same game.
Keep the same outline thickness, color contrast, proportions and lighting.
Do not change camera angle.
The result must look like it belongs to the same spritesheet.

После этого стиль стал держаться заметно лучше. Не идеально, но достаточно, чтобы ручная обработка в Krita и Pixelorama не превращалась в полную перерисовку.

Анимации и генеративное видео

Для простых объектов хватало генерации изображений. Но со сложными анимациями все становилось тяжелее: атака, смерть, трансформация, рывок, фаза босса, огненный эффект.

Изображение-модель часто рисует отдельные красивые кадры, но плохо держит:

  • одинаковый размер персонажа;

  • одинаковую позу между кадрами;

  • центр объекта;

  • направление камеры;

  • читаемость движения;

  • прозрачный фон или удобную хромакей-заливку.

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

Спрайт анимации огненной атаки, 8 кадров, сгенерированный GPT Image 2
Спрайт анимации огненной атаки, 8 кадров, сгенерированный GPT Image 2

Схема была примерно такая:

reference sprite
  -> video generation
  -> frame extraction
  -> cleanup / chroma key
  -> alignment in Pixelorama
  -> spritesheet
  -> Phaser animation config

Здесь AI помогал не только с генерацией, но и с технической рутиной: порезать атлас, определить размеры кадров, написать конфиг анимации, подключить VFX к нужной атаке. Но финальное “хорошо ли это выглядит в игре” все равно проверялось только глазами и playtest.

Звук и события

Звуки в игре оказались не менее важны, чем графика. Top-down шутер без нормального звукового отклика быстро ощущается пустым: выстрелы, попадания, смерть врагов, атаки босса, UI-клики, подбор предметов - все это дает игроку обратную связь.

В проекте звук разнесен по типам:

assets/sfx/enemies
assets/sfx/player
assets/sfx/weapon
assets/sfx/tank
assets/sfx/level3
assets/sfx/boss_level_*
assets/sfx/ui
assets/soundtracks/background_music

Отдельная проблема - громкость. Сгенерированные звуки редко приходят в едином уровне. Один эффект почти не слышно, второй бьет по ушам, третий конфликтует с музыкой. Поэтому понадобилась отдельная настройка громкостей и событий, а не просто “положить mp3 в папку”.

Для непрерывных эффектов вроде лазера, двигателя или некоторых фаз босса важно не забывать про lifecycle: старт, loop, остановка, смена сцены, пауза, смерть босса. Это как раз тот тип задач, где AI может написать рабочий happy path, но забыть очистку.

Оптимизация

Когда игра стала похожа на игру, выяснилось, что ассеты быстро раздувают проект. Особенно это касается:

  • spritesheet из видео;

  • больших PNG с прозрачными полями;

  • музыки;

  • UI-изображений;

  • повторяющихся VFX.

В проекте есть несколько показательных цифр:

Метрика

Значение

Файлов в assets

около 517

Размер assets

около 163 МБ

Размер dist

около 131 МБ

Самая тяжелая карта JSON

level4.json, около 910 КБ

Самый тяжелый аудиофайл

wave_3_music.mp3, около 5.7 МБ

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

Что помогало:

  • резать лишние прозрачные поля у spritesheet;

  • уменьшать размеры кадров, если в игре ассет все равно рисуется маленьким;

  • не тащить в рантайм промежуточные файлы;

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

  • разделять эффекты и не делать один гигантский универсальный spritesheet;

  • следить, чтобы временные Phaser-объекты уничтожались после анимации;

  • ограничивать количество одновременно живущих частиц и эффектов.

Отдельно удивило, что с оптимизацией ассетов хорошо справлялись не только флагманские модели, но и более доступные open source модели. Я давал им крупные spritesheet, описывал, как ассет используется в игре, и просил уменьшить размер без заметной потери качества: убрать лишние прозрачные поля, подобрать более компактную сетку кадров, уменьшить разрешение там, где это не влияет на читаемость в рантайме.

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

Где AI ошибался

Самые частые ошибки были не в синтаксисе. Синтаксис модели обычно пишут уверенно. Проблемы начинались на границах систем.

Несуществующий API

Про Phaser 4 я уже писал выше: без skills модель могла предлагать методы из старых версий или просто правдоподобные вызовы. Это лечилось дополнительным контекстом, ограничением версии и проверкой через build/запуск.

Cleanup объектов

Модель легко создает эффект:

this.scene.add.sprite(x, y, 'effect')

Но может забыть, что эффект нужно уничтожить после проигрывания, остановить loop-звук, снять listener или убрать timer. В игре это проявляется не сразу, а после нескольких перезапусков уровня.

Глобальные правки

Если попросить “улучшить архитектуру”, модель может начать менять слишком много файлов. Иногда изменения выглядят красиво, но ломают уже работающие сценарии. Лучше просить маленькие шаги: сначала вынести один менеджер, потом проверить, потом следующий.

Баланс

AI может предложить параметры оружия, здоровья или скорости, но балансируется это только игрой. На бумаге cooldown: 300 и damage: 10 выглядят нормально. В реальности оружие может быть скучным, слишком сильным или неприятным по ощущению.

Визуальный стиль

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

Итоги

Этот проект был для меня способом проверить, как AI меняет разработку небольшой игры на практике. Самый полезный сценарий оказался не в том, чтобы просить “сделай игру”, а в том, чтобы разбивать проект на понятные задачи:

  • сгенерировать черновой ассет;

  • привести его к нужному формату;

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

  • вынести подсистему в менеджер;

  • проверить Phaser API через skills;

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

  • подготовить рутинный glue code.

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

Если резюмировать опыт коротко:

  • AI хорошо ускоряет прототипирование;

  • Phaser skills сильно уменьшают количество выдуманного API;

  • референсы почти обязательны для единого визуального стиля;

  • Tiled удобен как источник данных, а не просто редактор картинки;

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

  • большие задачи для AI лучше дробить на маленькие проверяемые шаги.

Ссылки

Если хочется посмотреть, что получилось в результате, игра доступна на Playgama: Not So Cute.

Превью-страница проекта: kotey-ye.ru/not-so-cute.

Иногда я пишу про похожие эксперименты с AI и разработкой в Telegram-канале: AI на коленке.

Спасибо за внимание. Буду рад вопросам, особенно по Phaser 4, Tiled, пайплайну ассетов и тому, как вы сами ограничиваете AI-ассистентов в игровых проектах.