1 """Sprite and tile engine.
3 <p>[[tilevid]], [[isovid]], [[hexvid]] are all subclasses of
6 <p>Includes support for:</p>
12 <li> Sprite-Sprite Collision handling
13 <li> Sprite-Tile Collision handling
15 <li> Loading from PGU tile and sprite formats (optional)
16 <li> Set rate FPS (optional)
19 <p>This code was previously known as the King James Version (named after the
20 Bible of the same name for historical reasons.)</p>
24 from pygame
.rect
import Rect
25 from pygame
.locals import *
29 """The object used for Sprites.
31 <pre>Sprite(ishape,pos)</pre>
34 <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will
35 describe the shape of the image, used for collision
37 <dt>pos <dd>initial (x,y) position of the Sprite.
40 <strong>Attributes</strong>
42 <dt>rect <dd>the current position of the Sprite
43 <dt>_rect <dd>the previous position of the Sprite
44 <dt>groups <dd>the groups the Sprite is in
45 <dt>agroups <dd>the groups the Sprite can hit in a collision
46 <dt>hit <dd>the handler for hits -- hit(g,s,a)
47 <dt>loop <dd>the loop handler, called once a frame
50 def __init__(self
,ishape
,pos
):
51 if not isinstance(ishape
, tuple):
55 shape
= pygame
.Rect(0,0,image
.get_width(),image
.get_height())
56 if isinstance(shape
, tuple): shape
= pygame
.Rect(shape
)
58 self
._image
= self
.image
60 self
.rect
= pygame
.Rect(pos
[0],pos
[1],shape
.w
,shape
.h
)
61 self
._rect
= pygame
.Rect(self
.rect
)
62 self
.irect
= pygame
.Rect(pos
[0]-self
.shape
.x
,pos
[1]-self
.shape
.y
,
63 image
.get_width(),image
.get_height())
64 self
._irect
= pygame
.Rect(self
.irect
)
69 def setimage(self
,ishape
):
70 """Set the image of the Sprite.
72 <pre>Sprite.setimage(ishape)</pre>
75 <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will
76 describe the shape of the image, used for collision detection.
79 if not isinstance(ishape
, tuple):
83 shape
= pygame
.Rect(0,0,image
.get_width(),image
.get_height())
84 if isinstance(shape
, tuple):
85 shape
= pygame
.Rect(shape
)
88 self
.rect
.w
,self
.rect
.h
= shape
.w
,shape
.h
89 self
.irect
.w
,self
.irect
.h
= image
.get_width(),image
.get_height()
94 """Tile Object used by TileCollide.
96 <pre>Tile(image=None)</pre>
98 <dt>image <dd>an image for the Tile.
101 <strong>Attributes</strong>
103 <dt>agroups <dd>the groups the Tile can hit in a collision
104 <dt>hit <dd>the handler for hits -- hit(g,t,a)
107 def __init__(self
,image
=None):
111 def __setattr__(self
,k
,v
):
112 if k
== 'image' and v
!= None:
113 self
.image_h
= v
.get_height()
114 self
.image_w
= v
.get_width()
117 class _Sprites(list):
129 self
.removed
.append(v
)
132 """An engine for rendering Sprites and Tiles.
136 <strong>Attributes</strong>
138 <dt>sprites <dd>a list of the Sprites to be displayed. You may append and
139 remove Sprites from it.
140 <dt>images <dd>a dict for images to be put in.
141 <dt>size <dd>the width, height in Tiles of the layers. Do not modify.
142 <dt>view <dd>a pygame.Rect of the viewed area. You may change .x, .y,
143 etc to move the viewed area around.
144 <dt>bounds <dd>a pygame.Rect (set to None by default) that sets the bounds
145 of the viewable area. Useful for setting certain borders
147 <dt>tlayer <dd>the foreground tiles layer
148 <dt>clayer <dd>the code layer (optional)
149 <dt>blayer <dd>the background tiles layer (optional)
150 <dt>groups <dd>a hash of group names to group values (32 groups max, as a tile/sprites
151 membership in a group is determined by the bits in an integer)
156 self
.tiles
= [None for x
in xrange(0,256)]
157 self
.sprites
= _Sprites()
158 self
.images
= {} #just a store for images.
161 self
.view
= pygame
.Rect(0,0,0,0)
162 self
._view
= pygame
.Rect(self
.view
)
168 def resize(self
,size
,bg
=0):
169 """Resize the layers.
171 <pre>Vid.resize(size,bg=0)</pre>
174 <dt>size <dd>w,h in Tiles of the layers
175 <dt>bg <dd>set to 1 if you wish to use both a foreground layer and a
181 self
.layers
= [[[0 for x
in xrange(0,w
)] for y
in xrange(0,h
)]
182 for z
in xrange(0,4)]
183 self
.tlayer
= self
.layers
[0]
184 self
.blayer
= self
.layers
[1]
185 if not bg
: self
.blayer
= None
186 self
.clayer
= self
.layers
[2]
187 self
.alayer
= self
.layers
[3]
189 self
.view
.x
, self
.view
.y
= 0,0
190 self
._view
.x
, self
.view
.y
= 0,0
196 """Set a tile in the foreground to a value.
198 <p>Use this method to set tiles in the foreground, as it will make
199 sure the screen is updated with the change. Directly changing
200 the tlayer will not guarantee updates unless you are using .paint()
203 <pre>Vid.set(pos,v)</pre>
206 <dt>pos <dd>(x,y) of tile
210 if self
.tlayer
[pos
[1]][pos
[0]] == v
: return
211 self
.tlayer
[pos
[1]][pos
[0]] = v
212 self
.alayer
[pos
[1]][pos
[0]] = 1
213 self
.updates
.append(pos
)
216 """Get the tlayer at pos.
218 <pre>Vid.get(pos): return value</pre>
221 <dt>pos <dd>(x,y) of tile
224 return self
.tlayer
[pos
[1]][pos
[0]]
229 <pre>Vid.paint(screen): return [updates]</pre>
232 <dt>screen <dd>a pygame.Surface to paint to
235 <p>returns the updated portion of the screen (all of it)</p>
240 """Update the screen.
242 <pre>Vid.update(screen): return [updates]</pre>
245 <dt>screen <dd>a pygame.Rect to update
248 <p>returns a list of updated rectangles.</p>
253 def tga_load_level(self
,fname
,bg
=0):
256 <pre>Vid.tga_load_level(fname,bg=0)</pre>
259 <dt>g <dd>a Tilevid instance
260 <dt>fname <dd>tga image to load
261 <dt>bg <dd>set to 1 if you wish to load the background layer
264 if type(fname
) == str: img
= pygame
.image
.load(fname
)
266 w
,h
= img
.get_width(),img
.get_height()
267 self
.resize((w
,h
),bg
)
270 t
,b
,c
,_a
= img
.get_at((x
,y
))
271 self
.tlayer
[y
][x
] = t
272 if bg
: self
.blayer
[y
][x
] = b
273 self
.clayer
[y
][x
] = c
275 def tga_save_level(self
,fname
):
278 <pre>Vid.tga_save_level(fname)</pre>
281 <dt>fname <dd>tga image to save to
285 img
= pygame
.Surface((w
,h
),SWSURFACE
,32)
289 t
= self
.tlayer
[y
][x
]
292 b
= self
.blayer
[y
][x
]
293 c
= self
.clayer
[y
][x
]
295 img
.set_at((x
,y
),(t
,b
,c
,_a
))
296 pygame
.image
.save(img
,fname
)
300 def tga_load_tiles(self
,fname
,size
,tdata
={}):
301 """Load a TGA tileset.
303 <pre>Vid.tga_load_tiles(fname,size,tdata={})</pre>
306 <dt>g <dd>a Tilevid instance
307 <dt>fname <dd>tga image to load
308 <dt>size <dd>(w,h) size of tiles in pixels
309 <dt>tdata <dd>tile data, a dict of tile:(agroups, hit handler, config)
313 if type(fname
) == str: img
= pygame
.image
.load(fname
).convert_alpha()
315 w
,h
= img
.get_width(),img
.get_height()
318 for y
in range(0,h
,TH
):
319 for x
in range(0,w
,TW
):
320 i
= img
.subsurface((x
,y
,TW
,TH
))
324 agroups
,hit
,config
= tdata
[n
]
325 tile
.agroups
= self
.string2groups(agroups
)
331 def load_images(self
,idata
):
334 <pre>Vid.load_images(idata)</pre>
337 <dt>idata <dd>a list of (name, fname, shape)
340 for name
,fname
,shape
in idata
:
341 self
.images
[name
] = pygame
.image
.load(fname
).convert_alpha(),shape
343 def run_codes(self
,cdata
,rect
):
346 <pre>Vid.run_codes(cdata,rect)</pre>
349 <dt>cdata <dd>a dict of code:(handler function, value)
350 <dt>rect <dd>a tile rect of the parts of the layer that should have
354 tw
,th
= self
.tiles
[0].image
.get_width(),self
.tiles
[0].image
.get_height()
359 for y
in range(y1
,y1
+h
):
360 for x
in range(x1
,x1
+w
):
365 t
.rect
= pygame
.Rect(x
*tw
,y
*th
,tw
,th
)
369 def string2groups(self
,str):
370 """Convert a string to groups.
372 <pre>Vid.string2groups(str): return groups</pre>
374 if str == None: return 0
375 return self
.list2groups(str.split(","))
377 def list2groups(self
,igroups
):
378 """Convert a list to groups.
379 <pre>Vid.list2groups(igroups): return groups</pre>
382 if not s
in self
.groups
:
383 self
.groups
[s
] = 2**len(self
.groups
)
385 for s
,n
in self
.groups
.items():
386 if s
in igroups
: v|
=n
389 def groups2list(self
,groups
):
390 """Convert a groups to a list.
391 <pre>Vid.groups2list(groups): return list</pre>
394 for s
,n
in self
.groups
.items():
395 if (n
&groups
)!=0: v
.append(s
)
398 def hit(self
,x
,y
,t
,s
):
400 tw
,th
= tiles
[0].image
.get_width(),tiles
[0].image
.get_height()
403 t
.rect
= Rect(x
*tw
,y
*th
,tw
,th
)
409 """Update and hit testing loop. Run this once per frame.
410 <pre>Vid.loop()</pre>
412 self
.loop_sprites() #sprites may move
413 self
.loop_tilehits() #sprites move
414 self
.loop_spritehits() #no sprites should move
415 for s
in self
.sprites
:
416 s
._rect
= pygame
.Rect(s
.rect
)
418 def loop_sprites(self
):
419 as_
= self
.sprites
[:]
421 if hasattr(s
,'loop'):
424 def loop_tilehits(self
):
426 tw
,th
= tiles
[0].image
.get_width(),tiles
[0].image
.get_height()
428 layer
= self
.layers
[0]
430 as_
= self
.sprites
[:]
434 def _tilehits(self
,s
):
436 tw
,th
= tiles
[0].image
.get_width(),tiles
[0].image
.get_height()
437 layer
= self
.layers
[0]
459 ct
,cb
,cl
,cr
= rect
.top
,rect
.bottom
,rect
.left
,rect
.right
467 t
= tiles
[layer
[yy
][xx
]]
468 if (s
.groups
& t
.agroups
)!=0:
470 d
= math
.hypot(rect
.centerx
-(xx
*tw
+tw
/2),
471 rect
.centery
-(yy
*th
+th
/2))
472 hits
.append((d
,t
,xx
,yy
))
478 #if len(hits) > 0: print self.frame,hits
479 for d
,t
,xx
,yy
in hits
:
482 #switching directions...
489 ct
,cb
,cl
,cr
= rect
.top
,rect
.bottom
,rect
.left
,rect
.right
497 t
= tiles
[layer
[yy
][xx
]]
498 if (s
.groups
& t
.agroups
)!=0:
499 d
= math
.hypot(rect
.centerx
-(xx
*tw
+tw
/2),
500 rect
.centery
-(yy
*th
+th
/2))
501 hits
.append((d
,t
,xx
,yy
))
507 #if len(hits) > 0: print self.frame,hits
508 for d
,t
,xx
,yy
in hits
:
516 def loop_spritehits(self
):
517 as_
= self
.sprites
[:]
520 for n
in range(0,31):
526 if (g
&1)!=0: groups
[n
].append(s
)
532 rect1
,rect2
= s
.rect
,Rect(s
.rect
)
533 #if rect1.centerx < 320: rect2.x += 640
534 #else: rect2.x -= 640
540 if (s
!= b
and (s
.agroups
& b
.groups
)!=0
541 and s
.rect
.colliderect(b
.rect
)):
548 def screen_to_tile(self
,pos
):
549 """Convert a screen position to a tile position.
550 <pre>Vid.screen_to_tile(pos): return pos</pre>
554 def tile_to_screen(self
,pos
):
555 """Convert a tile position to a screen position.
556 <pre>Vid.tile_to_screen(pos): return pos</pre>
560 # vim: set filetype=python sts=4 sw=4 noet si :