Skip to content

Архитектура скриптов: скрипт на своём объекте ​

В 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()) передаются инспектору и другим скриптам только когда изменились.

Проверить все скрипты проекта разом (например, перед публикацией библиотеки):

bash
KVAS_ALL_SCRIPTS=1 cargo build --manifest-path scripts/Cargo.toml

Повесили в инспекторе скрипт, которого ещё нет в собранной библиотеке, — редактор сам запускает сборку.

Один скрипт — одна обязанность ​

rust
// 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
FlashlightFlashlightF, мерцание батарейки
каждая дверьDoorпетля, замок, анимация, звук
лампыFlickerLightмигание, питание от щитка
вентилятор кухниSpinnerвращение лопастей
Counter (монстр)MonsterAiпатруль, зрение, слух, погоня, клипы анимации
Anna, PavelNpcработа, поворот к игроку, разговор (клипы Animator)
GameGameDirectorсюжет, замки дверей, сохранения, музыка
UIGameUIменю, настройки, пауза, журнал, щиток, HUD

Общие типы и помощники — в scripts/src/shared.rs (файл без скриптов, компилируется всегда).

Документация движка в редакторе ​

Справка → Справочник API (Shift+F1) — все публичные структуры, поля, перечисления, функции и методы движка с полным описанием из исходников (rustdoc). В редакторе кода подсказки берутся из того же справочника (после ctx. — методы Ctx), кнопка 📖 у подсказки или F1 на слове под курсором открывают полную страницу.

Kvas Engine