Cross-platform windowing and multimedia library for Python with OpenGL graphics, event handling, and audio/video playback
Efficient 2D sprite rendering and geometric shape primitives.
What to render?
├─ Image/texture → pyglet.sprite.Sprite
├─ Basic shape → pyglet.shapes.*
├─ Multiple objects → Always use Batch!
└─ Layering needed → Use Groups (order parameter)# 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 spritesfrom 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()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(): ...# 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!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()All shapes support: x, y, position, rotation, color, opacity, visible, batch, group
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)# 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))# 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), ...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)# 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# 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) ** 2shape = 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)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)class pyglet.graphics.Batch:
__init__()
def draw() # Render all batched items
def invalidate() # Mark as needing update| Mistake | Problem | Solution |
|---|---|---|
| Not using Batch | 10-100x slower | Always use batch for multiple objects |
| Creating sprites per frame | Memory/CPU waste | Preallocate, use pools |
| Forgetting z-order | Wrong layer order | Use Groups with order parameter |
| Individual .draw() calls | Very slow | Use batch.draw() instead |
| Wrong anchor point | Rotation off-center | Anchor is separate from rotation center |
| Color as floats | Wrong values | Use 0-255 integers, not 0.0-1.0 |
# 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)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