Building a Simple Game with the SimVX Editor

This tutorial walks through building a 2D game scene using the SimVX editor. Each step has been validated with automated tests to ensure the editor is functional.

Prerequisites

# Install in dev mode
uv pip install -e packages/core -e packages/graphics -e packages/editor

# Launch the editor
uv run python -m simvx.editor.main
# Or: from simvx.editor.app import launch; launch()

Step-by-Step Tutorial

Step 1: Launch the Editor

Action: Run the editor. A window appears with:

  • Menu bar at top: File, Edit, Scene, View, Tools, Help

  • Toolbar below: Move/Rotate/Scale buttons, Play/Pause/Stop, 3D/2D toggle

  • Scene Tree panel (left): shows the node hierarchy with a “Root” node

  • Viewport (center): 3D preview area

  • Inspector panel (right): property editor (empty until a node is selected)

  • Status bar (bottom): mouse position and selection info

  • Bottom tabs: Console, Code, Files, Animation

Verify:

  • [x] The editor window opens without errors

  • [x] All panels are visible and labeled

  • [x] The Scene Tree shows a single “Root” node

  • [x] The undo stack is empty

  • [x] The editor is not in play mode

Tests: TestStep1_LaunchEditor (10 tests)


Step 2: Add a Game Node

Action: Click the “+” button in the Scene Tree header. The Add Node dialog appears.

In the dialog:

  1. You see a list of 12 node types (Node, Node2D, Node3D, Camera3D, etc.)

  2. There’s a filter text field at the top

  3. Click “Node2D” in the list

Result: A new Node2D appears under Root in the Scene Tree.

Verify:

  • [x] Clicking “+” opens the Add Node dialog

  • [x] The dialog shows all 12 available types

  • [x] Clicking a type creates the node and closes the dialog

  • [x] The new node appears in the Scene Tree

  • [x] The new node gets selected after creation

  • [x] The operation is recorded in the undo stack

Tests: TestStep2_AddNodes (8 tests), TestAddNodeDialogInteraction (3 tests)


Step 3: Rename the Node

Action: With the Node2D selected, press F2 to open the rename overlay.

  1. The rename field appears with the current name highlighted

  2. Type “Game”

  3. Press Enter to confirm

Result: The node is renamed from “Node2D” to “Game” in the Scene Tree.

Verify:

  • [x] F2 key opens the rename overlay

  • [x] Typing a name and pressing Enter renames the node

  • [x] The tree view updates to show the new name

  • [x] Renaming is undoable with Ctrl+Z

Tests: TestStep3_RenameNodes (7 tests), TestUndoRedoViaUI (3 tests)


Step 4: Add Child Nodes

Action: With “Game” selected, click “+” again.

  1. Select “Node2D” from the dialog

  2. The new node appears under Game (as a child)

  3. Press F2, type “Player”, press Enter

  4. Select “Game” again in the tree

  5. Click “+” and add another “Node2D”

  6. Rename it to “Enemy”

  7. Select “Game”, add a “Camera3D”

Result: Your scene tree looks like:

Root
  └─ Game (Node2D)
       ├─ Player (Node2D)
       ├─ Enemy (Node2D)
       └─ Camera3D

Verify:

  • [x] Nodes are added as children of the selected node

  • [x] If nothing is selected, nodes go under Root

  • [x] Multiple nodes can be added sequentially

  • [x] Each add is a separate undo operation

Tests: TestStep2_AddNodes::test_add_node_under_selected_parent


Step 5: Select a Node and View Properties

Action: Click on “Player” in the Scene Tree.

Result: The Inspector panel updates to show:

  • Type label: “Node2D”

  • Name field: “Player”

  • Node section: Visible checkbox

  • Transform section: Position (X, Y), Rotation, Scale (X, Y)

Verify:

  • [x] Clicking a tree item updates the inspector

  • [x] Inspector shows the correct type name

  • [x] Inspector shows the correct node name

  • [x] Transform section appears for spatial nodes

  • [x] Node section appears with visibility toggle

  • [x] Switching selection updates the inspector

Tests: TestStep4_SelectionInspector (9 tests)


Step 6: Edit Transform Properties

Action: In the Inspector, change the Player’s position:

  1. Click on the Position X spinbox

  2. Type 100 or use the increment buttons

  3. Change Position Y to 300

Result: The Player node’s position updates to (100, 300).

Verify:

  • [x] Position widgets exist for 2D nodes

  • [x] Initial values match the node’s position (0, 0)

  • [x] Changing a value updates the node immediately

  • [x] Position changes are undoable with Ctrl+Z

  • [x] 3D transforms work similarly with X, Y, Z components

  • [x] Rotation and Scale can also be edited

Tests: TestStep5_EditTransforms (7 tests), TestSpinBoxInteraction (2 tests)


Step 7: Undo and Redo

Action: Try the undo/redo system:

  1. Press Ctrl+Z to undo the last position change

  2. Press Ctrl+Shift+Z to redo it

  3. Undo multiple times to step back through all changes

  4. Redo to restore them

Verify:

  • [x] Ctrl+Z undoes the last operation

  • [x] Ctrl+Shift+Z redoes an undone operation

  • [x] Multiple undos can be chained

  • [x] Undo works for: add node, rename, property change, delete, duplicate

Tests: TestStep6_UndoRedo (6 tests), TestKeyboardShortcuts (5 tests)


Step 8: Delete and Duplicate Nodes

Action:

  1. Select “Enemy” in the tree

  2. Press Delete key — Enemy is removed

  3. Press Ctrl+Z — Enemy is restored

  4. Select “Player”

  5. Use the context menu (right-click) → Duplicate — creates “Player_copy”

  6. Select “Player_copy” and press Delete to remove it

Verify:

  • [x] Delete key removes the selected node

  • [x] Cannot delete the root node

  • [x] Delete clears the selection

  • [x] Deleting is undoable

  • [x] Duplicate creates a copy with “_copy” suffix

  • [x] Duplicate is undoable

  • [x] Right-click context menu appears with all options

Tests: TestStep7_DeleteDuplicate (7 tests), TestContextMenu (2 tests)


Step 9: Copy and Paste

Action:

  1. Select “Player”

  2. Right-click → Copy (or Ctrl+C)

  3. Select “Game”

  4. Right-click → Paste (or Ctrl+V) — creates “Player_paste” under Game

Verify:

  • [x] Copy stores the node in the clipboard

  • [x] Paste creates a new node from the clipboard

  • [x] Pasted node gets “_paste” suffix

  • [x] Paste is undoable

Tests: TestStep8_CopyPaste (3 tests)


Step 10: Save the Scene

Action:

  1. Press Ctrl+S to save

  2. If no path is set, the Save As dialog appears

  3. Navigate to your project directory

  4. Enter filename: “game.py”

  5. Click Save

Result: Scene is saved to disk as a Python source file — the scene tree, properties, and attached scripts round-trip as a Node subclass.

Verify:

  • [x] Save creates a Python (.py) file on disk

  • [x] Save clears the “modified” flag

  • [x] Save remembers the path for future saves

  • [x] The path is stored in current_scene_path

Tests: TestStep9_SaveLoad (6 tests)


Step 11: Load a Scene

Action:

  1. Press Ctrl+N to create a new empty scene

  2. Press Ctrl+O to open the file dialog

  3. Navigate to and select “game.py”

  4. Click Open

Result: The saved scene is loaded with all nodes restored.

Verify:

  • [x] New Scene clears everything (nodes, selection, undo)

  • [x] Open Scene loads nodes from the Python scene file

  • [x] Loading clears the undo stack

  • [x] Loaded scene has all the original nodes

Tests: TestStep9_SaveLoad (6 tests)


Step 12: Custom Properties

If you create a custom node class with Property descriptors:

from simvx.core import Node2D, Property

class Player(Node2D):
    speed = Property(5.0, range=(0, 20))
    health = Property(100, range=(0, 200))
    name_tag = Property("hero")
    is_alive = Property(True)
    mode = Property("walk", enum=["walk", "run", "idle"])

The Inspector will automatically create the right widget for each property:

Type

Widget

float with range

Slider

int / float without range

SpinBox

bool

CheckBox

str

TextEdit

str with enum

DropDown

Color tuple

ColorPicker

Vec2 / Vec3

Multi-SpinBox row

Tests: TestStep12_CustomProperties (5 tests)


Step 13: Play Mode

Action:

  1. Press F5 (or click Play) to enter play mode

  2. Press F7 (or click Pause) to toggle pause

  3. Press F6 (or click Stop) to exit play mode

Result: The scene is serialized before play, and restored when stopped.

Verify:

  • [x] Play enters play mode and emits signal

  • [x] Pause toggles the paused state

  • [x] Stop restores the scene to pre-play state

  • [x] Stop when not playing is a no-op

Tests: TestStep10_PlayMode (5 tests)


Step 14: Inspector Section Collapse

Action: Click on a section header (e.g., “Transform”) to collapse it.

Result: The section rows hide. Click again to expand.

Tests: TestStep14_SectionCollapseExpand (3 tests)


Step 15: Gizmo and Viewport Mode

Action:

  • Click Move/Rotate/Scale buttons in the toolbar to change gizmo mode

  • Press Q to cycle through modes

  • Click 3D/2D buttons to switch viewport mode

Tests: TestStep15_GizmoMode (3 tests), TestStep16_ViewportMode (3 tests)


Authoring custom classes

Beyond the basic Add Node / Duplicate flow above, the editor exposes the Node = Class = File model: instances configure via __init__ kwargs; classes are real Python files; refactorings update the project end-to-end.

Make Custom Class (promote to a user class)

Select a built-in node (Sprite2D, Node3D, etc.) and click Make Custom Class in the Inspector toolbar — or right-click in the scene tree and choose Make Custom Class. The dialog lets you pick:

  • A class name (defaults to the node’s name).

  • New file (default): creates <project>/<class_files_dir>/<snake_case>.py with class <Name>(<Base>): pass. class_files_dir is set in simvx.toml [editor] (default src/).

  • Inline in parent’s class: inserts a top-level class <Name>(<Base>): pass into the parent scene file just above the scene class.

After creation, the editor opens the new class file (or jumps to the inline block) in the code panel. The scene’s runtime node is reclassed to the new type, and the next save reflects the type swap in the source.

Duplicate Node (3 variants)

Right-click → Duplicate… opens a dialog with three options:

  • New Instance (default): adds another <SameClass>(...) to the parent with kwargs copied from the original. Same class definition, two instances.

  • Subclass: prompts for a class name (default <Original>2), creates <class_files_dir>/<snake_case>.py containing class <Name>(<Original>): pass, and adds an instance of <Name> to the parent.

  • Detached Copy: prompts for a class name. Creates a new file containing class <Name>(<OriginalBase>): <body> — the body is copied verbatim from the original class file, but with the original base class (not subclassing the original).

The keyboard shortcut Ctrl+D keeps the quick “New Instance” behaviour without the dialog.

Rename Class (project-wide)

Select a user-class instance and right-click → Rename Class… — the dialog prompts for the new name and offers an “Also rename file to match” checkbox. On submit, every file in the project is rewritten in one atomic step:

  • The class definition.

  • Every from <module> import <ClassName> (alias preserved when present).

  • Every base-class reference (class Frost(Player):class Frost(Hero):).

  • Every <ClassName>(...) instantiation.

  • Every isinstance(x, <ClassName>) and annotation site.

When Also rename file is ticked, the defining file moves to the new snake_case name and importers’ relative-import module paths follow via trailing-segment match.

Extract / Inline files

Right-click on a .py file with multiple top-level classes in the file browser → Extract classes to folder. The file becomes a folder of the same name, one class per <snake_case>.py, with an __init__.py re-exporting each so absolute imports of the original module path keep working.

Inverse: right-click on a folder containing __init__.pyInline folder to file concatenates every class back into a single .py. Refuses on unsupported constructs (module-level side effects, conditional imports, free functions); offers a Try Anyway path that proceeds best-effort and reports each suspect construct in a follow-up review panel — the generated source itself is left clean (no # noqa markers, no inserted comments).

Tests: test_make_custom_class_dialog.py, test_duplicate_node_dialog.py, test_project_classes.py::TestRenameClass, test_refactor.py, test_rename_refactor_integration.py.