Skip to content

Standalone SDK (браузер) ​

vimp-engine/standalone поднимает полноценный матч внутри одной вкладки: авторитетный хост, клиент и плагин игры живут на странице. Ни сервера платформы, ни входа, ни экрана лобби. Существует ради репозитория игры: npm run dev там должен открывать играбельный матч против scripted-участников самой игры.

Dedicated-сервер на Node.js — другой контур; модель хостов и транспортов — в overview.md.

Быстрый старт ​

Поставьте движок в репозитории игры (обычная зависимость dev-контура) и добавьте три файла.

index.html:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>my-game — dev</title>
    <style>
      html,
      body {
        margin: 0;
        height: 100%;
        background: #000;
      }
      /* контейнер SDK обязан быть полноэкранным и позиционированным */
      #game {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <div id="game"></div>
    <script type="module" src="/dev/main.js"></script>
  </body>
</html>

dev/main.js:

js
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'),
  assetsBase: '/assets/',
  playerName: 'dev',
  playerModel: 'm1',
  startupVotes: [['teamChange', 'team1']],
  startupCommands: ['/bot 4'],
  room: { map: 'arena', maxPlayers: 8 },
});

Стиль движка импортирует сам клиент, поэтому сборке ничего добавлять не нужно; экспорт vimp-engine/style.css существует для контуров, которые предпочитают подключать его явно (import 'vimp-engine/style.css').

vite.config.js:

js
import { defineConfig } from 'vite';

export default defineConfig({
  resolve: {
    // два экземпляра PixiJS = два реестра расширений и мёртвый рендер
    dedupe: ['pixi.js'],
  },
  optimizeDeps: {
    // движок публикуется ESM-исходниками: пребандлинг ломает его
    // динамические импорты и модульный boot-конфиг, общий с SDK
    exclude: ['vimp-engine'],
  },
  server: {
    // нужно только при `npm link`: слинкованный пакет лежит вне корня
    fs: { allow: ['..'] },
  },
});

API ​

js
startStandaloneGame(options): Promise<{ stop(): void }>
ОпцияУмолчаниеЗначение
hostPlugin—живой объект HostPlugin (обязателен)
clientPlugin—живой объект ClientPlugin (обязателен)
wasmUrl—URL web-сборки ядра игры (обязателен)
containerdocument.bodyточка монтирования каркаса интерфейса и канвасов
assetsBase'/'база ассетов игры; звуки берутся из ${assetsBase}sounds/
playerName—задан → форма авторизации пропускается, вход гостевой
playerModel—поле model игровой authSchema
auth{}прочие поля authSchema игры
startupVotes[]ответы на initialVote, например [['teamChange', 'team1']]
startupCommands[]чат-команды игры после голосований, например ['/bot 4']
room{}переопределения комнаты: map, maxPlayers, roundTime, mapTime, friendlyFire, seed
devModefalseroom.isDevMode: рекордер матча и хостовый лог CONSOLE
reportUrlnullкуда отправлять журнал клиентских ошибок (POST JSON-пакетов с необработанными ошибками, отклонёнными промисами и нарушениями CSP через navigator.sendBeacon; отчёт не несёт ника, id игрока и IP); null — ничего не отправляется, в solo сервера нет

Оба плагина сверяются с ENGINE_API_VERSION до всего остального: плагин, собранный под другую версию API движка, отбивается сразу, а не падает где-то в середине хендшейка.

stop() закрывает транспорт, и матч гасится целиком: снимается рендер-луп, уничтожается inline-хост (иначе его таймеры продолжали бы крутиться), освобождаются звук и клавиатура.

Контейнер ​

Контейнер обязан быть полноэкранным и позиционированным (position: relative). #panel, #stat и создаваемый в рантайме #vote — position: absolute, а их containing block — ближайший позиционированный предок. Движок достраивает недостающие элементы интерфейса внутри контейнера и монтирует туда же игровые канвасы; элементы, которые игра положила в свою разметку сама (<canvas id="vimp">, #chat, …), переиспользуются как есть и никуда не переносятся.

Движок также ставит на контейнер класс vimp-shell: style.css скрывает .vimp-shell > *, чтобы экраны движка не показались разом — каждый из них показывает свой модуль. Сам контейнер не скрывает ничто и ни на какой глубине вложенности, display от страницы ему не нужен. Учтите, что правило — селектор по классу: собственный элемент на первом уровне контейнера, который показан правилом по типу или классу (canvas { display: block }), правилу проиграет — целитесь в него по id.

style.css — таблица уровня страницы, а не изолированная: вместе с SDK встраивающая страница получает html, body { width: 100%; height: 100% }, движковые фон, цвет и шрифт body, а также * { user-select: none } — то есть выделение текста пропадает на всей странице, а не только внутри контейнера. Страницу стоит отдать игре целиком.

wasmUrl и ассеты ​

wasmUrl — web-сборка ядра игры, импортированная с суффиксом Vite ?url: сборщик кладёт файл в выдачу и возвращает его URL. Node-сборка ядра (entries.wasmNode) предназначена headless-раннеру и dedicated-серверу, во вкладке она не используется.

Звуки ищутся под ${assetsBase}sounds/. Их отсутствие не блокирует вход: SoundManager.init грузит их через Promise.allSettled и только пишет ошибки в консоль.

startupVotes раньше startupCommands ​

Игрок входит наблюдателем, а игра вправе требовать активной команды для своих чат-команд (у танков /bot отбивается наблюдателю). Выйти из наблюдателей можно только ответом на initialVote, поэтому startupVotes обязаны нести ['teamChange', '<team>'] и уходят строго раньшеstartupCommands. Обе пачки отправляются на первом renderTick после первого кадра.

Игры с gameConfig.noSpectators это не касается: наблюдателей в них нет, участник входит сразу в играющую команду и актора получает на первом кадре сам. startupVotes для такой игры оставляйте пустыми — отвечать нечему, а ['teamChange', '<та же команда>'] даст в чат «это ваша текущая команда».

Понятия «бот» у движка нет: scripted-участников спавнит игровая чат-команда, объявленная в hostPlugin.chatCommands — отсюда startupCommands: ['/bot 4'], а не опция bots: 4. Их число должно укладываться в room.maxPlayers, иначе игра отобьёт часть пачки.

Чем solo отличается от прода ​

прод (лобби)standalone (solo)
хостWeb Worker во вкладке создателя комнатыглавный поток (inline), без Worker'а
личностьпроверенная личность платформы (JWT), её проверяет хостгостевая: ник из формы или из playerName
rank / stateчитаются у платформы и сбрасываются на неёoffline-заглушка, ничего не сохраняется
каталог игркаталог платформыманифест в памяти, собранный из плагина
картыкаталог карт платформы, горячие обновлениякарты из gameConfig
транспортWebRTC (или loopback для хоста-игрока)loopback к inline-хосту
эстафета Worker'ов на новый кодестьнеприменима

Хост крутится inline, потому что HostPlugin непередаваем через postMessage (функции не клонируются), а SDK получает именно живой объект. Расхождение dev и прода осознанное.

Полезное следствие: solo не касается ни WebRTC, ни module-worker'ов — они задействованы только на lobby-путях, — поэтому игра стартует в браузере с полностью отключённым WebRTC.

Если что-то не работает ​

  • Пустой канвас без ошибок — два экземпляра PixiJS. Добавьте resolve.dedupe: ['pixi.js'].
  • Интерфейс собран в левом верхнем углу — контейнер не position: relative или не полноэкранный.
  • Чёрный экран при явно идущем матче (звук играет, ошибок нет) — сверху лежит собственный элемент контейнера, либо CSS игры показывает что-то на первом уровне контейнера правилом слабее .vimp-shell > * (см. выше) и движок держит это скрытым.
  • /bot отвечает «только для игроков» — не заданы startupVotes, игрок всё ещё наблюдатель (в игре с noSpectators такого не бывает — там startupVotes не нужны).
  • game "<id>" needs engine capabilities this build does not have: … — половина плагина объявила в requires имя, которого установленный vimp-engine не предоставляет (capabilities): игра новее движка, обновляйте vimp-engine. Возраст плагина причиной отказа не бывает — engineApi заморожен и гейтом больше не является.

← Предыдущая: Скаффолдинг игрового плагина (create-vimp-game) · Следующая: Plugin API →

vimp-engine 0.38.1 · vimp-engine-core 0.23.2 · create-vimp-game 0.5.4