Структура проекта
В итоге KILLSCRIPT загружает один клиентский и, для Reflex, один серверный скрипт. Исходникам не нужно повторять это ограничение. Статические импорты объединяются, поэтому файлы разделяются по компонентам, а не по количеству файлов в результате.
Рекомендуемая начальная структура
Заголовок раздела «Рекомендуемая начальная структура»src/├── client/│ ├── main.ts│ ├── controls.ts│ ├── network.ts│ └── features/│ ├── module-toggle.ts│ └── status-hud.ts├── server/ # Только Reflex│ ├── main.ts│ ├── network.ts│ └── features/│ └── state-control.ts└── shared/ ├── messages.ts └── settings.tspublic/├── images/├── sounds/└── ui/killscript.config.jsonЭто направление, а не требование заранее создать пустые папки. Добавляйте каталог, когда в модуле действительно появляется принадлежащий ему код.
Точки входа собирают компоненты
Заголовок раздела «Точки входа собирают компоненты»Пусть main.ts остаётся намеренно простым:
import "./features/module-toggle";import "./features/status-hud";Импортированный компонент регистрирует обработчики на верхнем уровне:
import { controls } from "../controls";
let enabled = false;
controls.toggle.onPressed(() => { enabled = !enabled; NotificationController.ShowHint( enabled ? "Module enabled" : "Module disabled", 1.5, );});Статические модули выполняются один раз при старте собранного скрипта. Функция
initSomething(), единственная задача которой — зарегистрировать обработчики,
не нужна. Экспортируйте функцию, когда другое место действительно должно
вызвать поведение, а не ради обязательной обёртки каждого файла.
Код находится у среды-владельца
Заголовок раздела «Код находится у среды-владельца»client/использует камеры, управление, UI, звук и другие клиентские API;server/использует Reflex API и принимает авторитетные решения;shared/содержит обычные типы, константы и преобразования данных без зависимости от конкретной среды;public/содержит ресурсы, копируемые в пакет;- настройки разрешено разделять, если обеим средам нужно одинаковое определение.
Компилятор отклоняет импорт клиентской реализации из server/ и наоборот. Так
архитектурная ошибка находится до запуска сгенерированного Lua.
Объявления можно хранить рядом с использованием
Заголовок раздела «Объявления можно хранить рядом с использованием»Настройки, управление и сетевые объявления разрешены в любом достижимом файле,
но должны инициализировать const на верхнем уровне:
// Правильно: настройка принадлежит этому компоненту.const settings = defineSettings("statusHud", { visible: true,});// Ошибка: сгенерированный JSON зависел бы от выполнения программы.function registerLater(): void { const settings = defineSettings("statusHud", { visible: true });}Выносите объявление в общий файл, когда его использует несколько компонентов. Если потребитель один, расположение рядом обычно понятнее.
Что создаёт компилятор
Заголовок раздела «Что создаёт компилятор»В собранный скрипт попадают только файлы, достижимые от точки входа:
src/client/main.ts ──▶ scripts/main.luasrc/server/main.ts ──▶ scripts/server.luapublic/** ──▶ ресурсы модуляОтносительные импорты пишутся без расширения. Не вызывайте require() вручную:
собранный скрипт содержит собственную статическую загрузку и не зависит от поиска
вложенных путей игрой.
В разделе Архитектурные паттерны разобраны включаемые компоненты, владение ресурсами и общее состояние.