Skip to main content

pygameP

Pygame Plus — A comprehensive game development framework for Pygame with 44 modules: GLSL shaders, physics, scene management, lighting, dialogue, AI behavior trees, inventory, quests, achievements, CLI tools, and more.

Installation

pip install pygameP

Dependencies are installed automatically:

  • pygame >= 2.0.0
  • PyOpenGL >= 3.1.0 (for shader support)

Features

Module Description
shaders GLSL shader system + built-in effects (grayscale, blur, invert, brightness, pulse, wave)
performance Object pool, spatial hash, FPS monitor, batch renderer
scene .pgstage JSON scene files with multi-scene switching and script bindings
physics Simple rigid-body physics (gravity, collision, raycasting)
input Unified input for keyboard, mouse, gamepad, and multi-touch
camera 2D camera with smooth follow, screen shake, zoom, and bounds
audio Audio manager with BGM fade, SFX pooling, and volume control
audio_fx Audio effects (reverb, echo, pitch shift) + 2D positional audio
animation SpriteSheet, Animation, AnimationController
particles Particle emitters, lifecycle, color gradients
tween 22+ easing functions, delay, callbacks
sprites SpriteGroup batch update/draw, collision detection
lighting 2D lighting with raycasted shadows, shadow-bleed, soft edges, distance falloff
dialogue Branching dialogue trees, typewriter effect, choices
behaviortree AI behavior trees (Selector, Sequence, Action, Condition, decorators)
statemachine Finite state machine for game states and AI
events Publish/subscribe event bus
pathfinding A* pathfinding with grid maps
tilemap Orthographic + isometric tilemap rendering
inventory Item system with stacking, equipment, serialization
quest Quest tracking, objectives, prerequisites, rewards
achievement Achievement unlock tracking, progress, notifications
gui Button, Slider, TextInput, ProgressBar, Label
gui_widgets Dropdown, Checkbox, RadioButton, Panel, ImageWidget
scrollview ScrollView, TabPanel, Tooltip, ContextMenu
layout VBox, HBox, GridLayout auto-arrangement
dialog MessageBox, ConfirmDialog, InputDialog
theme GUI theme system (dark/light/retro built-in)
sprite_atlas Texture atlas packing + 9-slice/9-patch UI rendering
render_target Off-screen rendering, post-processing (Bloom, Vignette, ColorGrade)
noise Perlin noise, cave/maze/terrain generation
i18n Multi-language support with string formatting
parallax Multi-layer parallax scrolling backgrounds
async_utils AsyncTimer, LoopRunner for non-blocking tasks
assets Cached asset loader (images, sounds, fonts, JSON)
save JSON save slots + config manager
debug Debug overlay with FPS, hitboxes, custom stats
console In-game debug console + screenshot capture
virtual_joystick On-screen touch/mouse joystick
tiled Tiled Map Editor (.tmx) importer
script_engine Script bindings: file path or inline code (start/update/event hooks)
webview Embed HTML content into Pygame (WebSurface)
video Play MP4 videos in Pygame (VideoPlayer)
window Resizable windows, maximize, fullscreen, adaptive scaling (letterbox/pillarbox)
cli CLI project management (new, run, build, pgstage, validate, merge, diff, etc.)

Quick Start

1. GLSL Shader Effects

Load shaders from .glsl files or inline code strings. Apply to the entire screen or individual sprites.

from pygameP import Shader, ShaderEffect, BuiltInEffects

# Use built-in effects
grayscale = BuiltInEffects.grayscale()
blur = BuiltInEffects.blur(radius=3.0)
invert = BuiltInEffects.invert()
pulse = BuiltInEffects.pulse(speed=2.0)
wave = BuiltInEffects.wave(amplitude=0.05, frequency=10.0)

# Apply to the entire screen
grayscale.apply(screen)

# Apply to a single sprite
pulse.apply(my_sprite)

# Custom GLSL code
my_shader = Shader(fragment_source="""
#version 330 core
in vec2 v_texcoord;
out vec4 frag_color;
uniform sampler2D u_texture;
uniform float u_time;
void main() {
    vec4 c = texture(u_texture, v_texcoord);
    frag_color = vec4(c.r, c.g * abs(sin(u_time)), c.b, c.a);
}
""")

# Load from file
custom = Shader(
    vertex_file="assets/shaders/default.vert",
    fragment_file="assets/shaders/plasma.frag"
)

2. Performance Optimization

from pygameP import ObjectPool, SpatialHash, FPSMonitor, BatchRenderer

# Object pool — reduce allocation overhead for bullets/particles
bullet_pool = ObjectPool(lambda: Bullet(), initial_size=200)
bullet = bullet_pool.acquire()   # grab from pool
# ... use it ...
bullet_pool.release(bullet)      # return to pool

# Spatial hash — fast collision detection
spatial = SpatialHash(cell_size=64)
spatial.insert(enemy, enemy.rect)
nearby = spatial.query(player.rect)  # only check nearby objects

# FPS monitor + adaptive quality
fps_monitor = FPSMonitor(target_fps=60)
while running:
    dt = fps_monitor.tick()
    if fps_monitor.should_reduce_quality():
        reduce_particle_count()

# Batch rendering — merge draw calls
batch = BatchRenderer(screen)
for sprite in sprites:
    batch.blit(sprite.image, sprite.rect.topleft)
batch.render()  # execute all at once

3. Scene Management (.pgstage)

.pgstage is pygameP's custom scene file format, based on JSON.

Scene Editor: .pgstage files are created and managed by pgstage Editor, a visual scene editor. The pygameP library only loads and runs scenes. Online Editor: https://tomlct2015.github.io/pygameP/editor/pgstage-editor

{
  "name": "ExampleLevel",
  "width": 1600,
  "height": 1200,
  "background_color": [20, 25, 40],
  "camera_x": 0,
  "camera_y": 0,
  "properties": {
    "music": "assets/music/level1.ogg",
    "difficulty": "normal"
  },
  "entities": [
    {
      "type": "Player",
      "x": 100,
      "y": 800,
      "layer": 5,
      "hp": 100,
      "tag": "player"
    },
    {
      "type": "Enemy",
      "x": 500,
      "y": 800,
      "layer": 4,
      "hp": 50,
      "ai": "patrol"
    }
  ]
}

Usage in Python:

from pygameP import Scene, SceneManager, SceneEntity

# Load a scene from .pgstage file
scene = Scene.load("levels/level1.pgstage")

# Multi-scene management (with transition)
manager = SceneManager()
manager.load("menu", "scenes/menu.pgstage")
manager.load("game", "levels/level1.pgstage")
manager.switch_to("game", transition=0.5)

# Game loop
while running:
    dt = clock.tick(60) / 1000.0
    manager.update(dt)
    manager.draw(screen)

4. Simple Physics Engine

from pygameP import RigidBody, PhysicsWorld, BoxCollider, CircleCollider

# Create physics world
world = PhysicsWorld(gravity=980.0)

# Dynamic rigid body (affected by gravity)
player = RigidBody(x=100, y=0, mass=1.0, collider=BoxCollider(32, 64))
player.restitution = 0.3  # bounciness
player.friction = 0.2     # friction
world.add_body(player)

# Static rigid body (ground, platforms)
ground = RigidBody(x=0, y=600, mass=0, collider=BoxCollider(800, 40))
world.add_body(ground)

# Apply force or impulse
player.apply_force(500, 0)        # continuous force
player.apply_impulse(0, -300)     # jump (instant)

# Update each frame
world.update(dt)

# Collision callback
def on_hit(body1, body2):
    print("Collision!")
world.on_collision = on_hit

# Raycasting
hit = world.raycast((100, 300), (1, 0), max_distance=500)
if hit:
    body, distance, point = hit
    print(f"Hit {body} at distance {distance}")

5. Extended Input Devices

from pygameP import InputManager

input_mgr = InputManager()

# Map actions to multiple input sources
input_mgr.map_action("jump", "keyboard", pygame.K_SPACE)
input_mgr.map_action("jump", "gamepad", (0, 0))  # gamepad 0, button 0

input_mgr.map_action("shoot", "mouse", 1)  # left mouse button
input_mgr.map_action("move_right", "gamepad", (0, "axis_0"))

# Game loop
while running:
    events = pygame.event.get()
    input_mgr.update(events)

    if input_mgr.is_action_just_pressed("jump"):
        player.jump()
    if input_mgr.is_action_pressed("move_right"):
        player.move_right()

    # Analog input (gamepad stick)
    move_x = input_mgr.get_action_value("move_right")

    # Direct gamepad access
    pad = input_mgr.get_gamepad(0)
    if pad:
        left_stick = pad.get_left_stick()
        pad.rumble(0.5, 0.5, 100)  # vibration

    # Touch input
    if input_mgr.touch.is_touching():
        pos = input_mgr.touch.get_touch_position()

Supported input devices:

  • Keyboard — key press/release/just-pressed
  • Mouse — position, relative motion, scroll wheel, button state
  • Gamepad — buttons, stick axes, D-pad, rumble/vibration
  • Touch — multi-touch, touch start/end/move

6. Async Timer & Loop Runner

Non-blocking scheduled tasks for game loops.

from pygameP import AsyncTimer, LoopRunner

timer = AsyncTimer()

# Infinite loop — run callback every N seconds
timer.loop(0.1, update_ai)

# One-shot — run once after delay
timer.after(3.0, spawn_boss)

# Repeating — run every N seconds (stoppable)
task = timer.every(1.0, save_checkpoint)
task.stop()  # cancel later

# Per-frame loop runner
runner = LoopRunner()
runner.add("physics", update_physics)
runner.add("ai", update_ai)

# In game loop:
timer.update(dt)
runner.update(dt)

7. Asset Manager

Global resource loader with automatic caching.

from pygameP import AssetManager

assets = AssetManager("assets")

# Load (cached after first load)
img = assets.image("sprites/player.png")
snd = assets.sound("sfx/jump.ogg")
fnt = assets.font("fonts/arial.ttf", 24)
data = assets.json("data/level1.json")

# Preload all at startup
assets.preload_images(["bg.png", "player.png", "enemy.png"])
assets.preload_sounds(["jump.ogg", "hit.ogg"])

# Background music
assets.music("music/theme.ogg", fade_ms=1000)
assets.stop_music(fade_ms=500)

# Stats
print(assets.stats())  # {'images': 3, 'sounds': 2, ...}

# Cleanup
assets.unload("sprites/player.png")
assets.clear()  # clear all caches

8. Save System & Config

JSON-based save slots and game configuration.

from pygameP import SaveManager, Config

# Save game
save = SaveManager("saves")
save.save("slot1", {"level": 5, "hp": 100, "items": ["sword", "shield"]})
data = save.load("slot1")
save.exists("slot1")  # True
save.list_slots()     # ["slot1"]
save.delete("slot1")

# Config with defaults
cfg = Config("settings.json", defaults={
    "volume": 0.8,
    "resolution": [1280, 720],
    "fullscreen": False,
})
vol = cfg.get("volume")      # 0.8
cfg.set("volume", 0.5)
cfg["fullscreen"] = True     # bracket access
cfg.save()                   # write to disk
cfg.reset()                  # reset to defaults

9. Camera System

2D camera with smooth follow, screen shake, zoom, and world bounds.

from pygameP import Camera

cam = Camera(1280, 720, world_width=3200, world_height=1800)

# Smooth follow player
cam.follow(player.x, player.y, speed=5.0, dead_zone=20)

# Instant snap to position
cam.look_at(500, 300)

# Screen shake on hit
cam.shake(intensity=8, duration=0.3)

# Zoom
cam.zoom_to(1.5, speed=3.0)

# In game loop:
cam.update(dt)
offset = cam.offset  # (dx, dy) to subtract from entity positions

# Coordinate conversion
sx, sy = cam.world_to_screen(world_x, world_y)
wx, wy = cam.screen_to_world(mouse_x, mouse_y)

# Visibility check
if cam.visible(entity.x, entity.y, 32, 32):
    entity.draw(screen)

10. Audio Manager

BGM fade, SFX pooling, and volume control.

from pygameP import AudioManager

audio = AudioManager(sfx_channels=16)

# Background music with fade
audio.play_bgm("music/theme.ogg", fade_ms=1000)
audio.set_bgm_volume(0.7)
audio.stop_bgm(fade_ms=500)
audio.pause_bgm()
audio.resume_bgm()

# Sound effects (auto-cached)
audio.play_sfx("sfx/jump.ogg")
audio.play_sfx("sfx/explosion.ogg", volume=0.5)

# Volume groups
audio.set_master_volume(0.8)  # affects everything
audio.set_sfx_volume(0.6)     # affects SFX only

# Preload
audio.preload_sfx(["jump.ogg", "hit.ogg", "coin.ogg"])

11. GUI Widgets

Button, Slider, TextInput, ProgressBar, Label.

from pygameP import Button, Slider, TextInput, ProgressBar, Label

btn = Button(100, 200, 200, 50, "Start Game", callback=start_game)
slider = Slider(100, 300, 200, min_val=0.0, max_val=1.0, value=0.8)
text = TextInput(100, 400, 200, 40, placeholder="Enter name...")
bar = ProgressBar(100, 500, 200, 20, value=0.75)
label = Label(100, 550, "Score: 0", font_size=24)

# In game loop:
for event in pygame.event.get():
    btn.handle_event(event)
    slider.handle_event(event)
    text.handle_event(event)

text.update(dt)  # cursor blink

btn.draw(screen)
slider.draw(screen)
text.draw(screen)
bar.draw(screen)
label.draw(screen)

# Access values
print(slider.value)   # 0.0 to 1.0
print(text.text)      # typed string
bar.value = 0.9       # auto-clamped to 0.0-1.0

12. Debug Overlay

Press F1 to toggle. Shows FPS, entity count, hitboxes, and custom stats.

from pygameP import DebugOverlay

dbg = DebugOverlay()
dbg.show_hitboxes = True
dbg.show_fps = True
dbg.show_entity_count = True
dbg.hotkey = pygame.K_F1  # configurable

# In game loop:
dbg.handle_event(event)
dbg.update(dt, entity_count=len(entities), score=score, ammo=ammo)
dbg.draw(screen, entities)  # draws hitboxes + overlay

# Custom stats
dbg.set_stat("health", player.hp)
dbg.set_stat("mode", "survival")

13. Tiled Map Import

Load Tiled Map Editor (.tmx) files.

from pygameP import TiledLoader

loader = TiledLoader()
tilemap = loader.load("levels/level1.tmx")

# Access layers
for layer in tilemap.layers:
    print(layer.name, layer.width, layer.height)

# Get tile at position
tile_id = tilemap.get_tile("Ground", x=5, y=3)

# Access objects
for obj in tilemap.objects:
    print(obj.name, obj.type, obj.x, obj.y)

# Find objects by type
enemies = tilemap.get_object_layer("Enemies").get_objects_by_type("Enemy")
spawn = tilemap.get_object_layer("Spawns").get_object_by_name("PlayerStart")

# Tileset info
for ts in tilemap.tilesets:
    print(ts.name, ts.first_gid, ts.image_source)

# Coordinate conversion
px, py = tilemap.tile_to_pixel(5, 3)
tx, ty = tilemap.pixel_to_pixel(mouse_x, mouse_y)

# Check visibility
if tilemap.visible(entity.rect):
    entity.draw(screen)

API Reference

Shader

  • Shader(vertex_source, fragment_source, vertex_file, fragment_file) — create shader
  • shader.use() / shader.stop() — enable/disable
  • shader.set_uniform(name, value) — set uniform (float, int, vec2, vec3, vec4)
  • shader.apply_to_surface(surface) — apply to entire screen
  • shader.apply_to_sprite(sprite) — apply to single sprite

BuiltInEffects

  • grayscale() — grayscale filter
  • blur(radius) — blur effect
  • invert() — color inversion
  • brightness(amount) — brightness adjustment
  • pulse(speed) — pulsing glow
  • wave(amplitude, frequency) — wave distortion

ObjectPool

  • acquire() — get object from pool
  • release(obj) — return object
  • release_all() — return all objects
  • resize(n) — adjust pool size

SpatialHash

  • insert(obj, rect) — insert object
  • query(rect) — query objects in area
  • query_nearby(obj, rect) — query nearby (excludes self)
  • remove(obj) / clear()

Scene / SceneManager

  • Scene.load(path) — load .pgstage file
  • SceneManager.load(name, path) — load and register scene
  • SceneManager.switch_to(name, transition) — switch scenes

PhysicsWorld

  • add_body(body) / remove_body(body)
  • update(dt) — step physics (handles gravity and collisions)
  • raycast(start, direction, max_distance) — ray cast query

InputManager

  • update(events) — update all input devices
  • is_action_pressed(action) — is action held down
  • is_action_just_pressed(action) — was action just pressed this frame
  • get_action_value(action) — get analog value (-1 to 1)
  • map_action(action, device, binding) — map action to input

AsyncTimer

  • loop(interval, callback) — run callback every N seconds (infinite)
  • every(interval, callback) — alias for loop()
  • after(delay, callback) — run once after delay
  • update(dt) — advance all tasks
  • cancel_all() — cancel all tasks

LoopRunner

  • add(name, callback) — register per-frame callback
  • remove(name) — remove callback
  • update(dt) — call all callbacks with dt

AssetManager

  • image(path) — load/cache image
  • sound(path) — load/cache sound
  • font(path, size) — load/cache font
  • json(path) — load/cache JSON data
  • music(path, fade_ms) — play BGM
  • preload_images(paths) — bulk preload
  • stats() — cache statistics
  • clear() — clear all caches

SaveManager

  • save(slot, data) — save to slot
  • load(slot) — load from slot
  • exists(slot) — check if slot exists
  • delete(slot) — delete slot
  • list_slots() — list all slots

Config

  • get(key, default) — get value with fallback
  • set(key, value) — set value
  • save() — write to disk
  • reset() — reset to defaults
  • config[key] — bracket access

Camera

  • follow(x, y, speed, dead_zone) — smooth follow
  • look_at(x, y) — instant snap
  • shake(intensity, duration) — screen shake
  • zoom_to(value, speed) — smooth zoom
  • update(dt) — update camera
  • offset — (dx, dy) for rendering
  • world_to_screen(x, y) — coordinate conversion
  • screen_to_world(x, y) — reverse conversion
  • visible(rect) — visibility check

AudioManager

  • play_bgm(path, fade_ms) — play background music
  • stop_bgm(fade_ms) — stop BGM with fade
  • play_sfx(path, volume) — play sound effect
  • set_master_volume(v) — global volume
  • set_bgm_volume(v) — BGM volume
  • set_sfx_volume(v) — SFX volume
  • preload_sfx(paths) — bulk preload

GUI Widgets

  • Button(x, y, w, h, text, callback) — clickable button
  • Slider(x, y, w, min, max, value) — horizontal slider
  • TextInput(x, y, w, h, placeholder) — text input field
  • ProgressBar(x, y, w, h, value) — progress bar
  • Label(x, y, text, font_size) — static text
  • All widgets: handle_event(event), update(dt), draw(surface)

DebugOverlay

  • handle_event(event) — toggle on F1
  • update(dt, entity_count, **custom) — update stats
  • draw(surface, entities) — render overlay + hitboxes
  • set_stat(key, value) — add custom stat

TiledLoader

  • load(path) — load .tmx file
  • Returns TileMap with:
    • layers — list of TileLayer
    • object_layers — list of ObjectLayer
    • tilesets — list of Tileset
    • get_tile(layer_name, x, y) — get tile ID
    • tile_to_pixel(tx, ty) — coordinate conversion

Full Example

import pygame
from pygameP import (
    SceneManager, Scene, SceneEntity,
    PhysicsWorld, RigidBody, BoxCollider,
    InputManager, FPSMonitor, BuiltInEffects
)

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Scene
manager = SceneManager()
game = Scene({"name": "Game", "width": 800, "height": 600,
              "background_color": [30, 40, 60]})
manager.add("game", game)
manager.switch_to("game")

# Physics
world = PhysicsWorld(gravity=980.0)
player = RigidBody(400, 100, mass=1.0, collider=BoxCollider(32, 32))
world.add_body(player)
ground = RigidBody(0, 550, mass=0, collider=BoxCollider(800, 50))
world.add_body(ground)

# Input
input_mgr = InputManager()
input_mgr.map_action("jump", "keyboard", pygame.K_SPACE)
input_mgr.map_action("left", "keyboard", pygame.K_LEFT)
input_mgr.map_action("right", "keyboard", pygame.K_RIGHT)

# FPS monitor
fps = FPSMonitor(target_fps=60)

# Shader effect
effect = BuiltInEffects.pulse(speed=2.0)

running = True
while running:
    dt = clock.tick(60) / 1000.0
    fps.tick()

    events = pygame.event.get()
    input_mgr.update(events)

    for event in events:
        if event.type == pygame.QUIT:
            running = False

    # Input
    if input_mgr.is_action_just_pressed("jump") and player.grounded:
        player.apply_impulse(0, -500)
    if input_mgr.is_action_pressed("left"):
        player.apply_force(-300, 0)
    if input_mgr.is_action_pressed("right"):
        player.apply_force(300, 0)

    # Update
    world.update(dt)
    manager.update(dt)

    # Draw
    manager.draw(screen)
    pygame.draw.rect(screen, (255, 200, 100),
                     (player.x, player.y, 32, 32))
    pygame.draw.rect(screen, (100, 100, 100),
                     (ground.x, ground.y, 800, 50))

    pygame.display.flip()

pygame.quit()

New in v1.0.11

14. Lighting System (v1.0.17)

A full 2D lighting engine with raycasted visibility polygons, circular and rectangular shadow casters, and configurable visual effects.

Architecture — Shared Dark Cloth

All lights are composited into a single _light_pass surface per frame. Both render() and render_layer() reuse this surface, avoiding duplicate raycast computation.

from pygameP import Light2D, LightingSystem

lighting = LightingSystem(ambient=(20, 20, 40))

# Add shadow casters (rectangular and circular)
lighting.add_shadow_rect(pygame.Rect(100, 200, 300, 20))
lighting.add_shadow_circle(500, 400, 30)

# Create lights
torch = Light2D(x=400, y=300, radius=200, color=(255, 200, 100))
torch.flicker(speed=5.0, amount=0.15)
lighting.add(torch)

spot = Light2D(x=100, y=100, radius=300, angle=45, spread=30)
lighting.add(spot)

# Game loop
lighting.update(dt)
lighting.render(screen, scene_surface, camera_offset)

# Or per-layer rendering
lit_bg = lighting.render_layer((800, 600), bg_surface, 0, camera_offset)
lit_fg = lighting.render_layer((800, 600), fg_surface, 1, camera_offset)

Light2D — Point Light & Spotlight

Parameter Description
x, y World position
radius Maximum reach in pixels
color (r, g, b) light colour
intensity Brightness multiplier (0.0–1.0)
angle Spotlight direction in degrees (None for omnidirectional)
spread Half-angle of spotlight cone in degrees
affect_layers Which layers this light illuminates (None = all)
Attribute Default Description
enabled True Set False to disable without removing
cast_shadow True Set False to skip shadow computation (faster)
shadow_bleed 20.0 Pixels to extend light behind occluders (0 = off)
edge_softness 0.0 Soft-edge width in pixels (0 = hard edge)
distance_falloff 0.0 0.0 = uniform, 1.0 = fully dark at edge
# Point light (omnidirectional)
glow = Light2D(200, 300, radius=150, color=(200, 200, 255), intensity=0.5)

# Spotlight (directional cone)
flashlight = Light2D(
    x=400, y=300, radius=700,
    color=(255, 225, 145), intensity=1.0,
    angle=0.0, spread=28.0)

# Dynamic properties
flashlight.x, flashlight.y = player.x, player.y
flashlight.angle = player.facing_degrees
flashlight.radius = 700
flashlight.color = (180, 210, 255)  # change colour

Visual Effects

All three effects are per-light configurable and can also be set globally.

# Per-light
flashlight.shadow_bleed = 30        # light peeks behind walls
flashlight.edge_softness = 15       # soft transition at light boundary
flashlight.distance_falloff = 0.5   # darker at edges

# Global (affects ALL lights)
lighting.set_shadow_bleed(25)
lighting.set_edge_softness(10)
lighting.set_distance_falloff(0.3)
Effect Visual
Shadow Bleed Light slightly extends behind occluders, revealing their silhouette
Edge Softness Smooth glow transition at the light boundary instead of hard cut
Distance Falloff Gradual darkening from centre to edge

Raycasting Details

  • 360° uniform rays (1° spacing) + vertex-aimed rays (±ε at each wall corner)
  • Circle tangent rays for smooth shadow casting around circular obstacles
  • Arc interpolation: consecutive circle-boundary hits are connected by interpolated arc points (≤ 12° threshold → arc, > 12° → straight line for wall boundaries)
  • Polygon closes through the light source (no wedge-shaped gap at source)
  • Screen culling: lights fully outside the viewport are skipped

Light source markers should be drawn after lighting.render(), directly onto the screen, otherwise they get darkened by the multiply blend.

15. Dialogue System

from pygameP import DialogueTree, DialogueBox

tree = DialogueTree()
tree.add_node("start", speaker="Elder", text="Welcome, hero!")
tree.add_node("quest", speaker="Elder", text="Will you help?")
tree.add_choice("start", "quest", "Yes!")
tree.add_choice("start", "decline", "No.")
tree.add_node("decline", speaker="Elder", text="Very well...")

box = DialogueBox(tree)
box.start("start")

# Game loop
box.update(dt)
box.handle_event(event)
box.draw(screen)

16. AI Behavior Tree

from pygameP import BehaviorTree, Selector, Sequence, Action, Condition

patrol = Action("patrol", lambda bt: bt.blackboard["move_patrol"]())
chase = Action("chase", lambda bt: bt.blackboard["move_chase"]())
attack = Action("attack", lambda bt: bt.blackboard["do_attack"]())
visible = Condition("visible?", lambda bt: bt.blackboard["can_see_player"]())
close = Condition("close?", lambda bt: bt.blackboard["dist"]() < 50)

root = Selector("root", [
    Sequence("engage", [visible, Selector("combat", [
        Sequence("fight", [close, attack]),
        chase
    ])]),
    patrol
])

tree = BehaviorTree(root)
tree.tick(dt)

17. Inventory System

from pygameP import Inventory, Item

inv = Inventory(capacity=20)
potion = Item("potion", "Health Potion", stackable=True, max_stack=10)
sword = Item("sword", "Iron Sword", value=100)

inv.register_item(potion)
inv.add(potion, quantity=5)
inv.add(sword)
inv.use_item(0, player)
inv.has("potion", 3)  # True

18. Quest System

from pygameP import QuestManager, Quest, QuestObjective

qm = QuestManager()
quest = Quest("slay", "Slay the Dragon", "Defeat 3 dragons")
quest.add_objective(QuestObjective("kill", "Kill dragons", required=3))
quest.add_reward("gold", 500)
qm.add_quest(quest)
qm.accept("slay")
qm.progress("slay", "kill", amount=1)

19. Achievement System

from pygameP import AchievementManager, Achievement

am = AchievementManager()
am.add(Achievement("first_kill", "First Blood", "Defeat your first enemy"))
am.add(Achievement("explorer", "Explorer", "Visit all areas", progress_max=10))

am.unlock("first_kill")
am.progress("explorer", amount=1)

20. Theme System

from pygameP import ThemeManager, Theme, DARK_THEME, LIGHT_THEME, RETRO_THEME

ThemeManager.set_theme(DARK_THEME)
ThemeManager.switch("retro")

# Custom theme
custom = Theme("neon", {"accent": (0, 255, 200), "bg": (10, 10, 20)})
ThemeManager.register(custom)
ThemeManager.switch("neon")

21. ScrollView, TabPanel, Tooltip, ContextMenu

from pygameP import ScrollView, TabPanel, Tooltip, ContextMenu

scroll = ScrollView(x=50, y=50, width=300, height=400)
scroll.add_content(widget)

tabs = TabPanel(x=50, y=50, width=400, height=300)
tabs.add_tab("Settings", widgets)
tabs.add_tab("Items", item_widgets)

tooltip = Tooltip("Hover text")
tooltip.attach(widget.rect)

ctx = ContextMenu()
ctx.add_item("Copy", on_copy)
ctx.add_item("Delete", on_delete)
ctx.show(mouse_x, mouse_y)

22. SpriteAtlas & NineSlice

from pygameP import SpriteAtlas, NineSlice

atlas = SpriteAtlas("assets/atlas.png", tile_size=32)
frame = atlas.get_frame(0)
atlas.define_region("sword", 64, 0, 32, 32)

panel = NineSlice("assets/panel.png", border=12)
panel.draw(screen, pygame.Rect(50, 50, 300, 200))

23. RenderTarget & Post-Processing

from pygameP import RenderTarget, PostProcessChain, Bloom, Vignette

rt = RenderTarget(800, 600)
rt.begin()
# draw scene...
rt.end()

chain = PostProcessChain()
chain.add(Bloom(threshold=0.8, intensity=0.5))
chain.add(Vignette(intensity=0.3))
result = chain.process(rt.surface)
screen.blit(result, (0, 0))

24. In-Game Console

from pygameP import GameConsole, Screenshot

console = GameConsole()
console.register("spawn", lambda args: spawn(args[0]))
console.register("godmode", lambda args: setattr(player, 'invincible', True))

# Toggle with backtick
console.handle_event(event)
console.update(dt)
console.draw(screen)

Screenshot.capture(screen)  # saves to screenshots/

25. Virtual Joystick

from pygameP import VirtualJoystick

joy = VirtualJoystick(x=120, y=480, radius=60)
joy.handle_event(event)
dx, dy = joy.get_direction()  # -1.0 to 1.0
joy.draw(screen)

26. Audio Effects & Positional Audio

from pygameP import AudioFX, PositionalAudio

fx = AudioFX()
reverb_snd = fx.reverb(original, decay=0.5)
echo_snd = fx.echo(original, repeats=3, delay=0.3)
fast_snd = fx.pitch_shift(original, factor=1.5)

pos = PositionalAudio(listener_x=400, listener_y=300)
pos.add_source("torch", x=200, y=150, sound=snd, max_dist=300)
pos.update(player.x, player.y)  # auto volume + stereo pan

27. Script Engine (File + Inline Code)

# In .pgstage JSON:
{
  "script": {
    "start": "scripts/init.py",
    "update": {"code": "entity.x += 100 * dt"},
    "event": {"file": "scripts/events.py"}
  }
}

28. CLI Commands

pygameP new MyGame        # Create project
pygameP run               # Run game
pygameP build             # Build exe
pygameP pgstage scene.pgstage  # Open in editor
pygameP info              # Project stats
pygameP validate          # Validate .pgstage files
pygameP lint              # Check script references
pygameP export            # Export as zip
pygameP merge a.pgstage b.pgstage -o out.pgstage
pygameP diff a.pgstage b.pgstage
pygameP deps --install    # Install missing deps
pygameP test              # Run tests
pygameP clean             # Clean build artifacts
pygameP serve             # Dev server with hot reload
pygameP watch             # Monitor file changes

29. Window Management (v1.0.17)

Advanced window management with adaptive scaling — content is always drawn at a fixed base resolution and automatically scaled to fit the window while maintaining aspect ratio (letterbox/pillarbox).

from pygameP import Window

# Resizable window — content scales proportionally
win = Window("My Game", 800, 600, resizable=True)

# Custom titlebar with built-in fullscreen/minimize/close buttons
win = Window("My Game", 800, 600, custom_titlebar=True)

# Custom base resolution (game logic uses this size, window can be any size)
win = Window("My Game", 1280, 720, base_resolution=(1280, 720))

while running:
    events = win.handle_events()
    for event in events:
        if event.type == pygame.QUIT:
            running = False

    # Always draw at base resolution
    surface = win.get_surface()  # always 800x600 (or base_resolution)
    surface.fill((30, 30, 30))
    # ... draw your game ...

    # flip() auto-scales to window with letterbox/pillarbox
    win.flip()

Adaptive Scaling:

The window always provides a drawing surface at base_resolution (defaults to initial size). When the user resizes the window or enters fullscreen, flip() automatically:

  1. Computes the largest scale that fits the content while preserving aspect ratio
  2. Centres the scaled content in the window
  3. Fills leftover space with black bars (letterbox/pillarbox)
# Convert mouse coordinates from window pixels to base coordinates
mx, my = pygame.mouse.get_pos()
bx, by = win.screen_to_base(mx, my)  # use these for game logic

# Properties
print(win.scale)    # e.g. 1.5 = content displayed at 150%
print(win.offset)   # (x, y) letterbox offset in window pixels
print(win.base_width, win.base_height)  # logical resolution

Window Modes:

Mode Description
resizable=True Standard resizable window with maximize button enabled
custom_titlebar=True Custom titlebar with minimize, fullscreen, close buttons

Features:

  • Maximize button: Automatically enabled on Windows when resizable=True
  • Fullscreen toggle: Press F11 (configurable) or click overlay button
  • Overlay fullscreen button: add_fullscreen_button() adds a clickable button at any corner
  • ESC exits fullscreen: When in fullscreen, ESC returns to windowed mode
  • Adaptive scaling: Content never stretches — always proportional
  • Coordinate mapping: screen_to_base() / base_to_screen() for mouse conversion
  • Resize callbacks: win.on_resize(callback) for state tracking

Overlay Fullscreen Button:

# Add a fullscreen button at the top-right corner
win.add_fullscreen_button(corner="top-right", size=36, margin=10)

# Other corners: "top-left", "bottom-left", "bottom-right"
win.add_fullscreen_button(corner="bottom-right", size=40, margin=12)

# Custom colors
win.add_fullscreen_button(
    corner="top-left", size=32,
    color=(80, 80, 90),          # background
    hover_color=(120, 120, 130), # when mouse hovers
    icon_color=(255, 255, 255),  # icon
    alpha=180,                   # transparency
)

# Remove the button
win.remove_fullscreen_button()

The button is drawn as an overlay on the game content during flip() and automatically handles clicks to toggle fullscreen. The icon changes between expand (windowed) and shrink (fullscreen) states.

API:

  • win.get_surface() — Drawing surface (always base_resolution)
  • win.toggle_fullscreen() — Toggle fullscreen mode
  • win.add_fullscreen_button(corner, size, ...) — Add overlay fullscreen button
  • win.remove_fullscreen_button() — Remove overlay button
  • win.handle_events() — Process events (returns unhandled events)
  • win.flip() / win.update() — Scale + update display
  • win.screen_to_base(x, y) — Convert window coords to game coords
  • win.base_to_screen(x, y) — Convert game coords to window coords
  • win.scale — Current display scale factor
  • win.offset — Letterbox/pillarbox offset (x, y)
  • win.width, win.height — Current window pixel dimensions
  • win.base_width, win.base_height — Logical (base) dimensions

License

MIT License

Release files for pygameP 1.0.18

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

Source distribution (sdist)

Source distribution for pygameP 1.0.18
File Size Uploaded
pygamep-1.0.18.tar.gz 174.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pygameP 1.0.18
File Interpreter ABI Platform
pygamep-1.0.18-py3-none-any.whl Python 3 none any Details

Total release size: 318.3 kB

Release files / pygamep-1.0.18.tar.gz

Download URL pygamep-1.0.18.tar.gz
Size 174.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f0d78ec0108f03b95dff268e172909efdc4c8e938bed35d374b837ec52c3b060
BLAKE2b-256 checksum
How to use checksums
1c9e088a7c6bb6091a3f66b8b04f3b4b7f34ff4e5f09478a3640142f380d9064
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.8

Release files / pygamep-1.0.18-py3-none-any.whl

Download URL pygamep-1.0.18-py3-none-any.whl
Size 144.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
705983c7945203597804b92227d0af9824c653606546fdfb9539574ef4b28e38
BLAKE2b-256 checksum
How to use checksums
58ae6ca4a29b3bbb6a9f272985ffa2e57da0f314694e0393b3b3d8c27912af05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.8

Release history Release notifications | RSS feed

This release

1.0.18 This release

2 release files

1.0.17

2 release files

1.0.14

2 release files

1.0.13

2 release files

1.0.12

2 release files

1.0.11

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

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