simvx.core.scene_tree¶
SceneTree: Central manager for the node tree, groups, input routing, and UI focus.
Module Contents¶
Classes¶
Central manager for the node tree, groups, input routing, and UI focus. |
Data¶
API¶
- simvx.core.scene_tree.RESERVED_AUTOLOAD_EVENTS¶
‘events’
- simvx.core.scene_tree.log¶
‘getLogger(…)’
- class simvx.core.scene_tree.SceneTree(screen_size=None, *, isolated_input: bool = False)[source]¶
Central manager for the node tree, groups, input routing, and UI focus.
Owns the root node and drives per-frame
process/physics_process/drawtraversals. Also manages pause state, scene changes, and the UI input pipeline (mouse, keyboard, popups).Initialization
- classmethod current() simvx.core.scene_tree.SceneTree | None[source]¶
Return the most recently activated SceneTree, or
None.Activation happens automatically on
set_root/change_scene. Used byInputSimulatorto deliver scene-tree + UI-tree events without requiring callers to thread a tree reference through.
- property events: simvx.core.event_bus.EventBus[source]¶
Engine-provided typed event bus.
Use
tree.events.subscribe(EventCls, handler)to register a handler andtree.events.publish(event)(orpublish_deferred) to dispatch. The bus surviveschange_scene()– subscriptions held by autoloads or other long-lived objects keep firing across scene swaps. Deferred events queued during a frame are dispatched at the start of the nextprocess()tick, before any node_processruns.
- property audio_backend: AudioBackend | None[source]¶
The active audio backend, or
Noneif none was initialised.Returns the union :class:
AudioBackendtype for backwards compatibility: callers that only need one facet should prefer the narrowed :attr:audio_playback/ :attr:audio_streaming/- Attr:
audio_busesproperties so the type checker enforces the boundary. Reaching for the underscore-prefixed attribute is engine-private and may change.
The engine sets this during
App.runviamake_backend. Tests and headless harnesses that don’t initialise audio seeNone.
- install_audio_backend(backend: simvx.core.audio_protocol.AudioPlaybackBackend) None[source]¶
Install the audio backend for this tree: the one canonical path.
This is the only supported way to attach a backend; assigning the private
_audio_backendslot is no longer part of the contract. Used byApp.run/WebAppat startup and by tests that inject a- Class:
NullAudioBackendor a mock.
Semantics mirror startup driver selection in other engines (Godot’s
--audio-driver, pyglet’saudiooption): a backend is installed once, before the tree starts producing sound. Re-installing shuts the previous backend down first so device handles don’t leak.Facet conformance is enforced where it is consumed, not here: the
- Attr:
audio_playback/ :attr:audio_streaming/ :attr:audio_busesaccessors narrow viaisinstanceand returnNonefor a backend that doesn’t implement that facet, and callers raise- Class:
AudioCapabilityErroronNone. Gating here as well would duplicate that boundary and reject legitimate partial backends.
- property audio_playback: AudioPlaybackBackend | None[source]¶
The active backend narrowed to :class:
AudioPlaybackBackend, orNone.Always equals :attr:
audio_backendcast to the playback facet when a backend is present: every shipped backend implements playback, including the silent :class:NullAudioBackend.
- property audio_streaming: AudioStreamingBackend | None[source]¶
The active backend narrowed to :class:
AudioStreamingBackend, orNone.Returns
Nonewhen the active backend doesn’t implement streaming (the Null backend, any future no-device test stub). Callers that need streaming (:class:AudioSynthdriver, AudioWorklet feeds) should raise :class:AudioCapabilityErroronNone.
- property audio_buses: AudioBusBackend | None[source]¶
The active backend narrowed to :class:
AudioBusBackend, orNone.Always equals :attr:
audio_backendcast to the bus facet when a backend is present: every shipped backend implements bus + capability advertisement.
- audio_listener_3d() AudioListener3D | None[source]¶
The active 3D audio listener, lazy-creating one if none exists.
Returns the most recently entered :class:
AudioListener3Din the scene. If none has been added, auto-creates a fallback parented to the activeCamera3Dwith a one-time warning. ReturnsNoneonly if there’s no camera either.Audio players call this every frame, so the auto-creation is cheap once it’s happened (the cached listener is returned).
- audio_listener_2d() AudioListener2D | None[source]¶
The active 2D audio listener, lazy-creating one if none exists.
Same contract as :meth:
audio_listener_3dbut for 2D.
- activate_input()[source]¶
Context manager to make this tree’s Input/InputMap the active ones.
Use this when processing the tree so that game code calling
Input.is_action_pressed(...)sees this tree’s input state.
- property is_running: bool[source]¶
Whether the tree is actively being ticked by its driving app.
Set to False by :meth:
quit(or the graphics backend’s appquit()), signalling the main loop to exit at the end of the current frame.
- property now: float[source]¶
Monotonically-increasing scene time, in seconds.
Accumulates
dtat the start of every :meth:tickcall (after any :attr:simvx.graphics.App.time_scalescaling has been applied), so slow-motion and hitstop also slow this clock. Frozen while- Attr:
pausedis True. Resets to0.0only by constructing a freshSceneTree.
Use for time-based animation, slow-mo gating, and any “how long has the scene been running” query: preferable to per-node
self._time += dtaccumulators because every consumer reads the same monotonic value.
- quit() None[source]¶
Request a clean shutdown of the running tree.
Emits :attr:
quit_requestedand flips :attr:is_runningto False. The driving app polls this state and exits its loop at the end of the current frame. Safe to call from node callbacks or signal handlers.
- set_root(root: simvx.core.node.Node)[source]¶
Set the root node of the scene tree.
Before the root enters the tree, any
input_actionsdeclared on the root (class- or instance-leveldict[str, list]) is bulk- registered with this tree’sInputMap. This is the canonical replacement for the wrapper-class +on_readyboilerplate and surviveschange_sceneswaps – every new root’s actions are re-registered automatically.
- change_scene(new_root: simvx.core.node.Node)[source]¶
Swap the active root with
new_root.The old root receives
_exit_tree;new_rootthen runs the full_enter_tree/_ready_recursivepath, identical to the initial root. Autoloads are left in place and their groups and unique-name entries are re-registered on the rebuilt tree. Pending deletes, UI popup state, and the active 2D camera are cleared.Use this for title → gameplay → game-over navigation. See
- Doc:
../patternsfor a full example.
- tick(dt: float)[source]¶
Run process callbacks and coroutines on all nodes for one frame.
Order: autoloads’
_processfirst (Godot-style global singletons), thenself.events.flush_deferred()so deferred events queued during the previous frame (process, physics, or input) reach all subscribers before scene logic runs, then the scene root’s_process. Anythingemit_deferred’d during this frame’s autoload pass is also drained in the same flush, keeping the scene’s view of the world consistent.
- physics_tick(dt: float)[source]¶
Run physics_process callbacks on all nodes, then auto-step physics.
- propagate_input(event: simvx.core.events.TreeInputEvent) None[source]¶
Dispatch an input event to registered
@on_inputhandlers.Looks up handlers via the typed dispatch tables (built when nodes carrying
@on_input-decorated methods enter the tree). The traversal is O(handlers per event), not O(nodes): nodes without any input handlers cost nothing.A handler returning a truthy value marks the event consumed:
on_unhandled_inputonly fires if nothing consumed the event.event.handledis also flipped True so callers can short-circuit.
- push_modal(control)[source]¶
Register a control as an active modal (drawn on top, receives all UI input).
- input_cast(screen_pos: tuple[float, float] | numpy.ndarray, button: simvx.core.input.enums.MouseButton = MouseButton.LEFT)[source]¶
Cast a ray from screen_pos through the camera into the scene. Finds the nearest pickable CollisionShape3D and delivers an InputEvent to its parent node.
- get_group(name: str) list[simvx.core.node.Node][source]¶
Get all nodes in a group.
- property autoloads: dict[str, simvx.core.node.Node][source]¶
Read-only view of registered autoloads.
- add_autoload(name: str, node: simvx.core.node.Node)[source]¶
Register
nodeas a persistent singleton attached to the tree.The node enters the tree and runs
on_ready()immediately. Unlike a regular child, it is not reachable via the scene root: retrieve it viatree.autoloads[name]. Autoloads survivechange_scene(), making them the canonical home for global state (score, settings, audio manager). See :doc:../patterns.The name
"events"is reserved for the engine-provided- Class:
~simvx.core.event_bus.EventBus; usetree.eventsinstead.
- remove_autoload(name: str)[source]¶
Unregister and tear down an autoload. Calls
_exit_treeon the node.
- get_unique(name: str) simvx.core.node.Node | None[source]¶
Get a unique node by name. Returns None if not found.
- ui_input(mouse_pos: tuple[float, float] | numpy.ndarray = None, button: simvx.core.input.enums.MouseButton | None = None, pressed: bool = True, key: str = '', char: str = '')[source]¶
Route UI input events to controls.
buttonis aMouseButtonfor press/release,Nonefor keyboard / char / pure mouse-move events.