Skip to main content

A Python 3D game framework powered by a C++ OpenGL backend.

Project description

speepy3d

speepy3d is a Python 3D game framework with a C++ OpenGL backend.

The backend is currently Windows-only through Win32/WGL. It uses a shader/VBO renderer while keeping simple helpers for debug grid/axes.

Features

  • Native OpenGL window creation.
  • Keyboard and mouse state: is_key_down, is_mouse_down, mouse_delta.
  • Cursor helpers: set_cursor_locked, set_cursor_visible.
  • Timing: delta_time, fps, fpsbar=True, optional set_vsync.
  • Resize and close callbacks.
  • Input bindings: Input.action(), Input.axis(), Input.vec2(), mouse sensitivity and click-to-lock mouse.
  • Input contexts for game/menu/pause bindings.
  • Game layer helpers: GameApp, AssetManager, Timer, Profiler, controllers and particles.
  • Entity/component layer with lifecycle: Component.start(), update(), late_update().
  • Transform hierarchy: parent/children entities.
  • Events/signals: EventBus, app.events, app.on(), app.emit().
  • Prefabs and scene JSON save/load.
  • Collision callbacks and trigger callbacks for entities.
  • Tween and coroutine managers for small animations and timed logic.
  • Render layers for ordered model drawing.
  • Modern OpenGL basics: Shader, Material, Mesh, Texture, Renderer.
  • MeshBatch for building many cubes into one fast mesh.
  • Built-in Shader.basic(), Mesh.cube(), Mesh.plane(), Texture.checkerboard().
  • Mesh normals and basic ambient + directional lighting.
  • Simple OBJ loader: Mesh.from_obj("model.obj").
  • Simple glTF loader: Mesh.from_gltf("model.gltf").
  • Texture loading for .png, .jpg, .jpeg, .bmp and .ppm.
  • Math: Vec2, Vec3, Mat4.
  • Scene objects: Camera3D, Transform, Model, Scene, DirectionalLight.
  • Collision helpers: AABB, PhysicsBody, PhysicsWorld, move_and_collide, SphereCollider, Ray, raycast_aabb.
  • Debug drawing: DebugDraw.line(), DebugDraw.box(), DebugDraw.ray().
  • WAV sound helpers on Windows: Sound, Music.
  • Immediate UI: Label, Button, Panel, Menu, ContextMenu, UIManager.
  • Game loop helper: run(window, update, render).

Install

python -m pip install speepy3d

For local development from this source folder:

python -m pip install -e .

Quick Start

speepy3d new mygame
cd mygame
python main.py

Useful CLI commands:

speepy3d info
speepy3d examples
speepy3d --version

Modern Renderer Example

from speepy3d import Camera3D, DirectionalLight, Input, Model, Renderer, Scene, Texture, Vec3, Window

win = Window(800, 600, "speepy3d", fpsbar=True)
win.enable_depth_test(True)

camera = Camera3D(position=Vec3(0.0, 2.0, 6.0), pitch=-15.0)
renderer = Renderer(win)
controls = Input(win, mouse_sensitivity=0.08, mouse_locked_by_click=True)
controls.action("jump", "SPACE")
controls.axis("move_x", negative="A", positive="D")
controls.axis("move_z", negative="S", positive="W")

scene = Scene(
    camera=camera,
    ambient_light=Vec3(0.36, 0.40, 0.48),
    directional_light=DirectionalLight(direction=Vec3(-0.35, -1.0, -0.45), intensity=1.1),
)
cube = scene.add(Model.cube(1.5, texture=Texture.checkerboard()))
scene.add(Model.grid(10.0, 1.0))
scene.add(Model.axes(2.0))

while not win.should_close():
    win.poll_events()
    controls.update()

    dt = win.delta_time or 1.0 / 60.0
    raw_move = controls.vec2("move")
    move = camera.right * raw_move.x + camera.forward * raw_move.y
    if move.length > 0.0:
        camera.position += move.normalized() * camera.move_speed * dt

    controls.apply_look(camera)

    cube.transform.rotation.y += 60.0 * dt
    renderer.render(scene)
    win.swap_buffers()

Game Examples

python examples\snake3d.py
python examples\minicraft3d.py
python examples\platformer3d.py

Input

from speepy3d import Input

controls = Input(win, mouse_sensitivity=0.06, mouse_locked_by_click=True)
controls.action("jump", "SPACE")
controls.axis("move_x", negative="A", positive="D")
controls.axis("move_z", negative="S", positive="W")

while not win.should_close():
    win.poll_events()
    controls.update()

    if controls.action("jump"):
        jump()

    move = controls.vec2("move")

Game Layer

from speepy3d import GameApp, MeshBatch, PhysicsBody, PlatformerController, Vec3

app = GameApp(1280, 720, "short game")
app.set_camera((0, 3, 10), pitch=-15)

app.input.action("jump", "SPACE")
app.input.axis("move_x", negative="A", positive="D")

material = app.assets.material("grass", color=Vec3(0.25, 0.85, 0.35))
cube = app.assets.cube()
player = app.scene.spawn(cube, position=(0, 2, 0), material=material, name="player", tag="actor")

world = MeshBatch()
world.cube((0, 0, 0), size=(12, 1, 4), color=(0.3, 0.8, 0.35))
app.scene.add(world.build_model(material))

body = PhysicsBody.from_values(player.position, (1, 1, 1))
app.physics.static_box((0, 0, 0), (12, 1, 4))
controller = PlatformerController(body, app.input, app.physics)

@app.update
def update(dt):
    controller.update(dt)
    player.position = body.position
    app.debug.box(body.box)

@app.render
def render():
    app.draw_scene()
    app.ui.begin()
    app.ui.panel(20, 20, 220, 90)
    app.ui.label(f"FPS {app.fps:.0f}")
    app.profiler.draw(app.ui, 260, 20)
    app.ui.end()

app.run()

Useful systems:

app.timer.after(2.0, spawn_enemy)
app.timer.every(1.0, add_score)

app.particles.emit((0, 2, 0), count=24)

player = app.scene.find("player")
enemies = app.scene.find_by_tag("enemy")

Entity Components

from speepy3d import Component, GameApp, MeshRenderer, Prefab, Rigidbody, Vec3

app = GameApp(1280, 720, "components")
app.input.context("game").action("jump", "SPACE").axis("move_x", negative="A", positive="D")
app.input.use_context("game")

class Player(Component):
    def start(self):
        self.body = self.entity.get(Rigidbody)

    def update(self, dt):
        self.body.velocity.x = self.input.axis("move_x") * 7.0
        if self.input.action("jump") and self.body.body.on_ground:
            self.body.velocity.y = 10.0

player = app.scene.entity("player", tag="player", position=(0, 2, 0))
player.add(Player)
player.add(MeshRenderer(app.assets.cube(), app.assets.material("player", color=Vec3(0.2, 0.7, 1.0))))
player.add(Rigidbody(size=(1, 1, 1)))

prefab = Prefab.from_entity(player)
clone = prefab.instantiate(app.scene, name="clone", position=(3, 2, 0))

Collision and triggers:

@app.on_trigger("player", "coin")
def collect(player, coin):
    coin.destroy()
    app.emit("score:add", 1)

@player.on_collision("enemy")
def hurt(enemy):
    app.tween(player, "position.y", to=4.0, time=0.25, ease="smooth")

Scene save/load:

app.scene.save("level.json")
app.scene.load("level.json")

Coroutines:

def spawn_loop():
    while True:
        spawn_enemy()
        yield app.wait(2.0)

app.start_coroutine(spawn_loop)

Loading Assets

from speepy3d import Material, Mesh, Model, Shader, Texture, Vec3

mesh = Mesh.from_gltf("assets/crate.gltf")
material = Material(
    shader=Shader.basic(),
    texture=Texture.from_file("assets/crate.png"),
    color=Vec3(1.0, 1.0, 1.0),
    roughness=0.5,
)
model = Model(mesh, material=material)

Mesh.from_gltf() supports .gltf JSON files with triangle meshes and external or embedded buffers. .glb, skinning and animation are not implemented yet.

Collision

from speepy3d import AABB, Vec3, intersects

player = AABB.from_center_size(Vec3(0, 1, 0), Vec3(1, 2, 1))
wall = AABB.from_center_size(Vec3(2, 1, 0), Vec3(1, 2, 4))

if intersects(player, wall):
    print("hit")

UI

from speepy3d import Button, Label, Menu, MenuItem, UIManager

ui = UIManager()
ui.add(Label(20, 20, "SCORE 0"))
ui.add(Button(20, 55, 140, 36, text="START", on_click=start_game))
ui.add(Menu(20, 100, 120, 32, title="GAME", items=[
    MenuItem("RESET", reset_game),
    MenuItem("QUIT", win.close),
]))

while not win.should_close():
    win.poll_events()
    ui.update(win)
    render_3d()
    ui.draw(win)
    win.swap_buffers()

Immediate-mode UI:

ui = UIManager(win)

while not win.should_close():
    win.poll_events()
    render_3d()

    ui.begin()
    ui.panel(20, 20, 250, 180)
    ui.label("Score: 10")
    if ui.button("Restart"):
        restart()
    ui.end()

    win.swap_buffers()

Sound

from speepy3d import Sound

Sound("jump.wav").play()

Sound currently uses Python's Windows winsound, so WAV is the practical target.

Build

python setup.py sdist bdist_wheel

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

speepy3d-0.8.3.tar.gz (63.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

speepy3d-0.8.3-cp312-cp312-win_amd64.whl (72.3 kB view details)

Uploaded CPython 3.12Windows x86-64

File details

Details for the file speepy3d-0.8.3.tar.gz.

File metadata

  • Download URL: speepy3d-0.8.3.tar.gz
  • Upload date:
  • Size: 63.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for speepy3d-0.8.3.tar.gz
Algorithm Hash digest
SHA256 6ee46b54dc9cf140a921cfd7b60c408ecfdd58c1882c2f7f3ac47361661f747b
MD5 e798e16c6b508a7b3fbc2244bf7512d0
BLAKE2b-256 dc5c3c1c42e98c95b82e029bd7cdbf224dbe80aa2cca626659e996b4f420a4a9

See more details on using hashes here.

File details

Details for the file speepy3d-0.8.3-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: speepy3d-0.8.3-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 72.3 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for speepy3d-0.8.3-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 40cf8267b503722725a82da5d1074ca37f0808df8ae42a75f730e6070462841b
MD5 d6322024c53ce62f7a62b6d1c1c995fd
BLAKE2b-256 3b0f902c1cc7d2f402b119a478cf7b9176a03bbbbe6c9fb00363dc02dab1185a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page