Skip to main content

Pyroquad Game Engine


This is a Python game engine based on macroquad.

  • Install via pip install pyroquad (in the browser: micropip.install("pyroquad"))
  • Requires Python >= 3.9
  • Supported Platforms: windows, linux, mac, and WASM via Piodide.

Featuring 2D:

view the demo here

2D Screenshot

and 3D:

3D Screenshot

How to build:

  1. Prerequesites:

    • Python >= 3.9 ( python >= 3.14 is recommended)

    • Rust Compiler >= 1.85.0 (Required for the Rust 2024 Edition)

    • System libraries if using Linux (required for X11, OpenGL, and audio bindings):

      Ubuntu / Debian: sudo apt-get install -y libasound2-dev libx11-dev libxi-dev libgl1-mesa-dev libxcursor-dev libxinerama-dev libxrandr-dev

      Fedora: sudo dnf install alsa-lib-devel libX11-devel libXi-devel mesa-libGL-devel libXcursor-devel libXinerama-devel libXrandr-devel

      Arch Linux: sudo pacman -S alsa-lib libx11 libxi mesa libxcursor libxinerama libxrandr

  2. Create and activate a Python virtual environment:

    • macOS / Linux:

      python3 -m venv .venv

      source .venv/bin/activate

    • Windows:

      python -m venv .venv

      .venv\Scripts\activate

  3. Install the maturin build tool using pip install maturin

  4. Compilation:

    run maturin build --release --features abi_39 --generate-stubs for python >= 3.9

    run maturin build --release --features abi_310 --generate-stubs for python >= 3.10

    run maturin build --release --features abi_314 --generate-stubs for python >= 3.14

    (Note: Older ABIs offer better backward compatibility, but newer versions yield performance improvements)

  5. Installation:

    • The generated package can be found at: /target/wheels/
    • Install the package: pip install \path\to\your\file.whl --force-reinstall

How to build for the browser (WebAssembly):

The web build is a single WebAssembly module that runs inside Pyodide (CPython compiled with Emscripten). Nothing is forked or patched - macroquad, miniquad, quad-snd and Pyodide are all stock. docs/WASM.md explains how and why it works.

Since PEP 783 the browser build is published to PyPI like any other wheel, so most people never need to build it at all - see Running in the browser below. The rest of this section is for building it yourself.

  1. Prerequesites (in addition to the ones above):

    • The wasm32-unknown-emscripten Rust target:

      rustup target add wasm32-unknown-emscripten

    • pyodide-build and a recent maturin:

      pip install "pyodide-build>=0.39" "maturin>=1.13.2"

      pyodide-build owns the cross-build environment: it decides the Emscripten version, the linker flags and the wheel's platform tag, so that what you build matches the Pyodide that will load it. maturin 1.13.2 is the first release that emits the PEP 783 tag.

      pyodide-build does not run natively on Windows. Build the wheel on Linux, macOS or WSL. For local iteration on Windows there is a wheel-less path - see Developing on the module itself below.

    • The Emscripten SDK, at the version Pyodide was built with (step 3 prints it). The build script looks for emcc under $EMCC, then $EMSDK, then the project-local emsdk/, then PATH, so no emsdk_env activation step is needed:

      git clone https://github.com/emscripten-core/emsdk.git

      ./emsdk/emsdk install <version>

      ./emsdk/emsdk activate <version>

      (on Windows, use emsdk\emsdk.bat in place of ./emsdk/emsdk)

    • A browser with JSPI (JavaScript Promise Integration): Chrome 137+. This is what lets synchronous Python (while True: ... next_frame()) hand the page back to the browser between frames. Firefox and Safari have not shipped it yet.

  2. Fetch the Pyodide runtime into web/pyodide/ (once):

    python web/get_pyodide.py

    This is the stock pyodide-core release, unpacked and otherwise untouched - the web build deliberately runs on an unmodified Pyodide. It is what the local test page loads, and what check_imports.py resolves symbols against.

    (Pyodide releases are versioned after the CPython they ship: the pinned 314.0.7 is CPython 3.14.2, which is what --features abi_314 targets. python web/get_pyodide.py <version> takes a different one.)

  3. Install the matching cross-build environment:

    pyodide xbuildenv install 314.0.7

    Then read back what it wants - these are the values the build uses, and emscripten_version is the emsdk version to install in step 1:

    pyodide config get emscripten_version

    pyodide config get rustflags

    pyodide config get pyodide_abi_version

  4. Build the wheel (macOS / Linux / WSL):

    CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUSTFLAGS="$(pyodide config get rustflags)" \
    MATURIN_PYEMSCRIPTEN_PLATFORM_VERSION="$(pyodide config get pyodide_abi_version)" \
    PYO3_CROSS_PYTHON_VERSION=3.14 \
    maturin build --release --target wasm32-unknown-emscripten --out dist --features abi_314
    

    This writes dist/pyroquad-<version>-cp314-abi3-pyemscripten_<year>_<n>_wasm32.whl.

    pyodide config get rustflags is what turns the crate into an Emscripten side module - the same thing Pyodide builds every other extension module as. It is passed as CARGO_TARGET_<TARGET>_RUSTFLAGS rather than as a bare RUSTFLAGS on purpose: this crate has a build.rs, which is compiled for the host, and bare RUSTFLAGS would apply the side-module flags to it as well and break it.

  5. Check the build (optional, but it catches the one failure mode that is otherwise only visible as a cryptic dlopen error in the browser):

    python -m zipfile -e dist/*.whl wheel_unpacked/
    python web/check_imports.py wheel_unpacked/pyroquad/_pyroquad*.so
    

    It cross-references every symbol the module imports against what Pyodide actually provides, and exits non-zero if anything is unresolved.

  6. Stage the wheel for the test page, then run it:

    python web/build_web.py

    python web/serve.py

    then open

    • http://127.0.0.1:8000/web/index.html?script=tests/test_rec.py

    build_web.py copies the newest dist/*wasm32.whl to web/pyroquad.whl, and the page installs it with micropip - the same path micropip.install("pyroquad") takes for a real user, so a mis-tagged wheel is rejected here rather than in the wild.

Running in the browser:

Install it inside Pyodide with micropip - the pyemscripten_*_wasm32 wheel comes straight from PyPI, exactly like the desktop ones:

<script type="module">
 import { loadPyodide } from "./pyodide/pyodide.mjs";

 const pyodide = await loadPyodide();
 await pyodide.loadPackage("micropip");
 const micropip = pyodide.pyimport("micropip");
 await micropip.install("pyroquad");

 await pyodide.runPythonAsync(`
     from pyroquad import *

     activate_engine()

     while True:
         draw_rectangle(x=100, y=100, w=600, h=300, color=Color.GREEN)
         next_frame()
         examples.limit_fps(60)
 `);
</script>

The page needs a <canvas> for the engine to draw into, and Chrome 137+ for JSPI. web/index.html is a complete working example of both.

A plain static server is enough - this port uses JSPI rather than SharedArrayBuffer, so no COOP/COEP headers are required.

Developing on the module itself:

For iterating on the Rust side there is a shorter loop that skips the wheel (and works on Windows, where pyodide-build does not run). It builds the side module directly and hands it to the test page as a zip:

  • macOS / Linux:

    RUSTFLAGS="-C link-arg=-sSIDE_MODULE=2" PYO3_CROSS_PYTHON_VERSION=3.14 cargo build --target wasm32-unknown-emscripten --release --features abi_314

  • Windows (PowerShell):

    $env:RUSTFLAGS="-C link-arg=-sSIDE_MODULE=2"; $env:PYO3_CROSS_PYTHON_VERSION="3.14"

    cargo build --target wasm32-unknown-emscripten --release --features abi_314

then python web/build_web.py. With no wheel in dist/ it falls back to zipping the module together with the pure-Python half of the package into web/pyroquad_pkg.zip, which web/index.html unpacks directly instead of going through micropip. Re-run it after every cargo build.

Only ever one of the two is staged - build_web.py deletes the other - so the page cannot quietly load a stale artifact of the kind you are not testing.

(Remember to clear RUSTFLAGS again before building natively in the same shell - it applies to every target. This path uses whatever emsdk you have rather than the one Pyodide was built with; that skew is usually fine, for the reason given in docs/WASM.md §3, but the wheel build in step 4 is the one that matches the shipped artifact.)

For any Agents reading this:

Please refer to AI.md for best practice and an entrypoint into the engine.

Example Code:

Hello rectangle

from pyroquad import *

activate_engine()

while True:
    draw_rectangle(x=100, y=100, w=600, h=300, color=Color.GREEN)
    draw_text(text="Hello rectangle", x=200, y=200, font_size=50, color=Color.RED)
    next_frame()
    examples.limit_fps(60)

Textures

from pyroquad import *

activate_engine()

texture =  Loading.download_file(
    "https://raw.githubusercontent.com/Ludwig-000/pyroquad/main/docs/PyroquadLogo.png"
).to_Texture2D()

while True:
    draw_texture(texture)
    next_frame()
    examples.limit_fps(60)

Cube

from pyroquad import *

activate_engine()

cube = Cube(
    position=Vec3.ZERO,
    rotation=Vec3.ZERO,
    scale=Vec3.ONE,
    color=Color.RED)

cam = Camera3D(position=Vec3.splat(2), target=Vec3.ZERO)

while True:
    cam.set_camera()
    cube.rot += get_delta_time()
    draw_all_objects()
    next_frame()
    examples.limit_fps(60)

Skybox

from pyroquad import *

activate_engine()


skybox_tex = examples.loading_screen_future(
    lambda a: download_file_future(a),
    ["https://raw.githubusercontent.com/Ludwig-000/pyroquad/main/tests/HDR_blue_nebulae_2.png"],
    show_rotating_square=True
)[0].to_Texture2D()

player = examples.PlayerCamera(position=Vec3.ONE)

while True:
    if KeyCode.Escape in get_keys_pressed():
        break

    player.update()
    draw_skybox(skybox_tex)
    draw_grid(
        slices=1_000,
        spacing=1.0,
        axes_color=Color.YELLOW,
        other_color=Color.GREEN)

    next_frame()
    examples.limit_fps(60)

Multiple windows

import multiprocessing
from pyroquad import *

def task(message, color_name):
    activate_engine()
    prevent_quit()
    color = getattr(Color, color_name)
    
    while not is_quit_requested():
        clear_background(color)
        draw_text(message, 200, 200, Color.GREEN, 60)
        next_frame()
        examples.limit_fps(60)

if __name__ == "__main__":
    seq_data = [("multiple", "YELLOW"), ("windows", "BRICK"), ("using", "ORANGE")]
    for msg, color in seq_data:
        p = multiprocessing.Process(target=task, args=(msg, color))
        p.start()
        p.join()

    procs = [multiprocessing.Process(target=task, args=("multiprocessing", "BLUE")) for _ in range(5)]
    for p in procs: p.start()
    for p in procs: p.join()

Rectangle deletes itself (heartbreaking)

from pyroquad import *

activate_engine()

re = Rectangle(Vec2.splat(200), 0, Vec2.splat(100), Color.WHITE)

timer  = 120
def t(rec: Rectangle):
    global timer, re
    timer-= 1
    if timer == 0:
        del(re)
    rec.draw()

re.tick(t)

while True:
    next_frame()
    examples.limit_fps(60)

Metadata

Release files for pyroquad 0.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for pyroquad 0.1.7
File
pyroquad-0.1.7-cp314-abi3-win_amd64.whl CPython 3.14 abi3 Windows x86-64 Details
pyroquad-0.1.7-cp314-abi3-pyemscripten_2026_0_wasm32.whl CPython 3.14 abi3 PyEmscripten 2026.0+ WebAssembly Details
pyroquad-0.1.7-cp314-abi3-manylinux_2_38_x86_64.whl CPython 3.14 abi3 Linux glibc 2.38+ x86-64 Details
pyroquad-0.1.7-cp314-abi3-macosx_11_0_arm64.whl CPython 3.14 abi3 macOS 11.0+ ARM64 Details
pyroquad-0.1.7-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
pyroquad-0.1.7-cp310-abi3-manylinux_2_38_x86_64.whl CPython 3.10 abi3 Linux glibc 2.38+ x86-64 Details
pyroquad-0.1.7-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
pyroquad-0.1.7-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
pyroquad-0.1.7-cp39-abi3-manylinux_2_38_x86_64.whl CPython 3.9 abi3 Linux glibc 2.38+ x86-64 Details
pyroquad-0.1.7-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details

Total release size: 51.4 MB

Release files / pyroquad-0.1.7-cp314-abi3-win_amd64.whl

Download URL pyroquad-0.1.7-cp314-abi3-win_amd64.whl
Size 5.0 MB
Tags CPython 3.14 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
f8771c3e1f54e3c72459cdefb11f7d2d2e3c34f8613b7d108e74d4f6c76dd031
BLAKE2b-256 checksum
How to use checksums
d4b0be51676b1c36cb20b1d63f2a98bf446fed2322eec70641a1d3586a0fbb7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp314-abi3-pyemscripten_2026_0_wasm32.whl

Download URL pyroquad-0.1.7-cp314-abi3-pyemscripten_2026_0_wasm32.whl
Size 2.2 MB
Tags CPython 3.14 PyEmscripten 2026.0+ WebAssembly abi3
SHA-256 checksum
How to use checksums
79f5285ac1828c1643d7b2b6d66a935a1726cae17d11e181ddc0e572195c2e6d
BLAKE2b-256 checksum
How to use checksums
b752d062b4b1a6dfb260fbf98ca1307d9faffae706eb5de1277c2f941f933563
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp314-abi3-manylinux_2_38_x86_64.whl

Download URL pyroquad-0.1.7-cp314-abi3-manylinux_2_38_x86_64.whl
Size 6.2 MB
Tags CPython 3.14 Linux glibc 2.38+ x86-64 abi3
SHA-256 checksum
How to use checksums
55128011b9e96d80f673e14c6e8190bb5500a6d6af3f35c593f908987d08a492
BLAKE2b-256 checksum
How to use checksums
cf7343ab3ac99a9589efce12bffb54d4a923d1b5f3bff6886f922096bea8fcb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp314-abi3-macosx_11_0_arm64.whl

Download URL pyroquad-0.1.7-cp314-abi3-macosx_11_0_arm64.whl
Size 5.2 MB
Tags CPython 3.14 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
2134c4888ca2a2bb414e1e1b40496c2a14fc57b6b9a1fb138c242cd82c32f69c
BLAKE2b-256 checksum
How to use checksums
f62b2cb79326d5253891a9ff0e08da581b3caf3291700d4f9310816ad7490cd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp310-abi3-win_amd64.whl

Download URL pyroquad-0.1.7-cp310-abi3-win_amd64.whl
Size 5.0 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
b9e0e66b664c7a7cf0bede85d8a3937631311624734eb12593931810ed9a6287
BLAKE2b-256 checksum
How to use checksums
85aa6a84c091e8ed331f47e31da6a3012917d79350da20bde3b17c14b303eaf9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp310-abi3-manylinux_2_38_x86_64.whl

Download URL pyroquad-0.1.7-cp310-abi3-manylinux_2_38_x86_64.whl
Size 6.2 MB
Tags CPython 3.10 Linux glibc 2.38+ x86-64 abi3
SHA-256 checksum
How to use checksums
51adb52e7f5f2ba49e941a79b6c68ba39672a8f085a7b5280a433817047d0e51
BLAKE2b-256 checksum
How to use checksums
73cc9eca99db9a58f0b4cdb5a4cd44bbd1b2d5ff84bddaa785454c41a48a88a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp310-abi3-macosx_11_0_arm64.whl

Download URL pyroquad-0.1.7-cp310-abi3-macosx_11_0_arm64.whl
Size 5.2 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
14e364e20a54a23c6a28d2d12587915ab8806bcde47c67903d57454bc1494148
BLAKE2b-256 checksum
How to use checksums
f644873967c687fb85e5c685374b5a14245172be95b7d7a5d1b3e566b2d893cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp39-abi3-win_amd64.whl

Download URL pyroquad-0.1.7-cp39-abi3-win_amd64.whl
Size 5.0 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
05a6536ea62328b2257c352f464fc7aac68187a11d5cedebf4d8970aad79309e
BLAKE2b-256 checksum
How to use checksums
4409899402b8f0ff8a1508a50b86643f03f4194a8f232170927aa5ef9fe3eea6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp39-abi3-manylinux_2_38_x86_64.whl

Download URL pyroquad-0.1.7-cp39-abi3-manylinux_2_38_x86_64.whl
Size 6.2 MB
Tags CPython 3.9 Linux glibc 2.38+ x86-64 abi3
SHA-256 checksum
How to use checksums
dfcf71b1f2a502c35ea3a4c344b76bd725f37ec95052ccbc2d3aaf2c52e71ac4
BLAKE2b-256 checksum
How to use checksums
547edf3f3957f321211e3eed1aec21be47e00203baaa492b2d2b7b763b1dcb02
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / pyroquad-0.1.7-cp39-abi3-macosx_11_0_arm64.whl

Download URL pyroquad-0.1.7-cp39-abi3-macosx_11_0_arm64.whl
Size 5.2 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
05bce6d20f12271680731c01b8b2e8185ce05ecc76deeff994cda89beb03317f
BLAKE2b-256 checksum
How to use checksums
6ef8d7b89b683a19e5b626884cd409bd15b6a0e84f53b3237ccdbb7eb9c32801
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release history Release notifications | RSS feed

0.1.8

10 release files

This release

0.1.7 This release

10 release files

0.1.6

10 release files

0.1.5

9 release files

0.1.4

9 release files

0.1.3

9 release files

0.1.2

9 release files

0.1.1

9 release files

0.1.0

9 release files

0.0.5

9 release files

0.0.4

9 release files

0.0.2

9 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page