Node System¶
Nodes are the building blocks of every SimVX game. They form a tree hierarchy managed by the SceneTree.
Lifecycle¶
Every node goes through these stages:
Construction –
__init__()sets up propertiesEnter tree – Node is added to the scene tree via
add_child()Ready –
ready()is called once, after all children are ready (bottom-up)Process –
process(dt)is called every framePhysics process –
physics_process(dt)is called at fixed intervals (default 60 Hz)Exit tree – Node is removed from the tree via
destroy()
class Enemy(CharacterBody3D):
health = Property(100, range=(0, 200))
def ready(self):
self.health = 100
def process(self, dt):
self.rotate((0, 1, 0), 45 * dt) # degrees
def physics_process(self, dt):
self.move_and_slide(dt)
Properties¶
Property declares editor-visible, serializable values with optional validation:
from simvx.core import Node3D, Property
class Tank(Node3D):
speed = Property(5.0, range=(0, 20), hint="Movement speed")
armor = Property("heavy", enum=["light", "medium", "heavy"])
active = Property(True)
Query all properties on a class with get_properties():
Tank.get_properties() # {"speed": Property(...), "armor": Property(...), "active": Property(...)}
Hierarchy¶
Nodes have a single parent and any number of children:
root = Node(name="Root")
player = Node3D(name="Player")
camera = Camera3D(name="Camera", position=(0, 5, 10))
root.add_child(player)
player.add_child(camera)
# Access children by name, index, or attribute
root.children["Player"] # by name
root.children[0] # by index
root.children.Player # by attribute
# Path-based access via __getitem__
root["Player/Camera"] # slash-separated path
camera.get_node("../Player") # relative path (sibling)
camera.get_node("/Root") # absolute path
# Node properties
camera.parent # direct parent
camera.path # "/Root/Player/Camera"
camera.tree # the SceneTree
Signals¶
Signals provide decoupled event communication. connect() returns a Connection handle for later disconnection:
from simvx.core import Signal
class HealthComponent(Node):
def __init__(self):
super().__init__()
self.died = Signal()
self.hp = 100
def take_damage(self, amount):
self.hp -= amount
if self.hp <= 0:
self.died.emit()
# Connect and get a Connection handle
health = HealthComponent()
conn = health.died.connect(lambda: print("Game over"))
# One-shot connection: auto-disconnects after first call
health.died.connect(on_first_death, once=True)
# Disconnect manually
conn.disconnect()
Groups¶
Tag nodes for batch operations:
enemy.add_to_group("enemies")
enemy.is_in_group("enemies") # True
# Get all nodes in a group
all_enemies = scene_tree.get_group("enemies")
Coroutines¶
Generator-based coroutines for sequential async logic:
from simvx.core import wait, parallel, tween
def cutscene(self):
yield from wait(1.0) # pause 1 second
yield from tween(cam, "position", target, 2.0) # animate
yield from wait(0.5)
self.start_dialog()
# Run multiple animations simultaneously
self.start_coroutine(parallel(
tween(a, "position", pos_a, 1.0),
tween(b, "position", pos_b, 1.0),
))
Destroying Nodes¶
Remove nodes safely at the end of the frame:
enemy.destroy()
Finding Nodes¶
# Find first child of type
camera = root.find(Camera3D)
# Find all descendants of type
all_lights = root.find_all(Light3D)
all_meshes = root.find_all(MeshInstance3D, recursive=True)
API Reference¶
See simvx.core.engine for the complete Node API.