Организация Lua-кода
KILLSCRIPT запускает scripts/main.lua как клиентскую точку входа. У Reflex-модуля отдельно запускается scripts/server.lua. Каждый файл лучше строить из локального состояния, небольших функций и одного блока инициализации.
Базовая структура main.lua
Заголовок раздела «Базовая структура main.lua»local State = { enabled = true, frames = 0}
local function Update() State.frames = State.frames + 1
if not State.enabled then return end
ImGui:DrawDebugText( "Frames: " .. tostring(State.frames) )end
local function Initialize() print("Module initialized") Scheduler:OnFrame(Update)end
Initialize()Такой порядок легко читать:
- состояние;
- вспомогательные функции;
- обработчики;
- инициализация в конце файла.
Разделяйте клиентский код на файлы
Заголовок раздела «Разделяйте клиентский код на файлы»Если main.lua стал слишком большим, клиентскую часть можно разделить на несколько Lua-файлов. Все они должны лежать непосредственно в scripts/:
MyModule/└── scripts/ ├── main.lua # точка сборки и жизненный цикл ├── domain.lua # правила и расчёты ├── game_gateway.lua # обращения к API игры └── hud.lua # отображениеДополнительный файл возвращает свою публичную таблицу:
local Domain = {}
function Domain.ClampHealth(value) return math.max(0, math.min(value, 100))end
return DomainВ main.lua подключайте файл по имени без .lua и без префикса scripts/:
local Domain = require("domain")
print(Domain.ClampHealth(125))Это позволяет использовать многослойную архитектуру, не превращая main.lua в один большой файл. Для небольшого модуля достаточно одного файла; выделяйте слой, когда у него появляется самостоятельная ответственность.
Дополнительные файлы загружаются только клиентской частью. Reflex server получает один scripts/server.lua: оформляйте его слои локальными таблицами и функциями внутри файла либо заранее собирайте несколько исходников в один server.lua.
Используйте local
Заголовок раздела «Используйте local»Объявляйте переменные и функции через local, если они не должны быть глобальными:
local PanelVisible = true
local function TogglePanel() PanelVisible = not PanelVisibleendЭто защищает код от случайного пересечения имён и делает зависимости явными.
Проверяйте nil на границах API
Заголовок раздела «Проверяйте nil на границах API»Многие методы, включая методы Agents, корректно возвращают nil, когда объект пока отсутствует:
local localAgent = Agents:GetLocalAgent()
if localAgent == nil then returnendПроверяйте результат сразу после получения. Это особенно важно во время загрузки карты, смерти, наблюдения и смены раунда.
Не делайте тяжёлую работу каждый кадр
Заголовок раздела «Не делайте тяжёлую работу каждый кадр»Scheduler:OnFrame() вызывается с частотой FPS. Загружайте текстуры, звуки и UXML один раз при инициализации, а в кадре обновляйте только нужные значения.
local Icon = Textures:GetTexture("Icon.png")
Scheduler:OnFrame(function() if Icon ~= nil then ImGui:DrawTexture(Icon, Rect.new(20, 20, 64, 64)) endend)Учитывайте hot reload
Заголовок раздела «Учитывайте hot reload»После сохранения папочного модуля создаётся новый экземпляр Lua-кода. Не рассчитывайте, что локальные переменные переживут reload. Постоянные пользовательские данные храните в Storage, а настройки — в Config.
При создании объектов, которые можно удалить явно, сохраняйте ссылку:
local Line = WorldVisuals:CreateLineRenderer()
-- Когда объект больше не нужен:WorldVisuals:RemoveObject(Line)Line = nilРазделяйте client и server
Заголовок раздела «Разделяйте client и server»В Reflex не копируйте один и тот же update-код в обе точки входа:
main.lua— ввод, интерфейс, локальные эффекты и пользовательские данные;server.lua— серверные решения иScheduler:OnTick();Network— только необходимые сообщения между ними.
Подробная схема приведена в разделе Reflex.