Events¶
Doriax events use FunctionSubscribe<Ret(Args...)>, a multicast delegate that stores
callbacks with string tags. Adding a callback with an existing tag replaces the
previous one, which prevents duplicate subscriptions from the same source.
Events work identically in C++ and Lua. C++ uses macros from FunctionSubscribe.h.
Lua uses RegisterEvent and RegisterEngineEvent globals registered by LuaBinding.
Let the Code Editor write it
The event menu of the Code Editor adds the registration and an empty handler for any event on this page, in C++ or Lua.
How dispatch works¶
Platform input / frame tick
│
▼
Engine.systemTouchStart / systemUpdate
│
▼
FunctionSubscribe.call ──► C++ callbacks
└─► Lua closures
- The platform layer calls
Engine::systemDraw()(orsystemUpdate()when nothing can be drawn),systemTouchStart(), etc. - Engine forwards to static
FunctionSubscribemembers (onUpdate.call(), etc.). - Subsystems and components fire their own events during simulation (physics contacts, UI pointer events, action start/stop).
- Each subscriber runs in registration order unless disabled or removed.
A callback can add or remove subscribers, or destroy the object that owns the event. A subscriber added during a call first runs on the next one, and one removed before its turn is skipped.
FunctionSubscribe API¶
Defined in engine/core/util/FunctionSubscribe.h.
| Method | Purpose |
|---|---|
add(tag, callback) |
Register a callback; replaces existing tag |
add(tag, lua_State*) |
Register a Lua function with tag |
remove(tag) |
Remove one callback by exact tag |
removeByTagSubstring(substr) |
Remove all callbacks whose tag contains substring |
clear() |
Remove all callbacks |
call(args...) / operator() |
Invoke all enabled callbacks |
setEnabled(bool) |
Enable or disable the entire event without removing subscribers |
With DORIAX_CRASH_GUARD enabled, failing subscribers are removed automatically and
optionally reported through a global crash handler. A C++ exception that escapes a
subscriber counts as a failure too, and the report carries its message. Copies of an
event share their subscribers, so a failing one stops running in all of them.
Engine events¶
Engine exposes frame, lifecycle, input, and pointer events.
| Event | Signature | Use |
|---|---|---|
onInit |
void() |
User init via DORIAX_INIT macro (access through getOnInit()) |
onViewLoaded |
void() |
View/surface ready |
onViewChanged |
void() |
Canvas/view changed; Lua name: onCanvasChanged |
onViewDestroyed |
void() |
View destroyed |
onDraw |
void() |
Every drawn frame, before the scenes are drawn |
onUpdate |
void() |
Variable-step gameplay update |
onFixedUpdate |
void() |
Fixed-step simulation update |
onPostUpdate |
void() |
Work after regular update |
onPause / onResume |
void() |
App/game pause and resume |
onShutdown |
void() |
Runtime shutdown |
onSceneLoaded |
void() |
A scene loaded by SceneManager finished loading, before its loading scene closes |
onTouchStart/End/Move |
void(int, float, float) |
Touch identifier, x, y |
onTouchCancel |
void(int, float, float) |
One touch canceled; identifier and last recorded canvas position |
onMouseDown/Up |
void(int, float, float, int) |
Button, x, y, modifiers |
onMouseMove/Scroll |
void(float, float, int) |
Position or scroll offsets plus modifiers |
onMouseEnter/Leave |
void() |
Pointer enters/leaves view |
onKeyDown/Up |
void(int, bool, int) |
Key, repeat flag, modifiers |
onCharInput |
void(wchar_t) |
Text input codepoint |
onGamepadConnect/Disconnect |
void(int) |
Gamepad id connected/disconnected |
onGamepadButtonDown/Up |
void(int, int) |
Gamepad id, button index |
onGamepadAxisMove |
void(int, int, float) |
Gamepad id, axis index, value -1…1 |
Touch cancellation¶
Use onTouchEnd for a finger lifting normally and onTouchCancel for an
interrupted contact. Both identify the finger, but cancellation does not dispatch
UI pointer-up or click events. In a per-pointer cancellation, the canceled touch is
removed from Input before its callback runs; other fingers remain active. Release
only the gameplay action associated with that identifier in your handler.
A full cancellation removes all tracked touches before emitting onTouchCancel
once for each of them. There is no separate global notification; an empty full
cancellation emits no callbacks. Each callback receives the last recorded position
in logical canvas coordinates. An already removed or unknown identifier produces
no additional notification. Use Input::numTouches() (Input.numTouches() in Lua)
to check whether any fingers remain; it is zero during every full-cancel callback.
Subscribe to onTouchCancel as well as onTouchEnd when tracking held fingers.
Cancellation notifications follow pauseGameEvents, and are not filtered by
setIgnoreEventsHandledByUI or the current pointer position.
In the Code Editor, choose Add event > Input > Touch > onTouchCancel
(Touch is the section label). The generated handler has this signature:
Use REGISTER_ENGINE_EVENT(onTouchCancel) in the C++ constructor and
UNREGISTER_ENGINE_EVENT(onTouchCancel) in the destructor. In Lua:
function Player:init()
RegisterEngineEvent(self, "onTouchCancel")
end
function Player:onTouchCancel(pointer, x, y)
-- Release the action associated with this pointer.
end
Input routing options¶
Configure how events reach gameplay vs. UI:
| Engine setting | Effect |
|---|---|
setIgnoreEventsHandledByUI(true) |
Gameplay handlers skip events already consumed by UI |
setAllowEventsOutCanvas(false) |
Clip events to the logical canvas |
pauseGameEvents(true) |
Pause game event delivery (editor pause, etc.) |
setCallMouseInTouchEvent / setCallTouchInMouseEvent |
Mirror mouse/touch for cross-platform testing |
C++ registration macros¶
All macros live in FunctionSubscribe.h and generate a unique tag:
typeid(ClassName)_objectAddress_methodName.
Engine events¶
Any FunctionSubscribe member¶
PhysicsSystem* physics = scene->getSystem<PhysicsSystem>().get();
REGISTER_EVENT(physics->beginContact2D, onBeginContact);
UNREGISTER_EVENT(physics->beginContact2D, onBeginContact);
Component events on the same entity¶
REGISTER_COMPONENT_EVENT(UIComponent, onClick, onClick);
REGISTER_UI_EVENT(onClick, onClick); // shorthand for UIComponent
REGISTER_BUTTON_EVENT(onPress, onPress); // ButtonComponent
REGISTER_SCROLLBAR_EVENT(onChange, onChange); // ScrollbarComponent
REGISTER_PANEL_EVENT(onMove, onMove); // PanelComponent
Matching UNREGISTER_* macros remove subscriptions in destructors.
Example: full C++ script¶
class DoorTrigger : public doriax::ScriptBase {
public:
DoorTrigger(doriax::Scene* scene, doriax::Entity entity);
~DoorTrigger();
void onUpdate();
void onClick(float x, float y);
void onBeginContact(doriax::Body2D bodyA, unsigned long shapeA, doriax::Body2D bodyB, unsigned long shapeB);
private:
doriax::PhysicsSystem* physics = nullptr;
};
DoorTrigger::DoorTrigger(Scene* scene, Entity entity) : ScriptBase(scene, entity) {
physics = scene->getSystem<PhysicsSystem>().get();
REGISTER_ENGINE_EVENT(onUpdate);
REGISTER_UI_EVENT(onClick, onClick);
if (physics) {
REGISTER_EVENT(physics->beginContact2D, onBeginContact);
}
}
DoorTrigger::~DoorTrigger() {
UNREGISTER_ENGINE_EVENT(onUpdate);
UNREGISTER_UI_EVENT(onClick, onClick);
if (physics) {
UNREGISTER_EVENT(physics->beginContact2D, onBeginContact);
}
}
Lua event registration¶
LuaBinding::registerHelpersFunctions exposes two globals:
| Function | Arguments | Behavior |
|---|---|---|
RegisterEvent(self, event, methodName [, tag]) |
Script table, event object, method name | Subscribes self:methodName(...) to any exposed FunctionSubscribe |
RegisterEngineEvent(self, methodName [, tag]) |
Script table, engine event name | Looks up Engine.<methodName> and subscribes |
If tag is omitted, Doriax builds:
ClassName_instancePointer_methodName
Engine event (Lua)¶
function Player:init()
RegisterEngineEvent(self, "onUpdate")
end
function Player:onUpdate()
-- called every frame
end
Component event (Lua)¶
Lua has no per-component macros. The pattern that replaces REGISTER_UI_EVENT,
REGISTER_BUTTON_EVENT, and the other component macros is always the same: wrap the
entity, get the component, and pass its event to RegisterEvent:
function Trigger:init()
-- Get the UIComponent through a wrapper for this entity
local image = Image(self.scene, self.entity)
local ui = image:getUIComponent()
RegisterEvent(self, ui.onClick, "onClick")
end
function Trigger:onClick(x, y)
print("clicked", x, y)
end
| C++ macro | Lua equivalent |
|---|---|
REGISTER_ENGINE_EVENT(onUpdate) |
RegisterEngineEvent(self, "onUpdate") |
REGISTER_UI_EVENT(onClick, onClick) |
RegisterEvent(self, wrapper:getUIComponent().onClick, "onClick") |
REGISTER_BUTTON_EVENT(onPress, onPress) |
RegisterEvent(self, button:getButtonComponent().onPress, "onPress") |
REGISTER_SCROLLBAR_EVENT(onChange, onChange) |
RegisterEvent(self, scrollbar:getScrollbarComponent().onChange, "onChange") |
REGISTER_PANEL_EVENT(onMove, onMove) |
RegisterEvent(self, panel:getPanelComponent().onMove, "onMove") |
REGISTER_EVENT(physics->beginContact2D, onBeginContact) |
RegisterEvent(self, physics.beginContact2D, "onBeginContact") |
REGISTER_EVENT(AdMob::onAdLoaded, onAdLoaded) |
RegisterEvent(self, AdMob.onAdLoaded, "onAdLoaded") |
function MenuButton:init()
local button = Button(self.scene, self.entity)
local bc = button:getButtonComponent()
RegisterEvent(self, bc.onPress, "onPress")
end
function MenuButton:onPress()
print("button pressed")
end
Cleanup is also automatic in Lua: when the scene unloads, or destroyBundle removes the
script's entity, all subscriptions tagged with the script instance are removed — there is
no UNREGISTER_* to call.
Physics event (Lua)¶
function Trap:init()
local physics = self.scene:getPhysicsSystem()
RegisterEvent(self, physics.beginContact2D, "onBeginContact")
end
function Trap:onBeginContact(bodyA, shapeA, bodyB, shapeB)
print("2D contact")
end
The filters (preSolve2D, shouldCollide2D, shouldCollide3D) use what the method
returns, so end them with an explicit return: a method that returns nothing rejects the
contact. See PhysicsSystem.
Custom tag¶
Use custom tags when you need to replace or manually remove a specific subscription.
Physics events¶
PhysicsSystem exposes 2D (Box2D) and 3D (Jolt) collision events.
2D events¶
| Event | Signature | Meaning |
|---|---|---|
beginContact2D |
void(Body2D, unsigned long, Body2D, unsigned long) |
Contact begins |
endContact2D |
void(Body2D, unsigned long, Body2D, unsigned long) |
Contact ends |
hitContact2D |
void(Body2D, unsigned long, Body2D, unsigned long, Vector2, Vector2, float) |
Impact with hit data |
beginSensorContact2D |
void(Body2D, unsigned long, Body2D, unsigned long) |
Sensor overlap begins |
endSensorContact2D |
void(Body2D, unsigned long, Body2D, unsigned long) |
Sensor overlap ends |
preSolve2D |
bool(Body2D, unsigned long, Body2D, unsigned long, Manifold2D) |
Return false to disable contact before solve |
shouldCollide2D |
bool(Body2D, unsigned long, Body2D, unsigned long) |
Filter pairs before collision |
beginContact2D, endContact2D, hitContact2D and preSolve2D fire only for pairs where
one of the shapes has Contact Events, Enable Hit Events or PreSolve Events on,
and each is off by default — see
Body2D events.
3D events¶
| Event | Meaning |
|---|---|
onBodyActivated3D / onBodyDeactivated3D |
Body activation state changed |
onContactAdded3D / onContactPersisted3D / onContactRemoved3D |
3D contact lifecycle |
shouldCollide3D |
Filter whether two 3D bodies/shapes should collide |
Unlike the 2D contact events, these run while Jolt is stepping, which limits what they may do to the world — see What a 3D callback may do.
UI component events¶
UIComponent events (also available through REGISTER_UI_EVENT):
| Event | Signature | When |
|---|---|---|
onGetFocus / onLostFocus |
void() |
Focus changes |
onPointerEnter / onPointerLeave |
void(float, float) |
Pointer enters/leaves widget bounds |
onPointerMove |
void(float, float) |
Pointer moves over widget |
onPointerDown / onPointerUp |
void(float, float) |
Pointer button down/up |
onClick / onDoubleClick |
void(float, float) |
Click gestures |
onDragStart / onDrag / onDragEnd |
void(float, float) |
Drag gestures |
Widget-specific events¶
| Component | Events |
|---|---|
ButtonComponent |
onPress, onRelease |
ScrollbarComponent |
onChange |
PanelComponent |
onMove, onResize |
TextEditComponent |
onChange, onSubmit |
Action and sound events¶
| Component | Events |
|---|---|
ActionComponent |
onStart, onStop, onPause, onStep |
SoundComponent |
onStart, onStop, onPause |
Use these to chain gameplay reactions to tweens, timelines, or audio playback.
Service events¶
The monetization services report through static events of their classes, which belong to no entity or scene:
| Class | Events |
|---|---|
AdMob |
onInitialized, onConsentUpdated, onAdLoaded, onAdFailedToLoad, onAdShown, onAdFailedToShow, onAdDismissed, onAdClicked, onAdImpression, onUserEarnedReward, onAdPaid, onAdInspectorClosed |
InAppPurchase |
onInitialized, onDisconnected, onProductsQueried, onPurchaseUpdated, onPurchaseFailed, onPurchasesQueried, onPurchaseAcknowledged, onPurchaseConsumed |
WebPortal |
onInitialized, onAdStarted, onAdFinished, onAdError, onDataLoaded, onDataLoadFailed, onDataSaveFailed |
Pass the static member to REGISTER_EVENT in C++ or RegisterEvent in Lua:
The platform answers on its own thread, and the engine calls these subscribers at the
start of the next frame, on the game thread. A C++ handler must take the parameters by
value, exactly as the event declares them (std::string, not const std::string&).
Since the events outlive scenes, remove C++ subscriptions in the destructor; Lua script
subscriptions go away with the script. See
How results arrive.
Easing as an event¶
Ease extends FunctionSubscribe<float(float)>. Timed actions can use custom easing
curves by subscribing to an Ease instance.
Cleanup¶
| Context | Cleanup mechanism |
|---|---|
| C++ script destructor | Call matching UNREGISTER_* macros |
| Lua script unload | cleanupLuaScripts removes tags containing instance pointer |
| Bundle instance destroyed | destroyBundle stops its C++ and Lua scripts as a scene unload does |
| Scene unload | Scene::removeSubscriptionsByTag, including the PhysicsSystem events |
| Component callbacks | Engine::clearComponentSubscriptions(scene) |
| Full reset | Engine::clearAllSubscriptions() |
Always unregister in C++ destructors when the object can outlive a scene reload. A callback added
straight to a component event (button.onPress.add("pressed", ...)) is not removed with the script
that added it, so unregister it too — Engine::clearComponentSubscriptions(scene) drops every
component callback of a scene at once.
Best practices¶
- Register in a constructor or Lua
init(). Never register in hot per-frame code. - Unregister in destructors (C++) or rely on Lua cleanup for script components.
- Use
onFixedUpdatefor physics-sensitive simulation, notonUpdate. - Inside
onFixedUpdate, move physics bodies withBody2D/Body3DsetPositionor velocities — anObject::setPositionon a body-carrying entity is discarded there. See Moving a body. - Enable
setIgnoreEventsHandledByUI(true)when UI and gameplay share screen space. - Return
falsefrompreSolve2DorshouldCollide*filters to implement one-way platforms, team filters, or trigger volumes.
Reference¶
- Engine — engine events (
onUpdate, input, lifecycle) - PhysicsSystem — collision and contact events
- AdMob, InAppPurchase, WebPortal — service events
- Script workflow: Creating Scripts