Quickstart

Two short programs: a square you move in 2D, and a cube you steer in 3D. Install SimVX first (Installation). Two imports cover a game: simvx.core for nodes, input, audio and UI, and simvx.graphics for the window.

2D: a square you move

from simvx.core import Axis2D, Controller, Input, Node2D
from simvx.graphics import App


class Player(Node2D):
    input_context = {"move": [Axis2D.WASD, Axis2D.ARROWS, Controller.LEFT_STICK]}

    def on_ready(self):
        self.position = self.viewport_size / 2

    def on_update(self, dt):
        self.position += Input.value("move") * 200 * dt

    def on_draw(self, renderer):
        renderer.rect((-30, -30), (60, 60), fill=(0.2, 0.4, 1.0))


if __name__ == "__main__":
    App(title="Quickstart").run(Player())

Save it as quickstart.py and run python quickstart.py. WASD, the arrow keys or a controller’s left stick move the square. From a source checkout, prefix commands with uv run.

3D: a cube you steer

from simvx.core import (Axis2D, Camera3D, Controller, DirectionalLight3D, Input, Material, Mesh,
                        MeshInstance3D, Node3D, TouchController)
from simvx.graphics import App


class Game(Node3D):
    input_context = {"move": [Axis2D.WASD, Axis2D.ARROWS, Controller.LEFT_STICK]}

    def on_ready(self):
        self.add_child(Camera3D(position=(0, 4, 8), look_at=(0, 0, 0)))
        self.add_child(DirectionalLight3D(direction=(-0.4, -1.0, -0.6)))
        self.cube = self.add_child(
            MeshInstance3D(mesh=Mesh.box(), material=Material(colour=(1.0, 0.3, 0.2, 1.0)))
        )
        self.add_child(TouchController())  # an on-screen stick for touch screens

    def on_update(self, dt):
        move = Input.value("move")
        self.cube.position += (move.x * 4 * dt, 0, move.y * 4 * dt)


if __name__ == "__main__":
    App(title="3D Quickstart").run(Game())

Save it as quickstart3d.py and run python quickstart3d.py: a red cube sits under a sun, and WASD, the arrow keys, a left stick or the on-screen stick slide it across the view.

What is happening

  1. A scene is a node. on_ready() runs once when the node joins the tree, on_update(dt) every frame, and on_draw(renderer) draws in 2D. App(...).run(root) opens the window and runs the loop.

  2. Input is a typed action. input_context declares move once. Its bindings make it a Vec2, so Input.value("move") returns the direction from whichever device is driving it.

  3. Children are nodes too. The camera, light and cube are added with add_child, with their settings passed as constructor keywords.

Next steps