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:

  1. Construction__init__() sets up properties

  2. Enter tree – Node is added to the scene tree via add_child()

  3. Readyready() is called once, after all children are ready (bottom-up)

  4. Processprocess(dt) is called every frame

  5. Physics processphysics_process(dt) is called at fixed intervals (default 60 Hz)

  6. 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.