Перейти к содержимому

Структура проекта

В итоге 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.ts
public/
├── images/
├── sounds/
└── ui/
killscript.config.json

Это направление, а не требование заранее создать пустые папки. Добавляйте каталог, когда в модуле действительно появляется принадлежащий ему код.

Пусть main.ts остаётся намеренно простым:

src/client/main.ts
import "./features/module-toggle";
import "./features/status-hud";

Импортированный компонент регистрирует обработчики на верхнем уровне:

src/client/features/module-toggle.ts
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.lua
src/server/main.ts ──▶ scripts/server.lua
public/** ──▶ ресурсы модуля

Относительные импорты пишутся без расширения. Не вызывайте require() вручную: собранный скрипт содержит собственную статическую загрузку и не зависит от поиска вложенных путей игрой.

В разделе Архитектурные паттерны разобраны включаемые компоненты, владение ресурсами и общее состояние.