Skip to main content

A tiny Python 3D windowing wrapper powered by a C++ OpenGL backend.

Project description

speepy3d

speepy3d is a tiny Python 3D/game library with a C++ OpenGL backend.

The backend is currently Windows-only through Win32/WGL. Version 0.3.0 adds a real shader/VBO renderer while keeping the old fixed-pipeline 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.
  • Game layer helpers: GameApp, AssetManager, Timer, Profiler, controllers and particles.
  • 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.
  • Tiny game loop helper: run(window, update, render).

Install

python -m pip install -e .

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")

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.0.tar.gz (50.8 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.0-cp312-cp312-win_amd64.whl (58.1 kB view details)

Uploaded CPython 3.12Windows x86-64

File details

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

File metadata

  • Download URL: speepy3d-0.8.0.tar.gz
  • Upload date:
  • Size: 50.8 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.0.tar.gz
Algorithm Hash digest
SHA256 64b5613539378e4547bb11245652e3fa4fa3c8af37a05b5598078b7c428f1f5b
MD5 0a538fe72879684b6b756f63e0ac5947
BLAKE2b-256 5455e56475251dbc283f0205b089efc0a401fe7113e27bb921bc52e5f7ad6540

See more details on using hashes here.

File details

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

File metadata

  • Download URL: speepy3d-0.8.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 58.1 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.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 86bccfad6a4846d855de4e86fef7090a4133ceb6ce6417b5bc004e0390bbde54
MD5 7df7d4d78f78a9902a0f9df9379f1df8
BLAKE2b-256 440445e8f311cbd63783b5a7ddc467ea1fdfd1f7400997b0b3fa7681fd6ac0c0

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