0
# Sprites and Game Objects
1
2
High-level game object management with sprite classes, collision detection, and group operations. Provides organized structure for managing game entities and their interactions.
3
4
## Capabilities
5
6
### Sprite Base Class
7
8
The Sprite class provides the foundation for all visible game objects.
9
10
```python { .api }
11
class Sprite:
12
def __init__(self):
13
"""
14
Initialize sprite object.
15
Must set image and rect attributes after creation.
16
"""
17
18
image: Surface # Sprite's visual representation
19
rect: Rect # Sprite's position and collision bounds
20
21
def update(self, *args, **kwargs) -> None:
22
"""
23
Update sprite state.
24
Override this method in subclasses for custom behavior.
25
26
Parameters:
27
*args: Variable arguments passed from group update
28
**kwargs: Keyword arguments passed from group update
29
"""
30
31
def add(self, *groups: Group) -> None:
32
"""
33
Add sprite to groups.
34
35
Parameters:
36
*groups: Groups to add sprite to
37
"""
38
39
def remove(self, *groups: Group) -> None:
40
"""
41
Remove sprite from groups.
42
43
Parameters:
44
*groups: Groups to remove sprite from
45
"""
46
47
def kill(self) -> None:
48
"""Remove sprite from all groups."""
49
50
def alive(self) -> bool:
51
"""
52
Check if sprite belongs to any groups.
53
54
Returns:
55
bool: True if sprite is in at least one group
56
"""
57
58
def groups(self) -> list[Group]:
59
"""
60
Get list of groups containing this sprite.
61
62
Returns:
63
list[Group]: Groups this sprite belongs to
64
"""
65
```
66
67
### Dirty Sprite
68
69
Enhanced sprite with dirty rectangle tracking for optimized rendering.
70
71
```python { .api }
72
class DirtySprite(Sprite):
73
def __init__(self):
74
"""Initialize dirty sprite with additional attributes."""
75
76
dirty: int = 1 # Dirty flag (0=clean, 1=dirty, 2=always dirty)
77
source_rect: Rect | None = None # Source rectangle for partial image blitting
78
visible: int = 1 # Visibility flag (0=invisible, 1=visible)
79
blendmode: int = 0 # Blend mode for rendering
80
```
81
82
### Group Management
83
84
Groups provide containers for managing collections of sprites.
85
86
```python { .api }
87
class Group:
88
def __init__(self, *sprites: Sprite):
89
"""
90
Initialize group with optional sprites.
91
92
Parameters:
93
*sprites: Initial sprites to add to group
94
"""
95
96
def sprites(self) -> list[Sprite]:
97
"""
98
Get list of sprites in group.
99
100
Returns:
101
list[Sprite]: Sprites in this group
102
"""
103
104
def copy(self) -> Group:
105
"""
106
Create copy of group.
107
108
Returns:
109
Group: New group with same sprites
110
"""
111
112
def add(self, *sprites: Sprite) -> None:
113
"""
114
Add sprites to group.
115
116
Parameters:
117
*sprites: Sprites to add
118
"""
119
120
def remove(self, *sprites: Sprite) -> None:
121
"""
122
Remove sprites from group.
123
124
Parameters:
125
*sprites: Sprites to remove
126
"""
127
128
def has(self, *sprites: Sprite) -> bool:
129
"""
130
Check if group contains sprites.
131
132
Parameters:
133
*sprites: Sprites to check for
134
135
Returns:
136
bool: True if all sprites are in group
137
"""
138
139
def update(self, *args, **kwargs) -> None:
140
"""
141
Call update() on all sprites in group.
142
143
Parameters:
144
*args: Arguments to pass to sprite update methods
145
**kwargs: Keyword arguments to pass to sprite update methods
146
"""
147
148
def draw(self, surface: Surface) -> list[Rect]:
149
"""
150
Draw all sprites to surface.
151
152
Parameters:
153
surface: Surface to draw sprites on
154
155
Returns:
156
list[Rect]: List of rectangles that were drawn
157
"""
158
159
def clear(self, surface: Surface, background: Surface | Color) -> list[Rect]:
160
"""
161
Clear sprites from surface using background.
162
163
Parameters:
164
surface: Surface to clear sprites from
165
background: Background to use for clearing
166
167
Returns:
168
list[Rect]: List of rectangles that were cleared
169
"""
170
171
def empty(self) -> None:
172
"""Remove all sprites from group."""
173
174
def __len__(self) -> int:
175
"""Get number of sprites in group."""
176
177
def __iter__(self):
178
"""Iterate over sprites in group."""
179
180
def __contains__(self, sprite: Sprite) -> bool:
181
"""Check if sprite is in group."""
182
```
183
184
### Specialized Groups
185
186
Enhanced group classes with additional functionality.
187
188
```python { .api }
189
class RenderUpdates(Group):
190
"""Group that tracks dirty rectangles for efficient rendering."""
191
192
def draw(self, surface: Surface) -> list[Rect]:
193
"""
194
Draw sprites and return changed rectangles.
195
196
Parameters:
197
surface: Surface to draw on
198
199
Returns:
200
list[Rect]: Rectangles that changed
201
"""
202
203
class OrderedUpdates(RenderUpdates):
204
"""RenderUpdates that maintains sprite order."""
205
206
def add(self, *sprites: Sprite) -> None:
207
"""Add sprites maintaining order."""
208
209
def remove(self, *sprites: Sprite) -> None:
210
"""Remove sprites maintaining order."""
211
212
class LayeredUpdates(Group):
213
"""Group with layered rendering support."""
214
215
def add(self, *sprites: Sprite, **kwargs) -> None:
216
"""
217
Add sprites to specific layer.
218
219
Parameters:
220
*sprites: Sprites to add
221
**kwargs: Can include 'layer' parameter
222
"""
223
224
def change_layer(self, sprite: Sprite, new_layer: int) -> None:
225
"""
226
Move sprite to different layer.
227
228
Parameters:
229
sprite: Sprite to move
230
new_layer: Target layer
231
"""
232
233
def get_layer_of_sprite(self, sprite: Sprite) -> int:
234
"""
235
Get layer of sprite.
236
237
Parameters:
238
sprite: Sprite to check
239
240
Returns:
241
int: Layer number
242
"""
243
244
def get_top_layer(self) -> int:
245
"""Get highest layer number."""
246
247
def get_bottom_layer(self) -> int:
248
"""Get lowest layer number."""
249
250
def move_to_front(self, sprite: Sprite) -> None:
251
"""Move sprite to front (highest layer)."""
252
253
def move_to_back(self, sprite: Sprite) -> None:
254
"""Move sprite to back (lowest layer)."""
255
256
def get_top_sprite(self) -> Sprite | None:
257
"""Get topmost sprite."""
258
259
def get_sprites_at(self, pos: tuple[int, int]) -> list[Sprite]:
260
"""
261
Get sprites at position.
262
263
Parameters:
264
pos: (x, y) position to check
265
266
Returns:
267
list[Sprite]: Sprites at position, ordered by layer
268
"""
269
270
def layers(self) -> list[int]:
271
"""Get list of all layers."""
272
273
def draw(self, surface: Surface) -> list[Rect]:
274
"""Draw sprites in layer order."""
275
276
class LayeredDirty(LayeredUpdates):
277
"""LayeredUpdates with dirty rectangle optimization."""
278
279
def draw(self, surface: Surface, bgd: Surface | None = None) -> list[Rect]:
280
"""
281
Draw only dirty sprites.
282
283
Parameters:
284
surface: Surface to draw on
285
bgd: Background surface for clearing
286
287
Returns:
288
list[Rect]: Changed rectangles
289
"""
290
291
def clear(self, surface: Surface, bgd: Surface) -> None:
292
"""Clear dirty sprites using background."""
293
294
def repaint_rect(self, screen_rect: Rect) -> None:
295
"""Mark screen area as needing repaint."""
296
297
def set_clip(self, screen_rect: Rect | None = None) -> None:
298
"""Set clipping rectangle for drawing."""
299
300
class GroupSingle(Group):
301
"""Group that contains only one sprite."""
302
303
sprite: Sprite | None # The single sprite in the group
304
305
def __init__(self, sprite: Sprite | None = None):
306
"""
307
Initialize single sprite group.
308
309
Parameters:
310
sprite: Initial sprite (optional)
311
"""
312
```
313
314
### Collision Detection
315
316
Functions for detecting collisions between sprites and groups.
317
318
```python { .api }
319
def spritecollide(sprite: Sprite, group: Group, dokill: bool, collided: callable | None = None) -> list[Sprite]:
320
"""
321
Find sprites in group that collide with sprite.
322
323
Parameters:
324
sprite: Sprite to test collision against
325
group: Group of sprites to check
326
dokill: If True, remove colliding sprites from group
327
collided: Custom collision function (default uses rect collision)
328
329
Returns:
330
list[Sprite]: Sprites that collided
331
"""
332
333
def groupcollide(group1: Group, group2: Group, dokill1: bool, dokill2: bool, collided: callable | None = None) -> dict[Sprite, list[Sprite]]:
334
"""
335
Find collisions between two groups.
336
337
Parameters:
338
group1: First group
339
group2: Second group
340
dokill1: Remove colliding sprites from group1
341
dokill2: Remove colliding sprites from group2
342
collided: Custom collision function
343
344
Returns:
345
dict[Sprite, list[Sprite]]: Mapping of group1 sprites to colliding group2 sprites
346
"""
347
348
def spritecollideany(sprite: Sprite, group: Group, collided: callable | None = None) -> Sprite | None:
349
"""
350
Check if sprite collides with any sprite in group.
351
352
Parameters:
353
sprite: Sprite to test
354
group: Group to check against
355
collided: Custom collision function
356
357
Returns:
358
Sprite | None: First colliding sprite or None
359
"""
360
```
361
362
### Collision Callbacks
363
364
Built-in collision detection functions.
365
366
```python { .api }
367
def collide_rect(left: Sprite, right: Sprite) -> bool:
368
"""
369
Rectangle collision detection.
370
371
Parameters:
372
left: First sprite
373
right: Second sprite
374
375
Returns:
376
bool: True if rectangles overlap
377
"""
378
379
def collide_rect_ratio(ratio: float) -> callable:
380
"""
381
Create rectangle collision function with size ratio.
382
383
Parameters:
384
ratio: Size ratio (1.0 = normal, 0.5 = half size, 2.0 = double size)
385
386
Returns:
387
callable: Collision function
388
"""
389
390
def collide_circle(left: Sprite, right: Sprite) -> bool:
391
"""
392
Circle collision detection using sprite radius.
393
394
Parameters:
395
left: First sprite (must have radius attribute)
396
right: Second sprite (must have radius attribute)
397
398
Returns:
399
bool: True if circles overlap
400
"""
401
402
def collide_circle_ratio(ratio: float) -> callable:
403
"""
404
Create circle collision function with radius ratio.
405
406
Parameters:
407
ratio: Radius ratio
408
409
Returns:
410
callable: Collision function
411
"""
412
413
def collide_mask(left: Sprite, right: Sprite) -> tuple[int, int] | None:
414
"""
415
Pixel-perfect collision using sprite masks.
416
417
Parameters:
418
left: First sprite (must have mask attribute)
419
right: Second sprite (must have mask attribute)
420
421
Returns:
422
tuple[int, int] | None: Collision point or None
423
"""
424
```
425
426
## Usage Examples
427
428
### Basic Sprite Usage
429
430
```python
431
import pygame
432
433
class Player(pygame.sprite.Sprite):
434
def __init__(self):
435
super().__init__()
436
self.image = pygame.Surface((50, 50))
437
self.image.fill((0, 128, 255)) # Blue
438
self.rect = self.image.get_rect()
439
self.rect.center = (400, 300)
440
self.speed = 5
441
442
def update(self):
443
# Handle input
444
keys = pygame.key.get_pressed()
445
if keys[pygame.K_LEFT]:
446
self.rect.x -= self.speed
447
if keys[pygame.K_RIGHT]:
448
self.rect.x += self.speed
449
if keys[pygame.K_UP]:
450
self.rect.y -= self.speed
451
if keys[pygame.K_DOWN]:
452
self.rect.y += self.speed
453
454
class Enemy(pygame.sprite.Sprite):
455
def __init__(self, x, y):
456
super().__init__()
457
self.image = pygame.Surface((30, 30))
458
self.image.fill((255, 0, 0)) # Red
459
self.rect = self.image.get_rect()
460
self.rect.center = (x, y)
461
self.speed = 2
462
463
def update(self):
464
# Simple movement
465
self.rect.x += self.speed
466
if self.rect.right > 800 or self.rect.left < 0:
467
self.speed = -self.speed
468
469
# Create sprites
470
player = Player()
471
enemy1 = Enemy(100, 100)
472
enemy2 = Enemy(200, 200)
473
474
# Create groups
475
all_sprites = pygame.sprite.Group()
476
enemies = pygame.sprite.Group()
477
478
all_sprites.add(player, enemy1, enemy2)
479
enemies.add(enemy1, enemy2)
480
481
# Game loop
482
pygame.init()
483
screen = pygame.display.set_mode((800, 600))
484
clock = pygame.time.Clock()
485
running = True
486
487
while running:
488
for event in pygame.event.get():
489
if event.type == pygame.QUIT:
490
running = False
491
492
# Update all sprites
493
all_sprites.update()
494
495
# Check collisions
496
hits = pygame.sprite.spritecollide(player, enemies, False)
497
if hits:
498
print("Player hit enemy!")
499
500
# Draw everything
501
screen.fill((0, 0, 0))
502
all_sprites.draw(screen)
503
pygame.display.flip()
504
clock.tick(60)
505
506
pygame.quit()
507
```
508
509
### Layered Sprites
510
511
```python
512
import pygame
513
514
class Background(pygame.sprite.Sprite):
515
def __init__(self):
516
super().__init__()
517
self.image = pygame.Surface((800, 600))
518
self.image.fill((0, 100, 0)) # Green background
519
self.rect = self.image.get_rect()
520
521
class Player(pygame.sprite.Sprite):
522
def __init__(self):
523
super().__init__()
524
self.image = pygame.Surface((50, 50))
525
self.image.fill((0, 0, 255)) # Blue player
526
self.rect = self.image.get_rect(center=(400, 300))
527
528
class Overlay(pygame.sprite.Sprite):
529
def __init__(self):
530
super().__init__()
531
self.image = pygame.Surface((200, 100))
532
self.image.fill((255, 255, 0)) # Yellow overlay
533
self.image.set_alpha(128) # Semi-transparent
534
self.rect = self.image.get_rect(topleft=(10, 10))
535
536
# Create layered group
537
layered_group = pygame.sprite.LayeredUpdates()
538
539
# Add sprites to different layers
540
background = Background()
541
player = Player()
542
overlay = Overlay()
543
544
layered_group.add(background, layer=0) # Background layer
545
layered_group.add(player, layer=1) # Player layer
546
layered_group.add(overlay, layer=2) # UI layer
547
548
# Game loop
549
pygame.init()
550
screen = pygame.display.set_mode((800, 600))
551
clock = pygame.time.Clock()
552
running = True
553
554
while running:
555
for event in pygame.event.get():
556
if event.type == pygame.QUIT:
557
running = False
558
559
# Update and draw in layer order
560
layered_group.update()
561
layered_group.draw(screen)
562
563
pygame.display.flip()
564
clock.tick(60)
565
566
pygame.quit()
567
```
568
569
### Advanced Collision Detection
570
571
```python
572
import pygame
573
import math
574
575
class CircleSprite(pygame.sprite.Sprite):
576
def __init__(self, x, y, radius, color):
577
super().__init__()
578
self.radius = radius
579
# Create circular image
580
self.image = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
581
pygame.draw.circle(self.image, color, (radius, radius), radius)
582
self.rect = self.image.get_rect(center=(x, y))
583
584
class Bullet(pygame.sprite.Sprite):
585
def __init__(self, x, y):
586
super().__init__()
587
self.image = pygame.Surface((10, 20))
588
self.image.fill((255, 255, 0)) # Yellow bullet
589
self.rect = self.image.get_rect(center=(x, y))
590
self.speed = 10
591
592
def update(self):
593
self.rect.y -= self.speed
594
if self.rect.bottom < 0:
595
self.kill()
596
597
# Create sprites
598
player = CircleSprite(400, 500, 25, (0, 0, 255))
599
enemies = pygame.sprite.Group()
600
bullets = pygame.sprite.Group()
601
all_sprites = pygame.sprite.Group()
602
603
# Add enemies
604
for i in range(5):
605
enemy = CircleSprite(100 + i * 150, 100, 30, (255, 0, 0))
606
enemies.add(enemy)
607
all_sprites.add(enemy)
608
609
all_sprites.add(player)
610
611
pygame.init()
612
screen = pygame.display.set_mode((800, 600))
613
clock = pygame.time.Clock()
614
running = True
615
616
while running:
617
for event in pygame.event.get():
618
if event.type == pygame.QUIT:
619
running = False
620
elif event.type == pygame.KEYDOWN:
621
if event.key == pygame.K_SPACE:
622
# Shoot bullet
623
bullet = Bullet(player.rect.centerx, player.rect.top)
624
bullets.add(bullet)
625
all_sprites.add(bullet)
626
627
# Update sprites
628
all_sprites.update()
629
630
# Collision detection with custom callback
631
hits = pygame.sprite.groupcollide(
632
bullets, enemies, True, True,
633
pygame.sprite.collide_circle
634
)
635
636
if hits:
637
print(f"Hit {len(hits)} enemies!")
638
639
# Draw everything
640
screen.fill((0, 0, 0))
641
all_sprites.draw(screen)
642
pygame.display.flip()
643
clock.tick(60)
644
645
pygame.quit()
646
```
647
648
### Optimized Rendering with Dirty Sprites
649
650
```python
651
import pygame
652
653
class MovingSprite(pygame.sprite.DirtySprite):
654
def __init__(self, x, y):
655
super().__init__()
656
self.image = pygame.Surface((50, 50))
657
self.image.fill((255, 0, 0))
658
self.rect = self.image.get_rect(center=(x, y))
659
self.speed_x = 3
660
self.speed_y = 2
661
self.dirty = 1 # Mark as dirty initially
662
663
def update(self):
664
old_rect = self.rect.copy()
665
666
# Move sprite
667
self.rect.x += self.speed_x
668
self.rect.y += self.speed_y
669
670
# Bounce off edges
671
if self.rect.left < 0 or self.rect.right > 800:
672
self.speed_x = -self.speed_x
673
if self.rect.top < 0 or self.rect.bottom > 600:
674
self.speed_y = -self.speed_y
675
676
# Mark as dirty if moved
677
if self.rect != old_rect:
678
self.dirty = 1
679
680
# Create dirty group for optimized rendering
681
dirty_group = pygame.sprite.LayeredDirty()
682
683
# Add moving sprites
684
for i in range(10):
685
sprite = MovingSprite(i * 70 + 50, i * 50 + 50)
686
dirty_group.add(sprite)
687
688
pygame.init()
689
screen = pygame.display.set_mode((800, 600))
690
background = pygame.Surface((800, 600))
691
background.fill((0, 0, 100)) # Dark blue background
692
693
clock = pygame.time.Clock()
694
running = True
695
696
while running:
697
for event in pygame.event.get():
698
if event.type == pygame.QUIT:
699
running = False
700
701
# Update sprites
702
dirty_group.update()
703
704
# Only redraw dirty areas
705
dirty_rects = dirty_group.draw(screen, background)
706
707
# Update only changed areas
708
pygame.display.update(dirty_rects)
709
clock.tick(60)
710
711
pygame.quit()
712
```