Skip to content

Процесс разработки ​

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

Шаг 0 — прочитайте контракт ​

Прочитайте страницы документации SDK целиком, в порядке из README.md. Опросник предполагает, что вы знаете, что движок уже даёт, чтобы сказать пользователю «это делает движок», а не проектировать заново.

Шаг 1 — интервью ​

Проведите questionnaire.md. Правила:

  • Ведите его на языке пользователя, на каком бы языке ни был написан файл.
  • Спрашивайте блок за блоком, а не все 14 блоков сразу.
  • К каждому вопросу предлагайте вариант «как в tanks», чтобы пользователь принял его одним словом.
  • Если ответ противоречит движку (собственное поле комнаты, голосование за кик, локализация в рантайме), скажите об этом сразу и предложите ближайшую рабочую замену.
  • Не спрашивайте то, что пользователь уже решил неявно.

Шаг 2 — документ-проект ​

Напишите короткий документ-проект и получите явное подтверждение до генерации кода. В нём нужно зафиксировать:

РешениеКуда идёт
Имя пакета, id, названиеpackage.json, manifest, оба плагина
URL репозиторияrepository в package.json — реестр игр ссылается на него; без него ссылки нет, а правило A7 предупреждает
Команды и команда наблюдателейgameConfig.teams, схема статистики, карты
Типы сущностей и их полясхема снапшота, строки Rust, части
Действия и клавишиplayerKeys, keySetList, обработка ввода в ядре
Ресурсы в HUDpanel.fields (хост) + схема панели (клиент)
Оружие/способностиparts.weapons, логика оружия в ядре, ключи событий снапшота
Что предсказывается локальносостояние игрока 8 × f32, предиктор, тест паритета
Полотна и камераcanvasManager.canvases
Карты и вместимость командdata/maps/*, respawns
Звуки и сигналыконфиг звуков, soundCues
ПрогрессияplayerState.defaultState, onCoreEvent

Шаг 3 — скаффолд ​

Не пишите дерево руками — сгенерируйте его:

bash
npm create vimp-game <directory> -- --id <game-id> --title "<Title>" \
  --repository <user/repo>

Передайте --repository (полный URL или сокращение user/repo) именно на этом шаге, а не правкой package.json позже: платформа берёт ссылку на проект из опубликованного пакета (repository), а добавление после публикации стоит перепубликации.

Результат — раскладка из packaging.md, уже заполненная: вся инфраструктура сборки (package.json, vite.config.js, vitest.config.js, eslint.config.js, Cargo.toml, пять скриптов сборки), пины vimp-engine и vimp-engine-core на текущие версии, dev-обвязка и минимальная, но играбельная игра — две команды, один класс актора, одно хитскан-оружие, одна процедурная карта, боты. Она проходит check:contract, core:test, test, build и sim с первого коммита, так что у каждого следующего шага есть зелёная точка отсчёта. Сгенерированный CLAUDE.md описывает границы и команды проверок.

Дальше работа — замена, а не создание: перепишите конфиги, ядро и части этой минимальной игры под документ-проект из шага 2. В assets/ лежат два авторских входа: audio-raw/ и img/ — листы тайлов и спрайты динамических тел, которые называют ваши карты. Движок не поставляет ни того, ни другого.

Писать дерево руками по-прежнему можно (packaging.md описывает каждый файл), но тогда никто не охраняет тихие инварианты, которые шаблон уже соблюдает.

Шаг 4 — конфигурация ​

Сначала пишите слой конфигов; это контракт, который реализует всё остальное.

  1. src/config/snapshot.js — схема передачи.
  2. src/data/models.js, src/data/weapons.js — каталоги.
  3. src/config/game.js — HostPlugin.gameConfig (импортирует предыдущие).
  4. src/config/client.js — клиентская половина.
  5. src/config/auth.js — экран входа.
  6. src/data/maps/*.js — минимум одна карта с полными respawns; каждый spriteSheet.img и physicsDynamic[].img, который она называет, должен существовать в assets/img/.
  7. src/config/sounds.js — реестр звуков.

Сверяйтесь по ходу: ключи панели совпадают на обеих половинах; имена в playerKeys совпадают с именами в keySetList; у каждого ключа снапшота есть запись в gameSets; каждый класс из gameSets есть в entitiesOnCanvas.

Шаг 5 — Rust-ядро ​

  1. core/src/config.rs — десериализация половины game из init JSON.
  2. core/src/<actor>.rs — появление/урон/жизненные показатели актора.
  3. core/src/motion.rs — математика движения, общая с предиктором.
  4. core/src/<game>.rs — impl GameSim, включая build_snapshot_blocks, выдающий строки точно в порядке схемы.
  5. core/src/client/predictor.rs — локальная предикция на основе motion.rs.
  6. core/src/client/mod.rs — impl GameClientDef.
  7. core/src/lib.rs — две структуры плюс export_game_core_abi! / export_client_core_abi! и написанные вручную new и игровые методы клиента.

Сборка: npm run core:build.

Шаг 6 — клиентская отрисовка ​

  1. src/client/parts/* — по классу на проекцию сущности.
  2. src/client/bakers/* — процедурные текстуры.
  3. src/client/<game>.css — стили ячеек панели и команд.
  4. src/client/index.js — ClientPlugin с тремя хуками.
  5. src/host/index.js, createModules.js, systemMessages.js, чат-команды и менеджер scripted (ботов).

Шаг 7 — тесты ​

Два набора:

JavaScript (Vitest) — повторите схему tanks, два проекта:

js
projects: [
  {
    test: {
      name: 'game',
      environment: 'happy-dom',
      include: ['tests/host/**', 'tests/client/**', 'tests/config/**'],
    },
  },
  { test: { name: 'integration', environment: 'node', include: ['tests/core/**'] } }, // гоняет core/pkg-node
];

Покройте как минимум:

  • хост-плагин выставляет все обязательные поля, а конфиг проходит проверку обязательных путей движка;
  • клиентский и хостовый конфиги согласованы по ключам панели, именам клавиш, ключам снапшота, gameSets ↔ entitiesOnCanvas;
  • чат-команды и менеджер ботов работают;
  • скрипт манифеста выдаёт корректный манифест.

Rust (cargo test --workspace) — модульные тесты по каждому модулю плюс набор паритета движения: прогоните авторитетную симуляцию и предиктор с одинаковым вводом и проверьте идентичное состояние. Запускайте его после каждой правки движения.

Шаг 8 — сборка и запуск ​

bash
npm run core:build     # wasm-pack web + nodejs
npm run build          # client → host → assets → manifest
npm run dev            # standalone: матч против ботов в одной вкладке

vimp-engine/standalone запускает весь матч в одной вкладке репозитория игры — без входа и экрана лобби; сгенерированный dev/main.js уже всё подключает. startStandaloneGame({ hostPlugin, clientPlugin, wasmUrl, … }) принимает живые объекты плагинов, поэтому это самый быстрый цикл, пока плагин ещё формируется:

js
// dev/main.js в репозитории игры, его грузит собственный index.html
import { startStandaloneGame } from 'vimp-engine/standalone';
import hostPlugin from '../src/host/index.js';
import clientPlugin from '../src/client/index.js';
import wasmUrl from '../core/pkg/my_game_bg.wasm?url';

await startStandaloneGame({
  hostPlugin,
  clientPlugin,
  wasmUrl,
  container: document.getElementById('game'), // во весь экран, position: relative
  // dev-корень ваших ресурсов: клиент читает `${assetsBase}img/` и
  // `${assetsBase}sounds/`, оба кладут в build/ скрипты копирования
  assetsBase: '/build/',
  playerName: 'dev',
  startupVotes: [['teamChange', 'team1']], // сначала уйти из наблюдателей…
  startupCommands: ['/bot 4'], // …и только потом свои чат-команды
});

У движка нет понятия бота: scripted-участников создаёт ваша чат-команда, и пока игрок остаётся наблюдателем, команда отклоняется — отсюда строгий порядок двух последних опций. Справка: standalone.md.

Шаг 9 — headless-симуляция (до браузера) ​

Две вкладки браузера — самый медленный и наименее информативный способ найти сломанный контракт. Сначала запустите headless-раннер: он замыкает весь цикл (хост → бинарный кадр → клиентское ядро → сцена) в одном процессе Node и называет каждый нарушенный контракт текстом. Полная справка: debugging.md.

bash
npm run sim -- --scenario <scenario.json>
  • [ ] entries.wasmNode указывает на копию Node-ядра внутри dist/ (./core-node/<crate>.js) — иначе передайте --core <path>.
  • [ ] Есть собственные сценарии, а не только встроенный: без --scenario раннер гоняет однокнопочный smoke, построенный из вашего gameConfig, и пропускает инварианты 2 и 9, которые не могут быть осмысленными для неизвестной ему игры.
  • [ ] По сценарию на каждую основную механику: движение, стрельба, смерть/респаун, конец раунда, смена карты, голосование.
  • [ ] Все инварианты зелёные — в первую очередь snapshotKeysUsed, fieldWidths, renderCoverage, panelContract, keyBindings: это тихие отказы из pitfalls.md, доведённые до автоматической проверки.
  • [ ] --determinism зелёный, когда матч стабилен.
  • [ ] Расхождение предикции в пределах порога (реализуйте GameClientDef::predicted_state для покомпонентного отчёта).

Исправьте всё, что сообщил раннер, прежде чем открывать браузер. Всё, что браузер поймает позже, можно записать через window.__vimpDebug и воспроизвести здесь командой npm run sim:replay.

Шаг 10 — ручная проверка ​

Откройте npm run dev во вкладке браузера (standalone-матч):

  • [ ] Карта отрисована на каждом полотне; радар (если есть) совпадает.
  • [ ] Движение отзывчиво, боты двигаются плавно.
  • [ ] Выстрел даёт ровно один эффект локально (без дубля от авторитетного события).
  • [ ] Панель обновляется: полоса ресурса, боезапас, активное оружие, таймер раунда.
  • [ ] Таблица статистики (Tab) показывает участников с верными командами и обновляет очки/смерти.
  • [ ] Чат работает; системное сообщение игры отображается со своим текстом.
  • [ ] Голосование можно начать, проголосовать и довести до результата.
  • [ ] Звуки играют, для выстрелов ботов — пространственно.
  • [ ] Убийство завершает раунд; очки и сообщение о победителе верны.
  • [ ] Голосование за смену карты ротирует карту и пересобирает всё.
  • [ ] npm run check:contract зелёный, а dist/manifest.json полон: платформа пропускает пакет, который не может прочитать, и игра не появится в лобби.

Матрица пересборки ​

ИзменениеНужные команды
src/config/*, src/data/* (JS)dev: ничего (HMR); prod: npm run build
src/client/**, src/host/** (JS)dev: ничего (HMR); prod: npm run build
Карты (src/data/maps/*)npm run build:assets && npm run build:manifest (платформа читает dist/maps/)
Звуки (исходные ресурсы)npm run audio:process && npm run build:assets && npm run build:manifest
Картинки (assets/img/*)npm run build:assets && npm run build:manifest; в dev predev уже кладёт build/img/
Rust-ядроnpm run core:build (+ перезагрузка; для prod ещё npm run build)
roomForm / roomDefaultsnpm run build:manifest
Что угодно перед первым запускомполностью npm run core:build && npm run build

Два правила за таблицей: платформа читает dist/manifest.json и dist/maps/ из собранного пакета, а npm run build никогда не пересобирает WASM-ядро.

Критерии готовности ​

  • [ ] npx eslint . чисто.
  • [ ] npm test зелёный (оба проекта Vitest).
  • [ ] npm run core:test зелёный, включая паритет движения.
  • [ ] npm run core:build && npm run build проходит с чистого dist/.
  • [ ] npm run sim -- --game <package> завершается с кодом 0 на каждом написанном вами сценарии (см. debugging.md).
  • [ ] Ручной чек-лист выше пройден во вкладке standalone.
  • [ ] Игра опубликована и подана (submitting.md).
  • [ ] Каждый пункт pitfalls.md проверен.

vimp-engine 0.38.3 · vimp-engine-core 0.23.4 · create-vimp-game 0.5.7