Metadata-Version: 2.4
Name: pygameP
Version: 0.0.10
Summary: Pygame Plus — advanced game framework: shaders, physics, scene management, input, CLI project management
Author: pygameP contributors
License: MIT
Project-URL: Homepage, https://github.com/pygameP/pygameP
Keywords: pygame,game,shader,physics,scene
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pygame>=2.0.0
Requires-Dist: PyOpenGL>=3.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Provides-Extra: web
Requires-Dist: pywebview>=4.0; extra == "web"
Provides-Extra: video
Requires-Dist: opencv-python>=4.0; extra == "video"
Provides-Extra: build
Requires-Dist: pyinstaller>=6.0; extra == "build"
Dynamic: license-file

# pygameP

**Pygame Plus** — An advanced extension framework for Pygame, providing GLSL shader effects, performance optimization tools, JSON scene management, a simple physics engine, and extended input device support.

## Installation

```bash
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 |
| **physics** | Simple rigid-body physics (gravity, collision, raycasting) |
| **input** | Unified input for keyboard, mouse, gamepad, and multi-touch |
| **async_utils** | Non-blocking async timer and loop runner for game loops |
| **assets** | Global asset loader with caching (images, sounds, fonts, data) |
| **save** | JSON-based save system and config manager |
| **camera** | 2D camera with smooth follow, screen shake, zoom, and bounds |
| **audio** | Audio manager with BGM fade, SFX pooling, and volume control |
| **gui** | GUI widgets: Button, Slider, TextInput, ProgressBar, Label |
| **debug** | Debug overlay with FPS, entity count, and hitbox rendering |
| **tiled** | Tiled Map Editor (.tmx) importer for tile maps and objects |

---

## Quick Start

### 1. GLSL Shader Effects

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

```python
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

```python
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](https://tomlct2015.github.io/pygameP/editor/pgstage-editor)**, a visual scene editor. The pygameP library only loads and runs scenes.
> Online Editor: https://tomlct2015.github.io/pygameP/editor/pgstage-editor

```json
{
  "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:

```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

```python
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

```python
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.

```python
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.

```python
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.

```python
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.

```python
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.

```python
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.

```python
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.

```python
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.

```python
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

```python
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()
```

## License

MIT License
