Архитектура скриптов: скрипт на своём объекте
В Kvas, как и в Unity, поведение раскладывается по объектам: дверь знает, как открываться, игрок — как ходить, камера — как смотреть. Один объект может нести несколько скриптов сразу (игрок: движение, взаимодействие, инвентарь). Скрипты общаются сигналами и общим состоянием, а не одним огромным файлом.
Как устроена сборка
Каждый файл в scripts/src/ (кроме lib.rs) может содержать скрипты: тип с impl Script for Имя регистрируется сам. Сборкой управляет сгенерированный scripts/build.rs:
- компилируются только используемые скрипты — те, что висят на объектах сцен и префабов (
assets/**/*.scene.json), на объектах открытой в редакторе сцены (.kvas/used_scripts.txt, пишет редактор), или упомянуты в коде используемых скриптов (attach_script("Имя"), общие типы). Незаконченный скрипт, который ни на ком не висит, не ломает сборку и ничего не стоит; - файлы без скриптов (общий код, константы, помощники) компилируются всегда;
- у каждого скрипта сборка запоминает, есть ли у него
updateиfixed_update. Скрипт без них никогда не вызывается каждый кадр — ни вызова, ни обмена переменными; - переменные скрипта (его
fields()) передаются инспектору и другим скриптам только когда изменились.
Проверить все скрипты проекта разом (например, перед публикацией библиотеки):
KVAS_ALL_SCRIPTS=1 cargo build --manifest-path scripts/Cargo.tomlПовесили в инспекторе скрипт, которого ещё нет в собранной библиотеке, — редактор сам запускает сборку.
Один скрипт — одна обязанность
// scripts/src/world/door.rs — висит на петле двери
use kvas_engine::prelude::*;
pub struct Door {
pub open: bool,
pub locked: bool,
pub angle: f32,
current: f32,
}
impl Default for Door {
fn default() -> Self {
Self { open: false, locked: false, angle: 95.0, current: 0.0 }
}
}
impl Script for Door {
fn update(&mut self, ctx: &mut Ctx) {
// Игрок нажал E, глядя на дверь: Interactor прислал сигнал "use".
if !ctx.signals("use").is_empty() && !self.locked {
self.open = !self.open;
}
let target = if self.open { self.angle } else { 0.0 };
self.current += (target - self.current).clamp(-160.0 * ctx.dt(), 160.0 * ctx.dt());
ctx.transform_mut().rotation.y = self.current;
}
fn fields(&mut self) -> Vec<Field<'_>> {
vec![Field::bool("open", &mut self.open), Field::bool("locked", &mut self.locked), Field::f32("angle", &mut self.angle)]
}
}Створку двери (модель и коллайдер) делают дочерними объектами петли — поворачиваясь, петля несёт их с собой.
Как скрипты разговаривают
| Способ | Когда | Пример |
|---|---|---|
Сигнал адресату ctx.send_to(e, "канал", данные) | событие для конкретного объекта | игрок → дверь: use |
Сигнал всем ctx.emit("канал", данные) | событие, которое слушают несколько | ls.say (субтитры), ls.caught |
Приём ctx.signals("канал") | в update получателя | for s in ctx.signals("use") { ... } |
Общее состояние ctx.set_global / ctx.global | значение, у которого один владелец и много читателей | режим игры, сюжет, настройки |
Переменные другого скрипта ctx.var / ctx.set_var | настроить чужой скрипт | режиссёр запирает дверь: ctx.set_var(door, "Door", "locked", true.into()) |
Сигнал доставляется к следующему кадру и виден в update этого кадра. Данные — JSON (serde_json::json!).
Правило для общего состояния: у каждого ключа один хозяин-писатель. Сюжет пишет только режиссёр, настройки — только интерфейс, «на что смотрит игрок» — только Interactor. Остальные читают.
Пример: LAST SERVICE
| Объект | Скрипты | Что делают |
|---|---|---|
| Lida (игрок) | PlayerController, Interactor, Inventory, RoomAcoustics | ходьба/бег/присед и шаги; луч взгляда и E; что в руках; эхо комнаты |
| Eyes (камера игрока) | MouseLook, CameraFx | мышь и покачивание; шейдеры страха, крови, VHS |
| Flashlight | Flashlight | F, мерцание батарейки |
| каждая дверь | Door | петля, замок, анимация, звук |
| лампы | FlickerLight | мигание, питание от щитка |
| вентилятор кухни | Spinner | вращение лопастей |
| Counter (монстр) | MonsterAi | патруль, зрение, слух, погоня, клипы анимации |
| Anna, Pavel | Npc | работа, поворот к игроку, разговор (клипы Animator) |
| Game | GameDirector | сюжет, замки дверей, сохранения, музыка |
| UI | GameUI | меню, настройки, пауза, журнал, щиток, HUD |
Общие типы и помощники — в scripts/src/shared.rs (файл без скриптов, компилируется всегда).
Документация движка в редакторе
Справка → Справочник API (Shift+F1) — все публичные структуры, поля, перечисления, функции и методы движка с полным описанием из исходников (rustdoc). В редакторе кода подсказки берутся из того же справочника (после ctx. — методы Ctx), кнопка 📖 у подсказки или F1 на слове под курсором открывают полную страницу.