API cheat sheet

The common API on one page. Each section links to the guide that explains it. Everything below imports from simvx.core, except App, which is in simvx.graphics.

Node lifecycle (Node system)

class Player(Node3D):
    def on_enter_tree(self): ...     # every time the node joins a tree
    def on_ready(self): ...          # once, after its children are ready
    def on_update(self, dt): ...     # every frame
    def on_fixed_update(self, dt): ...  # fixed timestep, 60 Hz by default
    def on_input(self, event): ...   # every input event that reaches the node
    def on_draw(self, renderer): ... # 2D drawing, in the node's own space
    def on_exit_tree(self): ...

Decorators add more handlers under any method name (Patterns):

@on_update
def animate(self, dt): ...

@on_input("jump")             # an action's press
def jump(self, event):
    return True               # claim the event: no later handler runs

@on_input(Key.CTRL + Key.S)   # a key with a modifier held
def save(self, event): ...

Properties and signals

class Rank(StrEnum):               # from enum import StrEnum
    GRUNT = "grunt"
    ELITE = "elite"
    BOSS = "boss"

class Enemy(Node):
    speed = Property(5.0, range=(0, 20), hint="px/sec")
    rank = Property(Rank.GRUNT)  # a closed set; also accepts the plain string
    hp = Property(100, on_change="_on_hp", persist=True)

    died = Signal()               # no arguments
    damaged = Signal(int)         # one int
    hit_by = Signal(int, str)     # an int and a str

    def _on_hp(self):             # the hook takes no arguments: read the property
        if self.hp <= 0:
            self.died.emit()      # fire the signal

enemy = Enemy(speed=8.0, rank="elite")       # Properties are constructor keywords
conn = enemy.died.connect(on_died)           # returns a Connection
enemy.damaged.connect(show_damage, once=True)
conn.disconnect()

Property takes more options than these; see Node system.

The tree

child = self.add_child(Node2D(name="HUD"))   # returns the child
self.parent                      # the parent node
self.tree                        # the SceneTree
self.app                         # the App, from on_enter_tree() on
self["HUD/Score"]                # a descendant by path
self.find(Camera3D)              # the first descendant of a type
self.find_all(Light3D)           # every descendant of a type
self.find("HUD", direct=True)    # a direct child by name
self.add_to_group("enemies")
self.tree.group("enemies")
self.destroy()                   # removed at the end of the frame

Input (Input)

class Game(Node):
    input_context = {
        "jump": [Key.SPACE, Controller.A],                                   # bool
        "throttle": [Axis1D(low=Key.S, high=Key.W), Controller.RIGHT_TRIGGER],  # float
        "move": [Axis2D.WASD, Axis2D.ARROWS, Controller.LEFT_STICK],         # Vec2
    }

Input.value("move")                    # the action's typed value
Input.pressed("jump")
Input.just_pressed("jump")
Input.just_released("jump")
Input.get_action_strength("throttle")  # the magnitude, as a float

Coroutines

from simvx.core import next_frame, parallel, tween, wait, wait_signal, wait_until

def cutscene(self):
    yield from wait(1.0)
    yield from tween(self.camera, "position", target, 2.0)
    yield from wait_signal(self.boss.died)
    yield from parallel(
        tween(a, "position", pa, 1.0),
        tween(b, "position", pb, 1.0),
    )

handle = self.start_coroutine(self.cutscene())

Scenes and singletons (Patterns)

self.tree.change_scene(GameOverScreen(score=self.score))
self.tree.add_singleton("Jukebox", Jukebox())   # survives change_scene
self.tree.singletons["Jukebox"]

Rendering (WorldEnvironment, App)

self.add_child(WorldEnvironment(bloom_enabled=True, bloom_threshold=0.8,
                                tonemap_mode="aces", ssao_enabled=True))

App(title="Game", physics_fps=120).run(root)   # defaults: 60 Hz physics, vsync on

# Headless, for tests: returns the captured frames as RGBA arrays
frames = App(visible=False).run_headless(root, frames=10, capture_frames=[0, 9])

Maths

Vec2 and Vec3 are float32 NumPy arrays, and Quat holds rotations. Angles are radians: use math.radians() when you think in degrees. Matrices are row-major. A tuple converts at every boundary: node.position = (100, 200) stores a Vec2.