Системы

Аспект сцены: игровая логика, у которой нет одного узла, где жить — маппинг ввода, пулы FX, HUD, игровой режим, телеметрия. У системы ровно тот же жизненный цикл, что у аспекта (onAttach / onDetach / onReconfigure, updateBefore / updateFixed / update, updateOrder / static after, события), и прикрепляется она к сцене, а не к узлу: scene.system(Fx) → scene.fx. Используй её там, где иначе написал бы класс «game» с ручным tick(), вызывающим всё по порядку.

Обзор

TypeScript
class Controls extends System<'controls'> {
  moveX = 0; moveY = 0; fire = false
  updateBefore() {                                     // РАНО: опрашиваем до шага симуляции
    this.moveX = (Input.key('KeyD') ? 1 : 0) - (Input.key('KeyA') ? 1 : 0)
    this.moveY = (Input.key('KeyW') ? 1 : 0) - (Input.key('KeyS') ? 1 : 0)
    this.fire = Input.key('MouseLeft')
  }
}
class Hud extends System<'hud', Scene, { announce: (text: string) => void }> {
  static after = [Controls]
  private banner = UIText('').style({ fontSize: 30, color: '#ffffff' })
  onAttach() { this.scene.setContent([UIWidget(this.banner).style({ left: '50%', top: '20%' })]) }
  announce(text: string) { this.banner.text = text; this.emit('announce', text) }
  update(dt: number) { this.banner.style.opacity = this.scene.get(Controls)!.fire ? 0.5 : 1 }
}

const scene = new Scene().system(Controls).system(Hud)
scene.open()
scene.hud.on('announce', (t) => console.log('[hud]', t))
scene.hud.announce('ROUND 1')

Прикрепление, доступ, открепление

TypeScript
scene.system(Class, opts?): scene & { name: instance }   // прикрепить + сконфигурировать (или переконфигурировать)
scene.get(Class): instance | undefined                   // безопасный доступ
scene.has(Class): boolean                                // проверка существования И type guard
scene.removeSystem(Class): scene                         // открепить (выполняет onDetach); цепочечный
scene.destroy(): void                                    // каждая система открепляется, затем сцена закрывается
Aspect.all(Class): readonly instance[]                   // системы регистрируются как аспекты

Те же правила, что у node.aspect(): имя аксессора берётся из первого generic-параметра, opts присваиваются до onAttach, второй system(Class, opts) переконфигурирует вместо повторного прикрепления, а аксессор типизирован на результате цепочки (const scene = new Scene().system(Hud) → scene.hud). destroy() открепляет системы в порядке «последняя прикреплённая — первая», пока сцена ещё существует; Scene2D затем уничтожается и нативно, 3D Scene закрывается, а её узлы остаются твоими.

Как написать систему

TypeScript
class Fx extends System<'fx'> {                 // <имя аксессора, вид сцены = Scene, события = {}>
  private sparks!: Particles
  onAttach() {                                  // this.scene — сцена, к которой прикрепили
    this.sparks = new Particles({ blend: 'add', rate: 0, lifetime: 0.4, size: 0.05 })
    this.scene.add(this.sparks)
  }
  onDetach() { this.sparks.destroy() }
  impact(p: Vec3) { this.sparks.position = p; this.sparks.spawn(20) }
}
  • this.scene (и this.node — тот же объект) — это сцена, типизированная вторым generic: System<'fx'> нацелен на 3D Scene, System<'fx', Scene2D> — на 2D. Прикрепление 2D-системы к 3D-сцене (или наоборот) — ошибка компиляции.
  • Внутри системы this.scene типизирован как простой вид сцены, без аксессоров других систем: доставай их через this.scene.get(Controls)!.
  • Системы создаёт scene.system(), никогда не new — конструктора нет; инициализируй в onAttach.
  • Порядок между системами и аспектами — один список на фазу: static after = [Controls] на аспекте может называть систему и наоборот (см. aspects.md).

Что относится к системе, а что к аспекту

Аспект (на узле) Система (на сцене)
Владеет поведением / состоянием одного узла состоянием всей сцены, пулами, сервисами
Примеры Health, ExplosiveBarrel, PlayerController, CameraFollow Controls, Fx, Hud, GameMode, Spawner
Находится через node.get(Ctor), Aspect.all(Ctor), use() в файле сцены scene.get(Ctor), scene.fx
Время жизни до removeAspect / node.destroy() до removeSystem / scene.destroy()

Не навешивай общесценное на узел-пустышку («пустой с аспектом Loop»): для этого и есть система. Но и не превращай состояние отдельного объекта в систему — hp бочки живёт на бочке.

Смотрите также