Scene events provide a way to send signals up or down the scene graph. They are all about decoupling parts of the scene graph from each other, allowing them to communicate through messages instead of directly binding to each other.
You define a custom event, send it from an entity, and any component along the way responds by overriding OnReceive.
SendDownsends the event from an entity downward through its children, grandchildren, and so on (depth-first).SendUpsends the event upward through its parent, grandparent, and so on toward the root.
Multiple components can respond to a scene event. Every component in an entity’s hierarchy gets its OnReceive called. A component can consume the event (return true) to stop propagation, or pass through (return false) to let it keep traveling.
The three things you need:
A custom event: A class that inherits from
scene_event.A responding component: Overrides
OnReceive, casts the event, and reacts.A send call:
Entity.SendDown(Event)orEntity.SendUp(Event).
Scene events can be reused across your projects, you can expand upon scene events by adding additional events or tweaking the behavior of entities and components in a chain of events to do something slightly different.
Cast Pattern
The OnReceive call provides a way to create a generic scene_event. To find out what specific type was sent, use a failable cast:
if (SpecificEvent := my_event[E]):
# Cast succeeded — access SpecificEvent.Amount, etc.If the event isn't that type, the cast fails and the if block is skipped. This filters for only the events you need. The OnReceive gets called for every scene event passing through the hierarchy, so the cast is essential.
Event Propagation
The SendDown event traverses depth-first, it calls OnReceive on each component of the starting entity, then recurses into the first child (and its children) before moving to the next sibling.
The SendUp event traverses in the opposite direction from SendDown, it starts entity first, then parent, then grandparent, up to the root.
Both event types stop immediately if any component consumes the event (returns true from OnReceive). Both return logic — true if the event was consumed somewhere, or false if it traveled the entire path without being consumed.
Consumed Events
Return
truefromOnReceive— propagation stops, no further entities react to the event.
This is useful when a component fully handles the event and no additional components in the hierarchy respond to the event. For example, a shield absorbing damage before it reaches the entity underneath.
damage_event<public> := class(scene_event):
Amount<public>:int
# A shield that absorbs all damage and stops it from reaching children.
shield_component := class(component):
var ShieldHP:int = 100
OnReceive<override>(E:scene_event):logic =
if (Dmg := damage_event[E]):
set ShieldHP -= Dmg.Amount
If the shield entity is an ancestor of the health entity, SendDown hits the shield first. Because the shield returns true, the health component is not affected by the event.
Pass Through Events
Return
false(the default) — the event keeps traveling through the hierarchy.
The base OnReceive on component returns false, which is why calling (super:)OnReceive(E) at the end of your override is the standard pattern for passing an event onward.
Scene Events Examples
Following are code examples of basic scene events behavior.
Define an Event
my_event<public> := class(scene_event):
Amount<public>:int
Respond to an Event in a Component
my_responder := class(component):
var Score:int = 0
OnReceive<override>(E:scene_event):logic =
if (Event := my_event[E]):
set Score += Event.Amount
(super:)OnReceive(E)
Send Event
Event := my_event{Amount := 10}
MyEntity.SendDown(Event)
Handle Multiple Event Types
OnReceive<override>(E:scene_event):logic =
if (DmgEvent := damage_event[E]):
# Handle damage...
else if (HealEvent := heal_event[E]):
# Handle healing...
(super:)OnReceive(E)Use var Fields to Collect Information from Responders
my_event<public> := class(scene_event):
I<public>:int
var Activations:int = 0 # Responders can increment this
# In the responder:
OnReceive<override>(E:scene_event):logic =
if (CE := my_event[E]):
set CE.Activations += 1
(super:)OnReceive(E)
Things to Watch Out For
Always call
(super:)OnReceive(E)to ensure superclasses get theirOnReceiveinvoked.For classes that inherit directly from components, this is less important. In those cases, returning
falseto continue propagation at the end of the function is reasonable.Always cast before accessing fields. The parameter type is
scene_event, not your specific event class.Don't consume accidentally. Returning
truesilently hides the event from the rest of the hierarchy.The entity must be in the scene for
SendUp/SendDownto work. This restriction might be lifted in the future.
API Quick Reference
API | What it does |
| Interface. Inherit from this to make your own event type. |
| Send event to this entity and all descendants. |
| Send event to this entity and all ancestors. |
| Send event from this component's entity downward. (prefer |
| Override to respond. Return
|