Skip to main content

SwirEngine 1.0.1

SwirEngine is a modern Python-first 2D/3D game engine built around one approachable API. Its goal is to let people create real games in Python without learning OpenGL before they can put a character or 3D model on screen.

SwirEngine 1.0.1 is the current stable release, with a tested cross-platform core, unified 2D/3D API, rendering, input, physics, audio, assets, scenes and developer tooling.

PyPI package revision 1.0.1.post1 refreshes documentation and package metadata only; runtime behavior and the public API remain the same as 1.0.1.

Install

python -m pip install swirengine

Optional audio support:

python -m pip install "swirengine[audio]"

What works now

  • one Game API for 2D and 3D
  • GPU rendering with ModernGL / OpenGL 3.3
  • GLFW keyboard + mouse input with held/pressed/released queries
  • colored 2D primitives, render layers and camera-independent screen-space objects
  • textured Sprite2D rendering with alpha blending
  • adjacent compatible sprite batching to reduce draw calls without changing render order
  • renderer statistics for draw calls, batches, sprites, meshes, triangles, lights and cache usage
  • frame profiler with update/physics/render timings and history averages
  • built-in live debug overlay through game.show_debug()
  • bounded LRU text-texture cache for changing HUD/debug text
  • cached Text2D rendering with custom fonts/sizes/colors
  • built-in UI labels, panels, buttons and progress bars
  • sprite-sheet UV regions and named AnimatedSprite2D animations
  • reusable TileMap2D grids backed by pooled sprites
  • lazy GPU texture cache
  • movable/zoomable Camera2D
  • real perspective Camera3D with look-at and local-space movement
  • reusable MeshData / Mesh3D geometry with lazy GPU mesh caching
  • Wavefront OBJ plus static glTF/GLB mesh and scene import
  • material-aware glTF/GLB base-color and packed metallic/roughness texture loading
  • DirectionalLight3D, PointLight3D and SpotLight3D
  • multiple simultaneous lights with explicit 4/4/4 directional/point/spot GPU budgets
  • legacy Phong materials plus native Cook-Torrance GGX metallic/roughness PBR
  • project AssetManager with aliases, scanning and diagnostics
  • sound effects and background music through a pluggable audio service
  • AABB collision detection plus fixed-step arcade rigid-body physics
  • pooled particle effects
  • JSON SaveStore with atomic persistence
  • reusable prefabs with per-instance overrides
  • versioned scene/prefab JSON serialization with a safe codec registry
  • scene lifecycle, names and tags
  • creator-friendly factories for gameplay, UI and 3D objects
  • variable update + deterministic fixed-update loop
  • vectors, colors and transforms
  • CLI project generator
  • tests + multi-platform GitHub Actions CI

Install for development

python -m pip install -e ".[dev]"
pytest
ruff check src tests examples
python -m compileall -q src examples

Audio playback is optional so headless development remains lightweight:

python -m pip install -e ".[audio]"

Tiny 2D game

Put player.png in assets/, then:

from swirengine import Game

game = Game("My Game")
player = game.sprite("player.png", x=0, y=0, width=96, height=96, name="player")

@game.update
def update(dt):
    speed = 300
    if game.key("A"):
        player.x -= speed * dt
    if game.key("D"):
        player.x += speed * dt

    game.camera.follow(player)

game.run()

Performance profiler and debug overlay

Enable the built-in diagnostics HUD before game.run():

from swirengine import Game

game = Game("Profile Me")
game.show_debug()
game.run()

The overlay displays FPS, frame/CPU time, update/physics/render timings, draw calls, sprite count, sprite batches, triangle count and 3D directional/point/spot light usage. It also reports lights dropped when a scene exceeds the forward renderer's per-type GPU budget. Programmatic data is available through game.profiler.latest, game.profiler.samples and game.profiler.average(...).

The 2D renderer automatically merges adjacent sprites that share the same texture, layer and screen/world-space mode into a single GPU draw call. Batching deliberately preserves render order, which keeps alpha-blended scenes predictable.

UI and text

UI elements are screen-space objects, so they stay fixed while the world camera moves. Buttons include hover, press and click handling, and overlapping buttons only activate the topmost control.

from swirengine import Game

game = Game("Menu")
game.panel(0, 0, 420, 260)
game.label("Main Menu", 0, 85, font_size=32)
progress = game.progress_bar(0, 20, 280, 24, value=0.4)


def play(_button):
    progress.value = min(1.0, progress.value + 0.1)


game.button("Play", 0, -70, 180, 52, on_click=play)
game.run()

Use game.text(...) for world-space text that follows the camera, and game.label(...) for HUD/menu text. Text2D supports a custom TrueType font path, size, color and scale. Dynamic text textures are kept in a bounded LRU cache so changing counters/debug labels do not grow GPU memory forever.

Audio and music

Put supported audio files under assets/, install the audio extra, then use the same asset resolution rules as sprites and tilemaps:

from swirengine import Game

game = Game("Audio Demo")
game.audio.master_volume = 0.8
game.audio.music_volume = 0.6
music = game.music("music/theme.ogg")

@game.update
def update(_dt):
    if game.key_pressed("SPACE"):
        shot = game.sound("sfx/laser.wav", volume=0.7)
        shot.set_volume(0.9)
    if game.key_pressed("M"):
        music.stop()

game.run()

Game.music(...) replaces the previous music track and loops by default. Sound effects can loop independently. AudioHandle exposes stop(), set_volume(...), active, loop and path. The backend protocol is public, so future platform targets can provide another implementation without changing game code.

Tilemaps

A tile atlas is addressed row-major from its top-left tile. The tilemap owns a fixed pool of sprites, so editing cells does not continuously allocate scene objects. Because pooled tiles usually share one atlas texture, they also benefit strongly from sprite batching.

from swirengine import Game

game = Game("Tile World")
world = game.tilemap(
    "tiles.png",
    width=20,
    height=12,
    tile_width=32,
    tile_height=32,
    atlas_columns=8,
    atlas_rows=4,
    layer=-10,
)

world.fill(0)
world.set_tile(3, 2, 7)
world.set_tile(4, 2, 7)
world.set_tile(5, 2, None)

game.run()

Use world.world_to_cell(x, y) for picking and world.cell_to_world(column, row) when placing actors on grid centers.

Save data

Pass save_path to autoload an existing JSON save file. Writes are atomic.

from swirengine import Game

game = Game("Persistent Game", save_path="saves/profile.json")
score = game.storage.get("score", 0)
game.storage.set("score", score + 100).save()

SaveStore can also be used directly when a game needs multiple save slots.

Sprite-sheet animation

from swirengine import AnimatedSprite2D, Game, SpriteSheet

game = Game("Animated Hero")
sheet = SpriteSheet(columns=6, rows=2)
hero = AnimatedSprite2D(game.assets.resolve("hero_sheet.png"), width=96, height=96)
hero.add_animation("idle", sheet.row(0), fps=6)
hero.add_animation("walk", sheet.row(1), fps=12)
game.add(hero)

@game.update
def update(dt):
    moving = game.key("A") or game.key("D")
    hero.play("walk" if moving else "idle")

game.run()

Animations advance automatically because AnimatedSprite2D participates in normal scene updates. Clips can loop or stop on their final frame.

Collision and physics

from swirengine import Color, Game, Rectangle2D

game = Game("Physics")
player = game.add(Rectangle2D(0, 0, 64, 64, Color(0.2, 0.7, 1.0, 1.0)))
body = game.rigidbody(player, restitution=0.1)
body.apply_impulse(220, 420)

game.run()

The collision layer also supports lightweight AABB queries when full rigid-body response is unnecessary.

Asset aliases and diagnostics

game.assets.register("hero", "characters/hero.png")
hero_path = game.assets.require("hero")
report = game.assets.diagnostics()

Aliases work for audio too, because the audio service shares the game's AssetManager. Asset diagnostics can scan the project tree, summarize sizes/types and identify aliases whose targets no longer exist without loading the assets into RAM or GPU memory.

3D camera, meshes and import

Game(mode="3d") creates a Camera3D. Its default view looks down the negative Z axis, so older Cube3D scenes remain compatible. You can move in camera-local axes and point it at any world position:

from swirengine import Color, Cube3D, Game, Vec3

game = Game("My 3D Game", mode="3d")
cube = game.add(Cube3D(position=Vec3(0, 0, -4), color=Color(0.8, 0.2, 0.5, 1)))
game.camera.move(0, 1, 2).look_at(cube.position)

@game.update
def spin(dt):
    cube.rotation.y += 45 * dt

game.run()

For imported geometry, put an OBJ, glTF or GLB asset below assets/. OBJ files can be loaded with game.obj(...), while static glTF meshes can be loaded with game.gltf(...). Lower-level glTF APIs preserve scene hierarchies and per-primitive materials when needed.

3D lighting and materials

Explicit lights are scene objects and can be created directly from Game. When a scene contains no explicit light, the renderer keeps the historical default directional light so older 3D projects continue to render as expected.

Legacy Phong materials remain supported:

from swirengine import Color, Game, Material3D, Vec3, cube_mesh

game = Game("Lighting", mode="3d")
material = Material3D(
    tint=Color(0.6, 0.8, 1.0, 1.0),
    ambient=0.12,
    diffuse=0.8,
    specular=0.6,
    shininess=48.0,
)
model = game.mesh(cube_mesh(), material=material)
model.position = Vec3(0.0, 0.0, -4.0)

game.directional_light(direction=Vec3(-0.4, -1.0, -0.3), intensity=0.7)
game.point_light(position=Vec3(2.0, 2.0, -2.0), range=8.0, intensity=2.0)
game.camera.look_at(model.position)
game.run()

For physically based metallic/roughness shading, set either PBR factor. SwirEngine then uses a Cook-Torrance BRDF with GGX normal distribution, Schlick-GGX geometry and Fresnel-Schlick reflection instead of the legacy Phong path:

material = Material3D(
    tint=Color(0.82, 0.35, 0.12, 1.0),
    ambient=0.04,
    metallic=0.9,
    roughness=0.18,
)

metallic_roughness_texture is also supported. It follows glTF 2.0 packing: roughness is read from the green channel and metallic from the blue channel, then multiplied by the scalar roughness and metallic factors. glTF/GLB metallicRoughnessTexture assets are resolved automatically alongside base-color textures, including external files, data URIs and images embedded in GLB buffer views.

DirectionalLight3D models distant sun/moon lighting. PointLight3D uses smooth distance attenuation, while SpotLight3D combines distance attenuation with smooth inner/outer cone cutoffs. The OpenGL 3.3 forward pass currently accepts up to four active lights of each type at once. Selection is deterministic in scene order, and any lights above the budget are reported through RendererStats.lights_dropped, FrameProfile.lights_dropped and the debug overlay instead of being silently ignored. MAX_DIRECTIONAL_LIGHTS, MAX_POINT_LIGHTS and MAX_SPOT_LIGHTS expose the current budgets.

Skybox/environment lighting, shadows, post-processing and broader glTF normal/occlusion/ emissive map support remain roadmap work. Color-space fidelity will also be hardened as the PBR renderer grows.

Prefabs and scene persistence

Reusable groups can be captured as prefabs, instantiated independently and overridden per instance. Scene/prefab JSON serialization uses a versioned format and an explicit safe codec registry rather than dynamically importing arbitrary classes named by a file. This is the foundation for later editor files, hot reload and plugin workflows.

CLI

swirengine info
swirengine new MyGame --mode 2d
swirengine new My3DGame --mode 3d

See ROADMAP.md for future SwirEngine development plans and upcoming engine/editor features.

Metadata

Release files for swirengine 1.0.1.post1

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

Source distribution (sdist)

Source distribution for swirengine 1.0.1.post1
File Size Uploaded
swirengine-1.0.1.post1.tar.gz 199.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for swirengine 1.0.1.post1
File Interpreter ABI Platform
swirengine-1.0.1.post1-py3-none-any.whl Python 3 none any Details

Total release size: 340.9 kB

Release files / swirengine-1.0.1.post1.tar.gz

Download URL swirengine-1.0.1.post1.tar.gz
Size 199.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5577d288bb7d075926e46660053d9a09b048af71d14e9d9df9fcd09c20cfe574
BLAKE2b-256 checksum
How to use checksums
22e6f8e21c395ca6b2d9fe152d4808a9168b9e2c134c5d076b668dfca084e86c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

Release files / swirengine-1.0.1.post1-py3-none-any.whl

Download URL swirengine-1.0.1.post1-py3-none-any.whl
Size 141.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7b260e79f3fbea0a36285d5388b74760b47faea67434c0095bd89469b0345f05
BLAKE2b-256 checksum
How to use checksums
1230e337c5f51fe9d137efac8f4d80d037d71296973ab49a3d415eaa0c056bc7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.

Transparency log

Release history Release notifications | RSS feed

2.2.1

3 release files

2.2.0

3 release files

2.1.0

3 release files

2.0.0

3 release files

1.5.0

3 release files

1.4.0

3 release files

1.3.0

3 release files

1.2.0

3 release files

1.1.0

3 release files

1.0.3

3 release files

1.0.2

2 release files

This release

1.0.1.post1 This release

2 release files

1.0.1

2 release files

0.4.22

2 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