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, optionalset_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. MeshBatchfor 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,.bmpand.ppm. - Math:
Vec2,Vec3,Mat4. - Scene objects:
Camera3D,Transform,Model,Scene,DirectionalLight. - Collision helpers:
AABB,PhysicsBody,PhysicsWorld,move_and_collide,SphereCollider,Ray,raycast_aabb. - ECS collision callbacks:
on_trigger_enter,on_trigger_exit,on_collision_enter,on_collision_exit. - Collision layers:
scene.ignore_collision(),scene.only_collision(). - Scene queries:
scene.query(),scene.find_all(),scene.find_component(). - Asset folder loading:
assets.load_folder("assets"). - 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 --template platformer
cd mygame
speepy3d run
Useful CLI commands:
speepy3d new mygame --template empty
speepy3d new mygame --template menu
speepy3d new mygame --template fps
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_enter("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")
Queries:
enemies = app.scene.query(tag="enemy", component=Rigidbody)
body = app.scene.find_component(Rigidbody)
app.scene.destroy("enemy_1")
app.scene.clear()
Collision layers:
player.add(Rigidbody(size=(1, 1, 1), layer="player"))
coin.add(Collider(size=(1, 1, 1), trigger=True, layer="pickup"))
app.scene.ignore_collision("enemy", "enemy")
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.
Folder workflow:
app.assets.load_folder("assets")
crate = app.assets.get_material("crate")
player_mesh = app.assets.get_mesh("player")
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file speepy3d-0.8.4.tar.gz.
File metadata
- Download URL: speepy3d-0.8.4.tar.gz
- Upload date:
- Size: 71.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65c2b26a1c185af189f937be3e3e9f12fa4c7c2fa6fc29aed98c4e125d1f99d2
|
|
| MD5 |
b8164a20d39b43c40529d3bce5232a77
|
|
| BLAKE2b-256 |
4c9039e27dea204dcd11e01e235725815523450713d073b454a31835dddd02ce
|
File details
Details for the file speepy3d-0.8.4-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: speepy3d-0.8.4-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 75.2 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c49e9626b0db64572368584609c0014b45c64b2d532efd57bf3d4fc06bb0eaa
|
|
| MD5 |
cd1904261da4da262660c3d903a5481a
|
|
| BLAKE2b-256 |
1b6943c60ce19287c3898b92a1d077a269a9cb279c52b31678ab25df3f07e26d
|