Zdarzenia i harmonogram
EventBus, Scheduler, Timer i Random — komunikacja między systemami, zadania z uchwytami, izolacja błędów i deterministyczna losowość.
EventBus#
Synchroniczna magistrala zdarzeń. Emisja idzie po kopii listy subskrybentów, a wyjątek jednego handlera nie blokuje pozostałych.
const off = this.getEvents().on<{ score: number }>('score:changed', ({ score }) => {
this.hud.setText(`Wynik: ${score}`)
})
this.getEvents().emit('score:changed', { score: 120 })
off()
API#
on(event, handler): () => off
subscribe(event, handler): () => off
subscribeForScene(sceneId, event, handler): () => off
off(event, handler); offById(event, id)
emit(event, payload)
clearScene(sceneId); clearAll(); listenerCount(event)
Subskrypcje per scena#
this.getEvents().subscribeForScene(this.id, 'player:died', () => {
this.getCamera().shake(200, 0.01)
})
Przy shutdown() sceny wszystkie jej subskrypcje znikają automatycznie — nie ma wycieków między scenami.
Zdarzenia silnika#
engine:start, engine:stop, engine:pause, engine:resume, engine:update, scene:start, scene:stop, assets:*, audio:unlocked.
Scheduler#
Zadania z uchwytami, właścicielami i izolacją błędów:
const handle = this.getScheduler().delay(2, () => {
this.spawnWave(2)
}, { owner: this.id })
this.getScheduler().interval(0.5, () => {
this.regenHp(1)
}, { owner: this.id, repeat: 10 })
this.getScheduler().cancel(handle)
this.getScheduler().cancelOwner(this.id)
scheduler.delay(seconds, callback, options?): TaskHandle
scheduler.interval(seconds, callback, options?): TaskHandle
scheduler.cancel(handle); scheduler.cancelOwner(owner): number; scheduler.clear()
scheduler.has(id); scheduler.count(owner?); scheduler.errorCount
TaskOptions = { owner?: string; repeat?: number; onError?: (e) => void }.
Izolacja błędów
Wyjątek w zadaniu nie zatrzymuje innych zadań. Trafia do errorCount i do onError, jeśli podano. Timery sceny są anulowane przy shutdown().
Przykład: fale przeciwników#
class GameScene extends Scene {
create() {
this.getScheduler().interval(8, () => this.spawnWave(), {
owner: this.id,
onError: (e) => this.getLogger().error('spawnWave failed', e)
})
}
private spawnWave() {
const count = this.getRandom().int(3, 6)
for (let i = 0; i < count; i++) {
this.createSprite('enemy', {
x: this.getRandom().float(100, 1180),
y: -40,
tags: ['enemy']
})
}
}
}
Timer#
Lżejszy wariant schedulera do prostych opóźnień:
this.getTimer().delay(1.5, () => this.flashScreen())
this.getTimer().interval(0.1, () => this.tick())
this.getTimer().pending
this.getTimer().clear()
Random#
Deterministyczna losowość z seedem (domyślnie 1337):
const random = this.getRandom()
random.next()
random.float(0, 100)
random.int(1, 6)
random.bool(0.3)
random.sign()
random.pick(['sword', 'axe', 'bow'])
const lootRandom = random.fork('loot')
- Ten sam seed → ta sama sekwencja, niezależnie od maszyny.
fork(salt)tworzy niezależny strumień — np. osobny dla lootu i osobny dla AI, żeby nie zaburzać sobie sekwencji.
const engine = createEngine({ parent: 'app', seed: 20260 })
Wskazówka
Nigdy nie używaj Math.random() w logice gry — łamie determinizm i powtarzalność zapisów. Cała losowość przechodzi przez Random.