Skip to content

t1k:cocos:playable:signalbus

FieldValue
Moduleplayable
Version0.5.6
Efforthigh
Tools

Keywords: ads, cocos, event, playable, pub sub, signal, signalbus

/t1k:cocos:playable:signalbus

Singleton event bus using class constructors as keys. Signals are plain TS classes — no base class required. See also: t1k-cocos-playable-lifecycle, t1k-cocos-playable-gameflow.

Import path: db://assets/PLAGameFoundation/signalBus/SignalBus

  • SignalBus.instance — singleton
  • Key = class constructor (no string keys, no magic strings)
  • Duplicate subscriptions are silently ignored (safe to call subscribe multiple times)
  • Callbacks snapshot before firing — safe to modify subscriptions inside callbacks
// Subscribe
SignalBus.instance.subscribe(MySignal, (data: MySignal) => { ... });
// Unsubscribe — store callback reference to unsubscribe later
const cb = (data: MySignal) => { ... };
SignalBus.instance.subscribe(MySignal, cb);
SignalBus.instance.unsubscribe(MySignal, cb);
// Fire — pass an instance, not the constructor
SignalBus.instance.fire(new MySignal(value));
// Async one-shot wait
const result = await SignalBus.instance.waitFor(MySignal);
const result = await SignalBus.instance.waitFor(MySignal, 5000); // 5s timeout, throws on expire
// Cleanup
SignalBus.instance.clear(MySignal); // remove all listeners for one type
SignalBus.instance.clearAll(); // remove everything (use in scene teardown)
SignalBus.instance.hasSubscriptions(MySignal); // bool check

Signals are plain TS classes. Constructor args become typed payload fields.

// No payload
export class FirstInteractionSignal {}
// With payload — use readonly constructor fields
export class TapSignal {
constructor(
public readonly worldPosition: Vec3,
public readonly uiPosition: Vec2
) {}
}
SignalSource filePayload
ParametersReadySignalPlayableParamterTool/PlayableParameterUpdatesnone
AllAsyncParametersReadySignalscripts/parameter/ParameterControllernone
FirstInteractionSignalPLAGameFoundation/inputService/InputSignalsnone
TapSignalPLAGameFoundation/inputService/InputSignalsworldPosition, uiPosition
SwipeSignalPLAGameFoundation/inputService/InputSignalsdirection, velocity
DragSignalPLAGameFoundation/inputService/InputSignalsworldPosition, uiPosition, delta
TouchStartSignalPLAGameFoundation/inputService/InputSignalsworldPosition, uiPosition
TouchEndSignalPLAGameFoundation/inputService/InputSignalsworldPosition, uiPosition
RaycastHitSignalPLAGameFoundation/inputService/InputSignalshitNode, hitPoint

Wait for parameters before initializing scene:

await SignalBus.instance.waitFor(AllAsyncParametersReadySignal);
this.initScene();

Subscribe in onEnable / unsubscribe in onDisable (Cocos component pattern):

private onTap = (s: TapSignal) => { this.handleTap(s); };
onEnable() { SignalBus.instance.subscribe(TapSignal, this.onTap); }
onDisable() { SignalBus.instance.unsubscribe(TapSignal, this.onTap); }

Fire after state change:

SignalBus.instance.fire(new ParametersReadySignal());
Use caseRecommendation
Cross-system communicationSignalBus
Parent → child componentCocos Node events or direct call
One-shot async gate (wait for load)waitFor()
UI button clicksCocos Button event + direct callback
Scene-local tight couplingDirect method call
  • Firing the constructor instead of an instance: fire(MySignal) — wrong. Use fire(new MySignal()).
  • Forgetting to unsubscribe in onDisable/onDestroy — causes ghost callbacks on recycled nodes.
  • Calling clearAll() mid-game — clears every listener globally; reserve for scene resets.
  • Unsubscribe in onDestroy, not onDisable — disabled-but-not-destroyed nodes still receive signals; disable-only unsubscribe leaves dead subscriptions on hot-reload.
  • Signal payloads should be serializable — passing a node reference through a signal that crosses scene-replay = dangling reference.
  • Event names are global — namespace via prefixes (game.start, ui.toast) to avoid silent collisions across modules.