Клиент и Reflex
У обычного модуля одна клиентская среда. Reflex-модуль добавляет отдельную серверную среду и транспорт между ними:
ввод/UI клиента ── намерение ──▶ решение Reflexотображение ◀── состояние ── результат ReflexСреды не разделяют переменные, живые объекты и обработчики. Вспомогательный код
из src/shared/ может импортироваться обеими точками входа, но попадёт в каждую
сборку отдельной копией. Через работающую границу клиента и Reflex проходят
только сериализованные сообщения.
Выбор владельца по результату
Заголовок раздела «Выбор владельца по результату»| Работа | Владелец | Причина |
|---|---|---|
| HUD, UXML, камеры, звук, подсказки | Клиент | Эти API существуют там, где игра их отображает |
| Бинды модуля и локальное состояние кнопок | Клиент | Input actions доступны только клиенту |
| Линии и отметки в мире | Клиент | Это отображение, а не серверные сущности |
| Решения, которые серверный API может наблюдать и применить | Reflex | Им не нужно доверять состоянию со слов клиента |
| Серверное время и повторяющиеся проверки | Reflex | Им не нужно ждать клиентские кадры и сетевой круг |
| Показ подтверждённого сервером результата | Клиент | Reflex передаёт данные, клиент выбирает отображение |
Задайте вопрос: какая среда способна увидеть нужное состояние, применить изменение и проверить результат? Там и должно приниматься решение. Reflex работает на сервере, но его API намеренно ограничен и учитывает видимость; само расположение на сервере не делает любое решение авторитетным. Клиент может показать ожидание или предсказанный результат, но не должен становиться источником истины для состояния, которое Reflex способен применить.
Структура проекта
Заголовок раздела «Структура проекта»Клиентскому модулю достаточно:
src/client/main.tsReflex-модуль добавляет:
src/server/main.tssrc/shared/messages.tsКлиент импортирует @killscript/sdk/client, сервер —
@killscript/sdk/server. Компилятор не разрешает клиенту импортировать
серверную реализацию и наоборот. Обычные интерфейсы и типы сообщений храните в
src/shared/.
Намерение → решение → подтверждённое состояние
Заголовок раздела «Намерение → решение → подтверждённое состояние»// Клиент сообщает, чего попросил игрок.controls.toggle.onPressed(() => { coreNetwork.send("setEnabled", { enabled: !displayedEnabled });});
coreNetwork.on("stateChanged", ({ enabled }) => { displayedEnabled = enabled; updateHud(enabled);});// Reflex владеет состоянием и подтверждает его.let enabled = false;
coreNetwork.on("setEnabled", ({ enabled: requested }) => { const local = Agents.GetLocalAgent(); enabled = local?.Stats.IsAlive === true && requested;
coreNetwork.send("stateChanged", { enabled, changedAtTick: Time.Tick, });});Клиент не утверждает, что запрос выполнен, а обновляется из состояния, принятого Reflex. И наоборот: Reflex не должен спрашивать клиента о данных, которые уже доступны в серверных глобальных API.
Полное объявление протокола и пакетная отправка описаны в Типизированной сети.
Планировщик каждой среды
Заголовок раздела «Планировщик каждой среды»// Клиентское отображение следует кадрам.import { scheduler } from "@killscript/sdk/client";scheduler.frame(updateCamera);// Серверная симуляция следует тикам.import { scheduler } from "@killscript/sdk/server";scheduler.tick(updateServerState);В открытом API нет клиентского OnTick и серверного OnFrame. Если отображению
нужно заметить новый тик симуляции, сравнивайте Time.Tick внутри кадрового
обработчика.