Metadata-Version: 2.4
Name: pygameP
Version: 0.0.2
Summary: Pygame Plus — advanced game framework: shaders, physics, scene management, input
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"
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 |

---

## 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 **[Objector Coder](https://tomlct2015.github.io/Objector-Coder/#download)**, a visual scene editor. The pygameP library only loads and runs scenes.
> Download: https://tomlct2015.github.io/Objector-Coder/#download

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

---

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

---

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