Мета-слой хоста
Часть хоста, которую наблюдает автор игры: как движок управляет Wasm-ядром игры и разбирает то, что ядро сообщает, и поверх этого — мета-слой движка: реестр участников, игровые правила, которыми владеет движок (раунды, счёт, команды, ротация карт, зрители), менеджеры и модули. Игра параметризует их через gameConfig; заменить их она не может.
Вызовы ядра и события
Движок управляет Wasm-ядром игры и разбирает то, что ядро сообщает (сам ABI — в plugin-api.md):
- Жизненный цикл. Карта грузится через
load_map(приходит уже отмасштабированной, поэтому ядро грузит её сscale: 1). Человек входит черезspawn_actorи уходит черезremove_actor; scripted-участник (бот) —spawn_scripted_actor/remove_scripted_actor; смена данных игрока зовётreset_actor. На слоёной (2.5D) карте точка респауна может называть свой уровень ([x, y, angle, level]), и движок передаёт его вызовомset_actor_level(gameId, level)— вызов определяется по наличию, старое ядро без него выводит уровень из геометрии. - Ввод. Ввод игрока попадает в ядро через
apply_input; ядро подтверждает номер последовательности в player-блоке кадра. - События. После каждого
stepдвижок вычерпываетtake_events()и сам разбирает стандартный словарь:panelSet/panelActiveобновляют панель (field— собственный ключ схемы панели игры),deathуходит в отчёт об убийстве (счёт, исход раунда),shakeтрясёт камеру.custom— единственный тип с игровым смыслом: он как есть передаётся в необязательныйHostPlugin.onCoreEvent(data, { panel, vimp }). Ядро работает с числовыми id (u32), мета-слой — со строковыми ключами; движок приводит их на этой границе. - Кадры. Снапшот упаковывается через
pack_body/pack_frame; первый кадр входящего игрока строится изplayers_data()ядра.
Мета-слой
Мета-слой движка ведёт матч поверх событий ядра. Код, который плагин добавляет в хост (обработчики команд, модули), обязан использовать только изоморфные API — см. overview.md.
Список карт, по которому комната ротируется, — актуальный на момент старта комнаты список каталога.
Участники
Люди и scripted-участники (боты) делят один реестр и одно числовое пространство id; различаются геттерами isScripted/isNetworked, не по формату id. У каждого участника есть gameId, name, model, team, teamId и status; имя scripted-участника — scripted.namePrefix плюс его id из конфига игры.
Игровые правила, которыми владеет движок
Эти правила живут в движке и не заменяются; игра параметризует их через gameConfig либо реализует альтернативу целиком внутри своего WASM-ядра. Детали по менеджерам — ниже.
- Раунды. Раунд заканчивается, когда команда уничтожена (каждый участник, люди и scripted, не жив), а выжившие остались не больше чем в одной другой команде; при трёх и более командах он идёт, пока живые есть только в одной. Победитель — команда, у которой остались живые, как бы ни погиб последний игрок (убийство противником, огонь по своим, самоуничтожение, окружение, уже вышедший убийца) — каждое событие
deathзапускает проверку уничтожения. Уничтожение без выживших где-либо (оба последних игрока погибли одновременно или игра с одной командой) завершает раунд без победителя: каждый игрок получает репликуdefeat(наблюдатели —victory) и сообщение о конце раунда без победившей команды. Истечение таймера раунда не завершает раунд результатом — оно просто начинает новый без изменения счёта. Следующий раунд стартует черезtimers.roundRestartDelay. Игре без раундов ближе всего один долгий раунд (большойroundTime, респауны в ядре) или одна команда, в которой уничтожение невозможно. - Счёт. Убийство противника: у убийцы
score +1и очки ранга+1, у жертвыdeaths +1иstatus = 'dead'. Огонь по своим: у убийцыscore −1и очки−1, у жертвыdeaths +1. Самоубийство: у жертвы толькоdeaths +1. Убийца, уже покинувший игру: фрага нет, жертва всё равно гибнет, раунд всё равно разрешается. Голове каждой уничтоженной команды —deaths +1один раз за раунд, а в конце раунда голове выжившей команды —score +1(при ничьей — нет). Правило±1пишется синхронно с отчётом об убийстве, хука для его изменения нет. Погибший игрок становится наблюдателем за убийцей до следующего раунда. - Реплики звука отсюда:
frag— убийце,death— жертве,victory— победившей команде и наблюдателям (при любом исходе),defeat— всем остальным в конце раунда. Существует ровно пять движковых событий (roundStart,victory,defeat,frag,death);gameConfig.soundCuesсопоставляет их именам из звукового конфига игры, озвученные таким образом звуки обходят лимит мировых голосов, несопоставленная реплика не отправляется, а{}их отключает. - Команды.
gameConfig.teamsсопоставляет имена команд числовым id и включает команду наблюдателей; приnoSpectators: trueв нём ровно одна команда, и входящий человек сразу попадает в неё (без голосования). Одна играющая команда — допустимая конфигурация. Вместимость команды —respawns[team].lengthна текущей карте. Вход в полную команду вызываетscripted.removeOneForHuman(team); если он вернулfalse, игрок получаетTEAMS_TEAM_FULLи остаётся на месте. Смена команды в течениеtimers.teamChangeGracePeriodот старта раунда бесплатна; позже сменивший команду погибает и наблюдает до следующего раунда. Падение ниже 2 активных людей сбрасывает статистику и сразу начинает новый раунд — если только игра не объявилаendlessRound: true, который отключает все инициированные движком перезапуски раунда (это правило, уничтожение команды и таймер раунда);/nrи смена карты по-прежнему его перезапускают. - Ротация карт. Истечение
timers.mapTimeзапускает системное голосование, предлагающееmapsInVoteкарт из каталога; игрок может предложить карту и через меню голосований (единственное предложение применяется сразу); переключение ждётtimers.mapChangeDelay. Смена карты пересоздаёт scripted-участников, сбрасывает панель и таблицу статистики, очищает голосования и сбрасывает ранг и состояние на платформу. - Наблюдатели. Погибшие игроки и члены команды наблюдателей следят за другим участником (камера после гибели следует за убийцей);
spectatorKeys(nextPlayer/prevPlayer, из набора клавиш0) переключают наблюдаемого; курсор скрывается после 3 с без движения мыши. - Игровые сообщения. Коды зафиксированы движком (
vimp-engine/src/config/gameCodes.js):winnerTeam→ индекс0gameInform.list,roundStart→1,gameOver→2. Индексы позиционные — перестановка массива меняет смысл; код1заодно запускает анимацию панели/логотипа на клиенте. Технические сообщения (techInformList, с движковыми умолчаниями) индексируются, как перечислено в plugin-api.md; плейсхолдеры{0},{1}берутся из массива params. - Начальное голосование.
gameConfig.initialVote(обычно'teamChange') — голосование, отправляемое игроку сразу после его первого кадра;nullкидает прямо в игру. - Профиль игрока.
rank— целое число, принадлежащее движку, в кросс-игровом формате, рисуется лобби платформы;state— собственный JSON-блоб игры, стартующий сplayerState.defaultState, читается и пишется через объектvimpвonCoreEventи сбрасывается на конце раунда, смене карты и уходе. Оба разнесены по(пользователь, gameId); поскольку хост — недоверенный браузер, оба технически подделываемы — известное ограничение P2P-модели. - Кики. Голосования за кик и эндпоинта
/banнет. Защита от плохого хоста — миграция хоста движка: автоматические триггеры (перегрузка, скрытая вкладка, сетевой лаг) и голосование/changehost, которое считает платформа, а не хост (только режим лобби). Ничего из этого не проходит через плагин, и в меню голосований игры для него нет пункта. Пороги и коды закрытия: game-config.md и snapshot.md.
Менеджеры и модули, которыми управляет игра
Игра не заменяет менеджеры и модули движка; она управляет ими через gameConfig и хуки плагина:
| Компонент | Что поставляет игра | Поведение, видимое игре |
|---|---|---|
RoundManager | teams, респауны карт, scripted, timers.*, endlessRound | раунды, команды и ротация карт ровно как в правилах выше |
CommandProcessor | чат-команды через HostPlugin.chatCommands (registerCommand(name, handler)) | собственных команд у движка нет; обработчик — handler(ctx, gameId, args); неизвестная команда отвечает «Command not found»; при нескольких активных людях команда может пройти через голосование |
VoteCoordinator, Vote | игровые голосования (createVote), их тексты | кулдаун темы timeBlockedVote, время жизни voteTime; больше 7 вариантов листаются; ничья решается случайным выбором; голосование, созданное при открытом, ждёт в очереди |
Panel | panel.fields, activeKey | HUD по пользователям; уходят только изменения, раз за тик снапшота; авторитетные значения (здоровье, боезапас) живут в ядре и попадают на панель через его события |
Stat | схема stat (колонки, агрегация, команды) | строки таблицы и итоги команд; дельта за тик, полное состояние при входе |
Chat | HostPlugin.systemMessages (группы кодов игры) и, на клиенте, их тексты | у групп движка s/v/m/c есть английские умолчания; игровой код без текста молча отбрасывается; каждое системное сообщение — код, данные идут в params |
TimerManager | timers.* | игровой цикл, таймеры раунда и карты, RTT-пинги, проверки простоя, отложенные рестарт раунда и смена карты |
RTTManager | rtt.*, idleKickTimeout | замер задержки и кики, см. snapshot.md |
PlayerDataSync | playerState.defaultState | rank/state по участникам, см. plugin-api.md |
← Предыдущая: Rust-ядро движка (vimp-engine-core) · Следующая: Клиентская среда для авторов игр →