Your First 3D Game

Build an asteroid dodger from scratch in 7 steps. Each section adds to the same project.

1. A Cube in Space

from simvx.core import Camera3D, Material, MeshInstance3D, Node, ResourceCache, Vec3
from simvx.graphics import App

class Game(Node):
    def ready(self):
        cam = self.add_child(Camera3D(name="Camera", position=Vec3(0, 12, 15)))
        cam.look_at(Vec3(0, 0, -5))
        cam.fov = 60.0

        self.player = self.add_child(MeshInstance3D(name="Player"))
        self.player.mesh = ResourceCache.get().resolve_mesh("mesh://cube?size=0.5")
        self.player.material = Material(colour=(0, 1, 0, 1))

App(title="Asteroid Dodger", width=1024, height=768).run(Game())

Camera3D defines the viewpoint – look_at() orients it toward a target. MeshInstance3D renders geometry; use ResourceCache to get built-in meshes (mesh://cube, mesh://sphere). Material(colour=) sets the surface colour as RGBA.

2. Move the Player

from simvx.core import Camera3D, Input, InputMap, Key, Material, MeshInstance3D, Node, ResourceCache, Vec3
from simvx.graphics import App

class Game(Node):
    def ready(self):
        cam = self.add_child(Camera3D(name="Camera", position=Vec3(0, 12, 15)))
        cam.look_at(Vec3(0, 0, -5))
        cam.fov = 60.0

        self.player = self.add_child(MeshInstance3D(name="Player", position=Vec3(0, 1, 0)))
        self.player.mesh = ResourceCache.get().resolve_mesh("mesh://cube?size=0.5")
        self.player.material = Material(colour=(0, 1, 0, 1))

    def process(self, dt: float):
        move = Input.get_vector("left", "right", "forward", "back")
        self.player.position += Vec3(move.x, 0, move.y) * 15.0 * dt
        self.player.position.x = max(-20, min(20, self.player.position.x))
        self.player.position.z = max(-20, min(20, self.player.position.z))

InputMap.add_action("left", [Key.A, Key.LEFT])
InputMap.add_action("right", [Key.D, Key.RIGHT])
InputMap.add_action("forward", [Key.W, Key.UP])
InputMap.add_action("back", [Key.S, Key.DOWN])
App(title="Asteroid Dodger", width=1024, height=768).run(Game())

Input.get_vector() returns a normalised Vec2 from four action names (left, right, up, down). We map it onto the XZ plane for top-down movement and clamp to a play area.

3. Spawn Asteroids

Add a timer that spawns falling cubes:

import random
from simvx.core import Timer, Vec3

class Asteroid(MeshInstance3D):
    fall_speed = 8.0

    def ready(self):
        self.mesh = ResourceCache.get().resolve_mesh("mesh://cube?size=1.0")
        self.material = Material(colour=(1, 0.2, 0.2, 1))

    def process(self, dt: float):
        self.position.y -= self.fall_speed * dt
        self.rotate_x(180.0 * dt)
        if self.position.y < -15:
            self.destroy()

In the Game.ready() method, add a spawn timer:

timer = Timer(duration=2.0, one_shot=False, autostart=True)
timer.timeout.connect(self._spawn_asteroid)
self.add_child(timer)

def _spawn_asteroid(self):
    x = random.uniform(-20, 20)
    z = random.uniform(-20, 20)
    self.add_child(Asteroid(name="Asteroid", position=Vec3(x, 20, z)))

Timer fires its timeout signal at regular intervals. destroy() removes a node and all its children from the tree.

4. Collision

Upgrade the player and asteroids to physics bodies with collision shapes:

from simvx.core import CharacterBody3D, CollisionShape3D

class Player(CharacterBody3D):
    speed = 15.0

    def ready(self):
        self.collision = self.add_child(CollisionShape3D(name="Collision", radius=0.5))
        mesh = self.add_child(MeshInstance3D(name="Mesh"))
        mesh.mesh = ResourceCache.get().resolve_mesh("mesh://cube?size=0.5")
        mesh.material = Material(colour=(0, 1, 0, 1))

    def process(self, dt: float):
        move = Input.get_vector("left", "right", "forward", "back")
        self.velocity = Vec3(move.x, 0, move.y) * self.speed
        self.move_and_slide(dt)

Check collisions in the game’s process():

for asteroid in self.find_all(Asteroid):
    if self.player.collision.overlaps(asteroid.collision):
        self.game_over = True

CharacterBody3D provides move_and_slide(dt) for physics-based movement. CollisionShape3D(radius=) creates a sphere collider. overlaps() checks intersection between two shapes.

5. Score and HUD

Use Text2D for screen-space text:

from simvx.core import Text2D

class Game(Node):
    def ready(self):
        # ... camera, player, timer ...
        self.score = 0
        self.score_text = self.add_child(
            Text2D(name="Score", text="Score: 0", x=20, y=20, font_scale=2.0, font_colour=(255, 255, 255, 255))
        )
        self.game_over_text = self.add_child(
            Text2D(name="GameOver", text="", x=400, y=350, font_scale=3.0, font_colour=(255, 0, 0, 255))
        )

    def process(self, dt: float):
        if self.game_over:
            self.game_over_text.text = f"GAME OVER! Score: {self.score}"
            return
        self.score = int(self.elapsed_time)
        self.score_text.text = f"Score: {self.score}"

Text2D renders text as a 2D overlay. Set x, y for screen position, font_scale for size, and font_colour for colour.

6. Polish

Add post-processing with WorldEnvironment:

from simvx.core import WorldEnvironment

class Game(Node):
    def ready(self):
        # ... game setup ...
        env = self.add_child(WorldEnvironment())
        env.bloom_enabled = True
        env.bloom_threshold = 0.8
        env.ssao_enabled = True
        env.fog_enabled = True
        env.fog_density = 0.02
        env.fog_colour = (0.05, 0.05, 0.15)

WorldEnvironment is the canonical way to configure rendering effects. The renderer reads its properties each frame – no direct renderer access needed.

7. Next Steps

More 3D examples to explore:

  • game_asteroids3d.py – Top-down arcade game with 3D objects

  • game_spaceinvaders3d.py – Classic arcade game with 3D meshes

  • 3d_lighting.py – Directional, point, and spot lights

  • 3d_shadows.py – Cascade shadow maps with debug visualisation

  • 3d_ssao.py – Screen-space ambient occlusion

  • 3d_particles.py – Sub-emitters, collision, trails

  • 3d_ibl.py – Image-based lighting with metallic spheres

  • 3d_model_viewer.py – Load glTF models with orbit camera

See Examples Gallery for the full list, or Building a Simple Game with the SimVX Editor to build games visually in the editor.