CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyglet

Cross-platform windowing and multimedia library for Python with OpenGL graphics, event handling, and audio/video playback

Overview
Eval results
Files

sprites-shapes.mddocs/

2D Sprites and Shapes

Efficient 2D sprite rendering and geometric shape primitives.

When to Use This Module

  • Rendering 2D game sprites
  • Drawing UI elements
  • Creating particles and effects
  • Rendering geometric shapes (circles, rectangles, lines)
  • Always use with Batch for performance (10-100x faster)

Quick Decision Guide

What to render?
├─ Image/texture → pyglet.sprite.Sprite
├─ Basic shape   → pyglet.shapes.*
├─ Multiple objects → Always use Batch!
└─ Layering needed → Use Groups (order parameter)

Sprites - Quick Reference

# Create sprite
image = pyglet.image.load('player.png')
sprite = pyglet.sprite.Sprite(image, x=100, y=100)

# With batching (REQUIRED for multiple sprites)
batch = pyglet.graphics.Batch()
sprite = pyglet.sprite.Sprite(image, x=100, y=100, batch=batch)

# Transform
sprite.x, sprite.y = 200, 300  # Position
sprite.rotation = 45  # Degrees (clockwise)
sprite.scale = 2.0  # Uniform scale
sprite.opacity = 128  # 0-255
sprite.color = (255, 0, 0)  # RGB tint

# Draw
batch.draw()  # Draws all batched sprites

Shapes - Quick Reference

from pyglet import shapes

batch = pyglet.graphics.Batch()

# Common shapes
circle = shapes.Circle(x=100, y=100, radius=50, color=(255,0,0), batch=batch)
rect = shapes.Rectangle(x=200, y=50, width=100, height=100, color=(0,255,0), batch=batch)
line = shapes.Line(x=0, y=0, x2=100, y2=100, width=3, color=(0,0,255), batch=batch)

# Draw all
batch.draw()

Sprite Class

class pyglet.sprite.Sprite:
    __init__(img, x=0, y=0, z=0, batch=None, group=None,
             blend_src=GL_SRC_ALPHA, blend_dest=GL_ONE_MINUS_SRC_ALPHA)

    # Transform
    x, y, z: float
    position: tuple  # (x, y, z)
    rotation: float  # Degrees clockwise
    scale: float  # Uniform scale
    scale_x, scale_y: float  # Non-uniform
    width, height: int  # Adjusts scale when set

    # Appearance
    opacity: int  # 0-255
    color: tuple  # RGB (0-255) multiplier
    visible: bool
    image: AbstractImage

    # Rendering
    batch: Batch
    group: Group

    # Animation (if image is Animation)
    paused: bool
    frame_index: int

    # Methods
    def draw()  # DON'T USE IN PRODUCTION (use batch instead)
    def delete()  # Remove from memory

    # Events (for animations)
    @sprite.event
    def on_animation_end(): ...

Batching & Groups (CRITICAL FOR PERFORMANCE)

The Batch Pattern (Required for Multiple Objects)

# WRONG: Very slow for multiple sprites
sprite1 = pyglet.sprite.Sprite(img, x=0, y=0)
sprite2 = pyglet.sprite.Sprite(img, x=100, y=100)

@window.event
def on_draw():
    window.clear()
    sprite1.draw()  # State change
    sprite2.draw()  # State change
    # 100 sprites = 100 state changes = SLOW

# CORRECT: Fast batch rendering
batch = pyglet.graphics.Batch()
sprite1 = pyglet.sprite.Sprite(img, x=0, y=0, batch=batch)
sprite2 = pyglet.sprite.Sprite(img, x=100, y=100, batch=batch)

@window.event
def on_draw():
    window.clear()
    batch.draw()  # One call = 10-100x faster!

Layering with Groups

batch = pyglet.graphics.Batch()

# Define layers (lower order drawn first)
background = pyglet.graphics.Group(order=0)
midground = pyglet.graphics.Group(order=1)
foreground = pyglet.graphics.Group(order=2)
ui = pyglet.graphics.Group(order=3)

# Assign sprites to layers
bg_sprite = pyglet.sprite.Sprite(bg_img, batch=batch, group=background)
player = pyglet.sprite.Sprite(player_img, batch=batch, group=midground)
effects = pyglet.sprite.Sprite(effect_img, batch=batch, group=foreground)
button = pyglet.sprite.Sprite(btn_img, batch=batch, group=ui)

# All drawn in correct order automatically
batch.draw()

Shape Classes

All shapes support: x, y, position, rotation, color, opacity, visible, batch, group

Basic Shapes

from pyglet import shapes

# Rectangle
Rectangle(x, y, width, height, color=(255,255,255,255), batch=None, group=None)

# Circle
Circle(x, y, radius, segments=None, color=(255,255,255,255), batch=None, group=None)

# Line
Line(x, y, x2, y2, thickness=1.0, color=(255,255,255,255), batch=None, group=None)

# Triangle
Triangle(x, y, x2, y2, x3, y3, color=(255,255,255,255), batch=None, group=None)

Styled Shapes

# Bordered rectangle
BorderedRectangle(x, y, width, height, border=1,
                  color=(255,255,255), border_color=(100,100,100))

# Rounded corners
RoundedRectangle(x, y, width, height, radius=5, color=(255,255,255,255))

# 3D-style box
Box(x, y, width, height, thickness=10, color=(255,255,255,255))

Advanced Shapes

# Star
Star(x, y, outer_radius, inner_radius, num_spikes=5, rotation=0)

# Polygon (any number of vertices)
Polygon(*coordinates, color=(255,255,255,255))
# coordinates: [(x1,y1), (x2,y2), (x3,y3), ...]

# Ellipse
Ellipse(x, y, a, b, segments=None, color=(255,255,255,255))
# a = semi-major axis, b = semi-minor axis

# Arc (curved line)
Arc(x, y, radius, angle=360.0, start_angle=0, thickness=1.0)

# Sector (pie slice)
Sector(x, y, radius, angle=90, start_angle=0)

# Bezier curve
BezierCurve(*points, t=1.0, segments=100, thickness=1.0)
# points: must be 3n+1 points: (x1,y1), (x2,y2), ...

Essential Patterns

Sprite Pool (Performance Critical)

class SpritePool:
    """Reuse sprites instead of creating/destroying"""
    def __init__(self, image, batch, size=100):
        self.active = []
        self.inactive = [
            pyglet.sprite.Sprite(image, batch=batch, visible=False)
            for _ in range(size)
        ]

    def spawn(self, x, y):
        if not self.inactive:
            return None
        sprite = self.inactive.pop()
        sprite.position = (x, y, 0)
        sprite.visible = True
        self.active.append(sprite)
        return sprite

    def recycle(self, sprite):
        sprite.visible = False
        self.active.remove(sprite)
        self.inactive.append(sprite)

Animation

# Load animated GIF
anim = pyglet.image.load_animation('explosion.gif')
sprite = pyglet.sprite.Sprite(anim, x=100, y=100, batch=batch)

# Or create from frames
frames = [pyglet.image.load(f'frame{i}.png') for i in range(10)]
anim = pyglet.image.Animation.from_image_sequence(
    frames, duration=0.1, loop=True
)
sprite = pyglet.sprite.Sprite(anim, batch=batch)

# Control
sprite.paused = True  # Pause
sprite.frame_index = 0  # Reset

@sprite.event
def on_animation_end():
    sprite.delete()  # Remove when done

Collision Detection

# Point in shape
if (mouse_x, mouse_y) in circle:
    print("Clicked!")

# AABB (Axis-Aligned Bounding Box)
def sprites_collide(s1, s2):
    return (abs(s1.x - s2.x) < (s1.width + s2.width) / 2 and
            abs(s1.y - s2.y) < (s1.height + s2.height) / 2)

# Circular collision
def circle_collision(s1, s2, r1, r2):
    dx = s1.x - s2.x
    dy = s1.y - s2.y
    dist_sq = dx*dx + dy*dy
    return dist_sq < (r1 + r2) ** 2

Dynamic Shape Updates

shape = shapes.Circle(200, 200, 50, batch=batch)

def update(dt):
    import math
    time = pyglet.clock.get_default().time()
    # Shapes auto-update when properties change
    shape.x = 400 + 100 * math.cos(time)
    shape.y = 300 + 100 * math.sin(time)
    shape.radius = 50 + 20 * math.sin(time * 2)

Group Class

class pyglet.graphics.Group:
    __init__(order=0, parent=None)

    order: int  # Draw order (lower first)
    parent: Group | None
    visible: bool  # Toggle entire group

    def set_state()  # Apply OpenGL state
    def unset_state()  # Restore state

# Specialized groups
TextureGroup(texture, order=0, parent=None)
ShaderGroup(program, order=0, parent=None)

Batch Class

class pyglet.graphics.Batch:
    __init__()

    def draw()  # Render all batched items
    def invalidate()  # Mark as needing update

Performance Rules

DO:

  1. ✓ Always use Batch for multiple objects
  2. ✓ Use Groups to organize by texture/shader
  3. ✓ Set visible=False for offscreen objects
  4. ✓ Preallocate sprites (use pools)
  5. ✓ Use texture atlases for sprite sheets
  6. ✓ Update only when needed

DON'T:

  1. ✗ Call sprite.draw() individually (very slow!)
  2. ✗ Create/delete sprites every frame
  3. ✗ Forget to batch
  4. ✗ Modify batch during batch.draw()
  5. ✗ Create new Batch per frame

Common Mistakes

MistakeProblemSolution
Not using Batch10-100x slowerAlways use batch for multiple objects
Creating sprites per frameMemory/CPU wastePreallocate, use pools
Forgetting z-orderWrong layer orderUse Groups with order parameter
Individual .draw() callsVery slowUse batch.draw() instead
Wrong anchor pointRotation off-centerAnchor is separate from rotation center
Color as floatsWrong valuesUse 0-255 integers, not 0.0-1.0

Critical Reminders

# CORRECT: Batched rendering
batch = pyglet.graphics.Batch()
sprites = [pyglet.sprite.Sprite(img, x=i*50, batch=batch) for i in range(100)]
batch.draw()  # One call

# WRONG: Individual draws (100x slower!)
sprites = [pyglet.sprite.Sprite(img, x=i*50) for i in range(100)]
for sprite in sprites:
    sprite.draw()  # 100 state changes!

# Origin is BOTTOM-LEFT
sprite.x = 0  # Left edge
sprite.y = 0  # Bottom edge (not top!)

# Colors are 0-255, not floats
sprite.color = (255, 0, 0)  # Red (CORRECT)
# sprite.color = (1.0, 0.0, 0.0)  # WRONG!

# Rotation is around (x, y), not anchor
sprite.x, sprite.y = 400, 300  # Center of rotation
sprite.rotation = 45  # Rotates around (400, 300)

Typical Game Structure

import pyglet

window = pyglet.window.Window(800, 600)
batch = pyglet.graphics.Batch()

# Layers
bg_group = pyglet.graphics.Group(order=0)
game_group = pyglet.graphics.Group(order=1)
ui_group = pyglet.graphics.Group(order=2)

# Load resources
player_img = pyglet.resource.image('player.png')
enemy_img = pyglet.resource.image('enemy.png')

# Create sprites
player = pyglet.sprite.Sprite(player_img, x=400, y=300,
                              batch=batch, group=game_group)
enemies = [
    pyglet.sprite.Sprite(enemy_img, x=100+i*150, y=400,
                        batch=batch, group=game_group)
    for i in range(5)
]

# Update
def update(dt):
    # Sprite properties auto-update the batch
    player.x += player.velocity_x * dt
    for enemy in enemies:
        enemy.x -= 50 * dt

pyglet.clock.schedule_interval(update, 1/60)

# Render
@window.event
def on_draw():
    window.clear()
    batch.draw()  # Draws all in correct order

pyglet.app.run()

Next Steps: See images-textures.md for texture atlases, graphics-rendering.md for custom shaders.

Install with Tessl CLI

npx tessl i tessl/pypi-pyglet@2.1.1

docs

3d-models.md

app-clock.md

audio-video.md

graphics-rendering.md

gui.md

images-textures.md

index.md

input-devices.md

math.md

opengl.md

resource-management.md

sprites-shapes.md

text-rendering.md

windowing.md

tile.json