~ubuntu-branches/ubuntu/gutsy/pygame/gutsy

« back to all changes in this revision

Viewing changes to lib/sprite.py

  • Committer: Bazaar Package Importer
  • Author(s): Ed Boraas
  • Date: 2002-02-20 06:39:24 UTC
  • Revision ID: james.westby@ubuntu.com-20020220063924-amlzj7tqkeods4eq
Tags: upstream-1.4
ImportĀ upstreamĀ versionĀ 1.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
##    pygame - Python Game Library
 
2
##    Copyright (C) 2000-2001  Pete Shinners
 
3
##
 
4
##    This library is free software; you can redistribute it and/or
 
5
##    modify it under the terms of the GNU Library General Public
 
6
##    License as published by the Free Software Foundation; either
 
7
##    version 2 of the License, or (at your option) any later version.
 
8
##
 
9
##    This library is distributed in the hope that it will be useful,
 
10
##    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
##    Library General Public License for more details.
 
13
##
 
14
##    You should have received a copy of the GNU Library General Public
 
15
##    License along with this library; if not, write to the Free
 
16
##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
##
 
18
##    Pete Shinners
 
19
##    pete@shinners.org
 
20
 
 
21
"""
 
22
This module contains a base class for sprite objects. Also
 
23
several different group classes you can use to store and
 
24
identify the sprites. Some of the groups can be used to
 
25
draw the sprites they contain. Lastly there are a handful
 
26
of collision detection functions to help you quickly find
 
27
intersecting sprites in a group.
 
28
 
 
29
The way the groups are designed, it is very efficient at
 
30
adding and removing sprites from groups. This makes the
 
31
groups a perfect use for cataloging or tagging different
 
32
sprites. instead of keeping an identifier or type as a
 
33
member of a sprite class, just store the sprite in a
 
34
different set of groups. this ends up being a much better
 
35
way to loop through, find, and effect different sprites.
 
36
It is also a very quick to test if a sprite is contained
 
37
in a given group.
 
38
 
 
39
You can manage the relationship between groups and sprites
 
40
from both the groups and the actual sprite classes. Both
 
41
have add() and remove() functions that let you add sprites
 
42
to groups and groups to sprites. Both have initializing
 
43
functions that can accept a list of containers or sprites.
 
44
 
 
45
The methods to add and remove sprites from groups are
 
46
smart enough to not delete sprites that aren't already part
 
47
of a group, and not add sprites to a group if it already
 
48
exists. You may also pass a sequence of sprites or groups
 
49
to these functions and each one will be used.
 
50
 
 
51
The design of the sprites and groups is very flexible.
 
52
There's no need to inherit from the provided classes, you
 
53
can use any object you want for the sprites, as long as it
 
54
contains "add_internal" and "remove_internal" methods,
 
55
which are called by the groups when they remove and add
 
56
sprites. The same is true for containers. A container
 
57
can be any python object that has "add_internal" and
 
58
"remove_internal" methods that the sprites call when
 
59
they want add and remove themselves from containers. The
 
60
containers must also have a member named "_spritegroup",
 
61
which can be set to any dummy value.
 
62
"""
 
63
 
 
64
##todo
 
65
## a group that holds only the 'n' most recent elements.
 
66
## sort of like the GroupSingle class, but holding more
 
67
## than one sprite
 
68
##
 
69
## drawing groups that can 'automatically' store the area
 
70
## underneath, so the can "clear" without needing a background
 
71
## function. obviously a little slower than normal, but nice
 
72
## to use in many situations. (also remember it must "clear"
 
73
## in the reverse order that it draws :])
 
74
##
 
75
## the drawing groups should also be able to take a background
 
76
## function, instead of just a background surface. the function
 
77
## would take a surface and a rectangle on that surface to erase.
 
78
##
 
79
## perhaps more types of collision functions? the current two
 
80
## should handle just about every need, but perhaps more optimized
 
81
## specific ones that aren't quite so general but fit into common
 
82
## specialized cases.
 
83
 
 
84
 
 
85
 
 
86
class Sprite:
 
87
    """the base class for your visible game objects.
 
88
       The sprite class is meant to be used as a base class
 
89
       for the objects in your game. It just provides functions
 
90
       to maintain itself in different groups. A sprite is
 
91
       considered 'alive' as long as it is a member of one
 
92
       or more groups. The kill() method simply removes this
 
93
       sprite from all groups."""
 
94
 
 
95
    def __init__(self, group=()):
 
96
        """__init__(group=())
 
97
           initialize a sprite object
 
98
 
 
99
           You can initialize a sprite by passing it a
 
100
           group or sequence of groups to be contained in."""
 
101
        self.__g = {}
 
102
        self.add(group)
 
103
 
 
104
    def add(self, group):
 
105
        """add(group)
 
106
           add a sprite to container
 
107
 
 
108
           Add the sprite to a group or sequence of groups."""
 
109
        has = self.__g.has_key
 
110
        if hasattr(group, '_spritegroup'):
 
111
            if not has(group):
 
112
                group.add_internal(self)
 
113
                self.__g[group] = 0
 
114
        else:
 
115
            groups = group
 
116
            for group in groups:
 
117
                if not has(group):
 
118
                    group.add_internal(self)
 
119
                    self.__g[group] = 0
 
120
 
 
121
    def remove(self, group):
 
122
        """remove(group)
 
123
           remove a sprite from container
 
124
 
 
125
           Remove the sprite from a group or sequence of groups."""
 
126
        has = self.__g.has_key
 
127
        if hasattr(group, '_spritegroup'):
 
128
            if has(group):
 
129
                group.remove_internal(self)
 
130
                del self.__g[group]
 
131
        else:
 
132
            for g in group:
 
133
                if has(g):
 
134
                    g.remove_internal(self)
 
135
                    del self.__g[group]
 
136
 
 
137
    def add_internal(self, group):
 
138
        self.__g[group] = 0
 
139
 
 
140
    def remove_internal(self, group):
 
141
        del self.__g[group]
 
142
 
 
143
    def update(self, *args, **kwargs):
 
144
        #note, this just ignores all args
 
145
        pass
 
146
 
 
147
    def kill(self):
 
148
        """kill()
 
149
           end life of sprite, remove from all groups
 
150
 
 
151
           Removes the sprite from all the groups that contain
 
152
           it. The sprite is still fine after calling this kill()
 
153
           so you could use it to remove a sprite from all groups,
 
154
           and then add it to some other groups."""
 
155
        for c in self.__g.keys():
 
156
            c.remove_internal(self)
 
157
        self.__g.clear()
 
158
 
 
159
    def groups(self):
 
160
        """groups() -> list
 
161
           list used sprite containers
 
162
 
 
163
           Returns a list of all the groups that contain this
 
164
           sprite."""
 
165
        return self.__g.keys()
 
166
 
 
167
    def alive(self):
 
168
        """alive() -> bool
 
169
           ask the life of a sprite
 
170
 
 
171
           Returns true if this sprite is a member of any groups."""
 
172
        return len(self.__g)
 
173
 
 
174
    def __repr__(self):
 
175
        return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
 
176
 
 
177
 
 
178
 
 
179
class Group:
 
180
    """the Group class is a container for sprites
 
181
       This is the base sprite group class. It does everything
 
182
       needed to behave as a normal group. You can easily inherit
 
183
       a new group class from this if you want to add more features."""
 
184
    _spritegroup = 1 #dummy val to identify sprite groups
 
185
 
 
186
    def __init__(self, sprite=()):
 
187
        """__init__(sprite=())
 
188
           instance a Group
 
189
 
 
190
           You can initialize a group by passing it a
 
191
           sprite or sequence of sprites to be contained."""
 
192
        self.spritedict = {}
 
193
        if sprite:
 
194
            self.add(sprite)
 
195
 
 
196
    def copy(self):
 
197
        """copy() -> Group
 
198
           copy a group with all the same sprites
 
199
 
 
200
           Returns a copy of the group that is the same class
 
201
           type, and has the same contained sprites."""
 
202
        return self.__class__(self.spritedict.keys())
 
203
 
 
204
    def sprites(self):
 
205
        """sprites() -> iterator
 
206
           return an object to loop over each sprite
 
207
 
 
208
           Returns an object that can be looped over with
 
209
           a 'for' loop. (For now it is always a list, but
 
210
           newer version of python could return different
 
211
           objects, like iterators.)"""
 
212
        return self.spritedict.keys()
 
213
 
 
214
    def add(self, sprite):
 
215
        """add(sprite)
 
216
           add sprite to group
 
217
 
 
218
           Add a sprite or sequence of sprites to a group."""
 
219
        has = self.spritedict.has_key
 
220
        if hasattr(sprite, '_spritegroup'):
 
221
            for sprite in sprite.sprites():
 
222
                if not has(sprite):
 
223
                    self.spritedict[sprite] = 0
 
224
                    sprite.add_internal(self)
 
225
        else:
 
226
            try: len(sprite) #see if its a sequence
 
227
            except (TypeError, AttributeError):
 
228
                if not has(sprite):
 
229
                        self.spritedict[sprite] = 0
 
230
                        sprite.add_internal(self)
 
231
            else:
 
232
                for sprite in sprite:
 
233
                    if not has(sprite):
 
234
                        self.spritedict[sprite] = 0
 
235
                        sprite.add_internal(self)
 
236
 
 
237
    def remove(self, sprite):
 
238
        """remove(sprite)
 
239
           remove sprite from group
 
240
 
 
241
           Remove a sprite or sequence of sprites from a group."""
 
242
        has = self.spritedict.has_key
 
243
        if hasattr(sprite, '_spritegroup'):
 
244
            for sprite in sprite.sprites():
 
245
                if has(sprite):
 
246
                    self.remove_internal(sprite)
 
247
                    sprite.remove_internal(self)
 
248
        else:
 
249
            try: len(sprite) #see if its a sequence
 
250
            except (TypeError, AttributeError):
 
251
                if has(sprite):
 
252
                    self.remove_internal(sprite)
 
253
                    sprite.remove_internal(self)
 
254
            else:
 
255
                for sprite in sprite:
 
256
                    if has(sprite):
 
257
                        self.remove_internal(sprite)
 
258
                        sprite.remove_internal(self)
 
259
 
 
260
    def add_internal(self, sprite):
 
261
        self.spritedict[sprite] = 0
 
262
 
 
263
    def remove_internal(self, sprite):
 
264
        del self.spritedict[sprite]
 
265
 
 
266
    def has(self, sprite):
 
267
        """has(sprite) -> bool
 
268
           ask if group has sprite
 
269
 
 
270
           Returns true if the given sprite or sprites are
 
271
           contained in the group"""
 
272
        has = self.spritedict.has_key
 
273
        if hasattr(sprite, '_spritegroup'):
 
274
            for sprite in sprite.sprites():
 
275
                if not has(sprite):
 
276
                    return 0
 
277
        else:
 
278
            try: len(sprite) #see if its a sequence
 
279
            except (TypeError, AttributeError):
 
280
                return has(sprite)
 
281
            else:
 
282
                for sprite in sprite:
 
283
                    if not has(sprite):
 
284
                        return 0
 
285
        return 1
 
286
 
 
287
        if hasattr(sprite, '_spritegroup'):
 
288
            return sprite in has(sprite)
 
289
        sprites = sprite
 
290
        for sprite in sprites:
 
291
            if not has(sprite):
 
292
                return 0
 
293
        return 1
 
294
 
 
295
    def empty(self):
 
296
        """empty()
 
297
           remove all sprites
 
298
 
 
299
           Removes all the sprites from the group."""
 
300
        for s in self.spritedict.keys():
 
301
            self.remove_internal(s)
 
302
            s.remove_internal(self)
 
303
        self.spritedict.clear()
 
304
 
 
305
    def update(self, *args, **kwargs):
 
306
        """update(...)
 
307
           call update for all member sprites
 
308
 
 
309
           calls the update method for all sprites in
 
310
           the group. any arguments are passed to the update
 
311
           function."""
 
312
        a=apply
 
313
        for s in self.spritedict.keys():
 
314
            a(s.update, args, kwargs)
 
315
 
 
316
    def __nonzero__(self):
 
317
        """__nonzero__() -> bool
 
318
           ask if group is empty
 
319
 
 
320
           Returns true if the group has any sprites. This
 
321
           lets you check if the group has any sprites by
 
322
           using it in a logical if statement."""
 
323
        return len(self.spritedict)
 
324
 
 
325
    def __len__(self):
 
326
        """__len__() -> int
 
327
           number of sprites in group
 
328
 
 
329
           Returns the number of sprites contained in the group."""
 
330
        return len(self.spritedict)
 
331
 
 
332
    def __repr__(self):
 
333
        return "<%s(%d sprites)>" % (self.__class__.__name__, len(self))
 
334
 
 
335
##note that this GroupSingle class is not derived from any other
 
336
##group class, it can make as a good example if you ever want to
 
337
##create your own new group type.
 
338
 
 
339
class GroupSingle:
 
340
    """a group container that holds a single most recent item
 
341
       This class works just like a regular group, but it only
 
342
       keeps a single sprite in the group. Whatever sprite has
 
343
       been added to the group last, will be the only sprite in
 
344
       the group."""
 
345
    _spritegroup = 1 #dummy val to identify groups
 
346
 
 
347
    def __init__(self, sprite=()):
 
348
        self.sprite = 0
 
349
        self.add(sprite)
 
350
 
 
351
    def copy(self):
 
352
        if self.sprite is not 0:
 
353
            return GroupSingle(self.sprite)
 
354
        return GroupSingle()
 
355
 
 
356
    def sprites(self):
 
357
        if self.sprite is not 0:
 
358
            return [self.sprite]
 
359
        return []
 
360
 
 
361
    def add(self, sprite):
 
362
        if hasattr(sprite, '_spritegroup'):
 
363
            for sprite in sprite.sprites(): pass
 
364
        else:
 
365
            try:
 
366
                if not len(sprite): return #see if its a sequence
 
367
                sprite = sprite[-1]
 
368
            except (TypeError, AttributeError): pass
 
369
        if sprite is not self.sprite:
 
370
            self.add_internal(sprite)
 
371
            sprite.add_internal(self)
 
372
 
 
373
    def remove(self, sprite):
 
374
        if hasattr(sprite, '_spritegroup'):
 
375
            for sprite in sprite.sprites():
 
376
                if self.sprite is sprite:
 
377
                    self.sprite = 0
 
378
                    sprite.remove_internal(self)
 
379
                    break
 
380
        else:
 
381
            try:
 
382
                if not len(sprite): return #see if its a sequence
 
383
            except (TypeError, AttributeError):
 
384
                if self.sprite is sprite:
 
385
                    self.sprite = 0
 
386
                    sprite.remove_internal(self)
 
387
            else:
 
388
                for sprite in sprite:
 
389
                    if self.sprite is sprite:
 
390
                        self.sprite = 0
 
391
                        sprite.remove_internal(self)
 
392
                        break
 
393
 
 
394
    def add_internal(self, sprite):
 
395
        if self.sprite is not 0:
 
396
            self.sprite.remove_internal(self)
 
397
        self.sprite = sprite
 
398
 
 
399
    def remove_internal(self, sprite):
 
400
        self.sprite = 0
 
401
 
 
402
    def has(self, sprite):
 
403
        return self.sprite is sprite
 
404
 
 
405
    def empty(self):
 
406
        if self.sprite is not 0:
 
407
            self.sprite.remove_internal(self)
 
408
            self.sprite = 0
 
409
 
 
410
    def update(self, *args, **kwargs):
 
411
        if self.sprite:
 
412
            apply(self.sprite.update(args, kwargs))
 
413
 
 
414
    def __nonzero__(self):
 
415
        return self.sprite is not 0
 
416
 
 
417
    def __len__(self):
 
418
        return self.sprite is not 0
 
419
 
 
420
    def __repr__(self):
 
421
        return "<%s(%d sprites)>" % (self.__class__.__name__, len(self))
 
422
 
 
423
 
 
424
##these render groups are derived from the normal Group
 
425
##class, they just add methods. the Updates and Clear versions
 
426
##of drawing are more complex groups. They keep track of sprites
 
427
##that have been drawn but are removed, and also keep track of
 
428
##drawing info from each sprite, by storing it in the dictionary
 
429
##values used to store the sprite. very sneaky, but a great
 
430
##example for you if you ever need your own group class that
 
431
##maintains its own data along with each sprite it holds.
 
432
 
 
433
class RenderPlain(Group):
 
434
    """a sprite group that can draw all its sprites
 
435
       The RenderPlain group is just like a normal group,
 
436
       it just adds a "draw" method. Any sprites used with
 
437
       this group to draw must contain two member elements
 
438
       named "image" and "rect". These are a pygame Surface
 
439
       and Rect object that are passed to blit."""
 
440
 
 
441
    def draw(self, surface):
 
442
        """draw(surface)
 
443
           draw all sprites onto a surface
 
444
 
 
445
           Draws all the sprites onto the given surface."""
 
446
        spritedict = self.spritedict
 
447
        surface_blit = surface.blit
 
448
        for s in spritedict.keys():
 
449
            surface_blit(s.image, s.rect)
 
450
 
 
451
 
 
452
 
 
453
class RenderClear(Group):
 
454
    """a group container that can draw and clear its sprites
 
455
       The RenderClear group is just like a normal group,
 
456
       but it can draw and clear the sprites. Any sprites
 
457
       used in this group must contain member elements
 
458
       named "image" and "rect". These are a pygame Surface
 
459
       and Rect, which are passed to a blit call."""
 
460
    def __init__(self, sprite=()):
 
461
        Group.__init__(self, sprite)
 
462
        self.lostsprites = []
 
463
 
 
464
    def remove_internal(self, sprite):
 
465
        r = self.spritedict[sprite]
 
466
        if r is not 0:
 
467
            self.lostsprites.append(r)
 
468
        del self.spritedict[sprite]
 
469
 
 
470
    def draw(self, surface):
 
471
        """draw(surface)
 
472
           draw all sprites onto a surface
 
473
 
 
474
           Draws all the sprites onto the given surface."""
 
475
        spritedict = self.spritedict
 
476
        surface_blit = surface.blit
 
477
        for s in spritedict.keys():
 
478
            spritedict[s] = surface_blit(s.image, s.rect)
 
479
 
 
480
    def clear(self, surface, bgd):
 
481
        """clear(surface, bgd)
 
482
           erase the previous position of all sprites
 
483
 
 
484
           Clears the area of all drawn sprites. the bgd
 
485
           argument should be Surface which is the same
 
486
           dimensions as the surface."""
 
487
        surface_blit = surface.blit
 
488
        for r in self.lostsprites:
 
489
            surface_blit(bgd, r, r)
 
490
        self.lostsprites = []
 
491
        for r in self.spritedict.values():
 
492
            if r is not 0:
 
493
                surface_blit(bgd, r, r)
 
494
 
 
495
 
 
496
class RenderUpdates(RenderClear):
 
497
    """a sprite group that can draw and clear with update rectangles
 
498
       The RenderUpdates is derived from the RenderClear group
 
499
       and keeps track of all the areas drawn and cleared. It
 
500
       also smartly handles overlapping areas between where a
 
501
       sprite was drawn and cleared when generating the update
 
502
       rectangles."""
 
503
 
 
504
    def draw(self, surface):
 
505
        """draw(surface)
 
506
           draw all sprites onto the surface
 
507
 
 
508
           Draws all the sprites onto the given surface. It
 
509
           returns a list of rectangles, which should be passed
 
510
           to pygame.display.update()"""
 
511
        spritedict = self.spritedict
 
512
        surface_blit = surface.blit
 
513
        dirty = self.lostsprites
 
514
        self.lostsprites = []
 
515
        dirty_append = dirty.append
 
516
        for s, r in spritedict.items():
 
517
            newrect = surface_blit(s.image, s.rect)
 
518
            if r is 0:
 
519
                dirty_append(newrect)
 
520
            else:
 
521
                dirty_append(newrect.union(r))
 
522
            spritedict[s] = newrect
 
523
        return dirty
 
524
 
 
525
 
 
526
def spritecollide(sprite, group, dokill):
 
527
    """pygame.sprite.spritecollide(sprite, group, dokill) -> list
 
528
       collision detection between sprite and group
 
529
 
 
530
       given a sprite and a group of sprites, this will
 
531
       return a list of all the sprites that intersect
 
532
       the given sprite.
 
533
       all sprites must have a "rect" method, which is a
 
534
       rectangle of the sprite area. if the dokill argument
 
535
       is true, the sprites that do collide will be
 
536
       automatically removed from all groups."""
 
537
    spritecollide = sprite.rect.colliderect
 
538
    crashed = []
 
539
    for s in group.sprites():
 
540
        if spritecollide(s.rect):
 
541
            if dokill: s.kill()
 
542
            crashed.append(s)
 
543
    return crashed
 
544
 
 
545
 
 
546
def groupcollide(groupa, groupb, dokilla, dokillb):
 
547
    """pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb) -> dict
 
548
       collision detection between group and group
 
549
 
 
550
       given two groups, this will find the intersections
 
551
       between all sprites in each group. it returns a
 
552
       dictionary of all sprites in the first group that
 
553
       collide. the value for each item in the dictionary
 
554
       is a list of the sprites in the second group it
 
555
       collides with. the two dokill arguments control if
 
556
       the sprites from either group will be automatically
 
557
       removed from all groups."""
 
558
    crashed = {}
 
559
    SC = spritecollide
 
560
    for s in groupa.sprites():
 
561
        c = SC(s, groupb, dokillb)
 
562
        if c:
 
563
            crashed[s] = c
 
564
            if dokilla: s.kill()
 
565
    return crashed
 
566
 
 
567
 
 
568
def spritecollideany(sprite, group):
 
569
    """pygame.sprite.spritecollideany(sprite, group) -> sprite
 
570
       finds any sprites that collide
 
571
 
 
572
       given a sprite and a group of sprites, this will
 
573
       return return any single sprite that collides with
 
574
       with the given sprite. If there are no collisions
 
575
       this returns None.
 
576
       
 
577
       if you don't need all the features of the
 
578
       spritecollide function, this function will be a
 
579
       bit quicker.
 
580
       
 
581
       all sprites must have a "rect" method, which is a
 
582
       rectangle of the sprite area. if the dokill argument
 
583
       is true, the sprites that do collide will be
 
584
       automatically removed from all groups."""
 
585
    spritecollide = sprite.rect.colliderect
 
586
    for s in group.sprites():
 
587
        if spritecollide(s.rect):
 
588
            return 1
 
589
    return 0