Events

The listener pattern shared by scenes, nodes, device, app, Router, players and sockets: addEventListener / removeEventListener on the object that emits. There is no Emitter global to construct. UI elements take their handlers through chainable on* methods instead (onClick); an aspect's own events are on / off.

  • listen for a tap or click on a scene or a node
  • stop listening to an event
  • react when the app goes to the background or the screen resizes
  • make an aspect fire its own typed event
  • handle a button tap in the UI

At a glance

TypeScript
const scene = new Scene2D()
scene.open()
const onClick = (ev: ClickEvent<Node2D | null>) => console.log(ev.clientX, ev.clientY, ev.target)
scene.addEventListener('click', onClick)
scene.removeEventListener('click', onClick)        // remove by the SAME function reference

device.addEventListener('resize', (width, height) => console.log('screen', width, height))

Listen and stop listening: addEventListener / removeEventListener

TypeScript
obj.addEventListener(channel, callback): void
obj.removeEventListener(channel, callback): void

Channels and callback signatures are typed per object (a node's 'click' delivers a ClickEvent, a media player's 'completed' delivers nothing). Removal matches by function identity — keep a reference to any callback you'll need to remove.

TypeScript
const onPause = () => console.log('to the background: save state now')
app.addEventListener('pause', onPause)              // the app left the foreground
app.addEventListener('resume', () => console.log('back in front'))
device.addEventListener('offline', () => console.log('connection lost'))
Note

there is no once() and no public dispatch — only the owning object emits its own events. Removing a listener from inside a callback is safe: dispatch iterates a snapshot (Router's change is the exception — it walks the live list). A scene's click / touchstart keep one copy of a callback; elsewhere the same function added twice runs twice.

Objects with events and their channels

  • 3D Scene, 2D Scene2D — click, touchstart (the target is the hit node, or null)
  • 3D Node — click, touchstart, physics enter / exit; AR anchors also track / untrack
  • Node2D — click, touchstart (on its Physics2D shape), contact enter / exit, sprite animation loopReached / completed
  • device — resize, online, offline, hdrchange
  • app — pause, resume, url, keyboard
  • Router — change
  • AudioPlayer / VideoPlayer — completed, loopReached
  • Voice (game audio) — ended (add it when you play: a listener added after the voice already ended never fires)
  • WebSocket — open, message, close, error

Custom events from an aspect: on / off / emit

An aspect declares its own event map in its third generic parameter and exposes on(channel, cb) / off(channel, cb) (both chain); only the aspect itself emits. Listeners are dropped when the aspect detaches or its node is destroyed.

TypeScript
class Barrel extends Aspect<'barrel', Node, { explode: (b: Barrel, power: number) => void }> {
  hit() { this.emit('explode', this, 3) }
}
const crate = new Node().aspect(Barrel)
crate.barrel.on('explode', (b, power) => console.log('boom', power))   // typed by the class

StateMachine uses the same pair for its 'change' event, and Input.on / Input.off for 'keydown' / 'keyup' (see Input).

UI element events: onClick and other on* methods

UI elements don't use addEventListener; they take callbacks through chainable on* methods so handlers slot into the build chain:

TypeScript
const ball = new Node()
UIButton(UIText('Play')).onClick(() => console.log('play'))    // UI: chainable on* setter
ball.addEventListener('click', () => console.log('kick'))      // scenes / nodes: addEventListener

See also