1 """Sprite and tile engine.
3 tilevid, isovid, hexvid are all subclasses of this interface.
10 * Sprite-Sprite Collision handling
11 * Sprite-Tile Collision handling
13 * Loading from PGU tile and sprite formats (optional)
14 * Set rate FPS (optional)
16 This code was previously known as the King James Version (named after the
17 Bible of the same name for historical reasons.)
22 from pygame.rect import Rect
23 from pygame.locals import *
27 """The object used for Sprites.
30 ishape -- an image, or an image, rectstyle. The rectstyle will
31 describe the shape of the image, used for collision
33 pos -- initial (x,y) position of the Sprite.
36 rect -- the current position of the Sprite
37 _rect -- the previous position of the Sprite
38 groups -- the groups the Sprite is in
39 agroups -- the groups the Sprite can hit in a collision
40 hit -- the handler for hits -- hit(g,s,a)
41 loop -- the loop handler, called once a frame
44 def __init__(self,ishape,pos):
45 if not isinstance(ishape, tuple):
49 shape = pygame.Rect(0,0,image.get_width(),image.get_height())
50 if isinstance(shape, tuple): shape = pygame.Rect(shape)
52 self._image = self.image
54 self.rect = pygame.Rect(pos[0],pos[1],shape.w,shape.h)
55 self._rect = pygame.Rect(self.rect)
56 self.irect = pygame.Rect(pos[0]-self.shape.x,pos[1]-self.shape.y,
57 image.get_width(),image.get_height())
58 self._irect = pygame.Rect(self.irect)
63 def setimage(self,ishape):
64 """Set the image of the Sprite.
67 ishape -- an image, or an image, rectstyle. The rectstyle will
68 describe the shape of the image, used for collision detection.
71 if not isinstance(ishape, tuple):
75 shape = pygame.Rect(0,0,image.get_width(),image.get_height())
76 if isinstance(shape, tuple):
77 shape = pygame.Rect(shape)
80 self.rect.w,self.rect.h = shape.w,shape.h
81 self.irect.w,self.irect.h = image.get_width(),image.get_height()
86 """Tile Object used by TileCollide.
89 image -- an image for the Tile.
92 agroups -- the groups the Tile can hit in a collision
93 hit -- the handler for hits -- hit(g,t,a)
96 def __init__(self,image=None):
100 def __setattr__(self,k,v):
101 if k == 'image' and v != None:
102 self.image_h = v.get_height()
103 self.image_w = v.get_width()
106 class _Sprites(list):
118 self.removed.append(v)
121 """An engine for rendering Sprites and Tiles.
124 sprites -- a list of the Sprites to be displayed. You may append and
125 remove Sprites from it.
126 images -- a dict for images to be put in.
127 size -- the width, height in Tiles of the layers. Do not modify.
128 view -- a pygame.Rect of the viewed area. You may change .x, .y,
129 etc to move the viewed area around.
130 bounds -- a pygame.Rect (set to None by default) that sets the bounds
131 of the viewable area. Useful for setting certain borders
133 tlayer -- the foreground tiles layer
134 clayer -- the code layer (optional)
135 blayer -- the background tiles layer (optional)
136 groups -- a hash of group names to group values (32 groups max, as a tile/sprites
137 membership in a group is determined by the bits in an integer)
142 self.tiles = [None for x in xrange(0,256)]
143 self.sprites = _Sprites()
144 self.images = {} #just a store for images.
147 self.view = pygame.Rect(0,0,0,0)
148 self._view = pygame.Rect(self.view)
154 def resize(self,size,bg=0):
155 """Resize the layers.
158 size -- w,h in Tiles of the layers
159 bg -- set to 1 if you wish to use both a foreground layer and a
165 self.layers = [[[0 for x in xrange(0,w)] for y in xrange(0,h)]
166 for z in xrange(0,4)]
167 self.tlayer = self.layers[0]
168 self.blayer = self.layers[1]
169 if not bg: self.blayer = None
170 self.clayer = self.layers[2]
171 self.alayer = self.layers[3]
173 self.view.x, self.view.y = 0,0
174 self._view.x, self.view.y = 0,0
180 """Set a tile in the foreground to a value.
182 Use this method to set tiles in the foreground, as it will make
183 sure the screen is updated with the change. Directly changing
184 the tlayer will not guarantee updates unless you are using .paint()
191 if self.tlayer[pos[1]][pos[0]] == v: return
192 self.tlayer[pos[1]][pos[0]] = v
193 self.alayer[pos[1]][pos[0]] = 1
194 self.updates.append(pos)
197 """Get the tlayer at pos.
203 return self.tlayer[pos[1]][pos[0]]
209 screen -- a pygame.Surface to paint to
211 Returns the updated portion of the screen (all of it)
217 """Update the screen.
220 screen -- a pygame.Rect to update
222 Returns a list of updated rectangles.
228 def tga_load_level(self,fname,bg=0):
232 g -- a Tilevid instance
233 fname -- tga image to load
234 bg -- set to 1 if you wish to load the background layer
237 if type(fname) == str: img = pygame.image.load(fname)
239 w,h = img.get_width(),img.get_height()
240 self.resize((w,h),bg)
243 t,b,c,_a = img.get_at((x,y))
244 self.tlayer[y][x] = t
245 if bg: self.blayer[y][x] = b
246 self.clayer[y][x] = c
248 def tga_save_level(self,fname):
252 fname -- tga image to save to
256 img = pygame.Surface((w,h),SWSURFACE,32)
260 t = self.tlayer[y][x]
263 b = self.blayer[y][x]
264 c = self.clayer[y][x]
266 img.set_at((x,y),(t,b,c,_a))
267 pygame.image.save(img,fname)
271 def tga_load_tiles(self,fname,size,tdata={}):
272 """Load a TGA tileset.
275 g -- a Tilevid instance
276 fname -- tga image to load
277 size -- (w,h) size of tiles in pixels
278 tdata -- tile data, a dict of tile:(agroups, hit handler, config)
282 if type(fname) == str: img = pygame.image.load(fname).convert_alpha()
284 w,h = img.get_width(),img.get_height()
287 for y in range(0,h,TH):
288 for x in range(0,w,TW):
289 i = img.subsurface((x,y,TW,TH))
293 agroups,hit,config = tdata[n]
294 tile.agroups = self.string2groups(agroups)
300 def load_images(self,idata):
304 idata -- a list of (name, fname, shape)
307 for name,fname,shape in idata:
308 self.images[name] = pygame.image.load(fname).convert_alpha(),shape
310 def run_codes(self,cdata,rect):
314 cdata -- a dict of code:(handler function, value)
315 rect -- a tile rect of the parts of the layer that should have
319 tw,th = self.tiles[0].image.get_width(),self.tiles[0].image.get_height()
324 for y in range(y1,y1+h):
325 for x in range(x1,x1+w):
330 t.rect = pygame.Rect(x*tw,y*th,tw,th)
334 def string2groups(self,str):
335 """Convert a string to groups."""
336 if str == None: return 0
337 return self.list2groups(str.split(","))
339 def list2groups(self,igroups):
340 """Convert a list to groups."""
342 if not s in self.groups:
343 self.groups[s] = 2**len(self.groups)
345 for s,n in self.groups.items():
346 if s in igroups: v|=n
349 def groups2list(self,groups):
350 """Convert a groups to a list."""
352 for s,n in self.groups.items():
353 if (n&groups)!=0: v.append(s)
356 def hit(self,x,y,t,s):
358 tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
361 t.rect = Rect(x*tw,y*th,tw,th)
367 """Update and hit testing loop. Run this once per frame."""
368 self.loop_sprites() #sprites may move
369 self.loop_tilehits() #sprites move
370 self.loop_spritehits() #no sprites should move
371 for s in self.sprites:
372 s._rect = pygame.Rect(s.rect)
374 def loop_sprites(self):
375 as_ = self.sprites[:]
377 if hasattr(s,'loop'):
380 def loop_tilehits(self):
382 tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
384 layer = self.layers[0]
386 as_ = self.sprites[:]
390 def _tilehits(self,s):
392 tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
393 layer = self.layers[0]
415 ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
423 t = tiles[layer[yy][xx]]
424 if (s.groups & t.agroups)!=0:
426 d = math.hypot(rect.centerx-(xx*tw+tw/2),
427 rect.centery-(yy*th+th/2))
428 hits.append((d,t,xx,yy))
434 #if len(hits) > 0: print self.frame,hits
435 for d,t,xx,yy in hits:
438 #switching directions...
445 ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
453 t = tiles[layer[yy][xx]]
454 if (s.groups & t.agroups)!=0:
455 d = math.hypot(rect.centerx-(xx*tw+tw/2),
456 rect.centery-(yy*th+th/2))
457 hits.append((d,t,xx,yy))
463 #if len(hits) > 0: print self.frame,hits
464 for d,t,xx,yy in hits:
472 def loop_spritehits(self):
473 as_ = self.sprites[:]
476 for n in range(0,31):
482 if (g&1)!=0: groups[n].append(s)
488 rect1,rect2 = s.rect,Rect(s.rect)
489 #if rect1.centerx < 320: rect2.x += 640
490 #else: rect2.x -= 640
496 if (s != b and (s.agroups & b.groups)!=0
497 and s.rect.colliderect(b.rect)):
504 def screen_to_tile(self,pos):
505 """Convert a screen position to a tile position."""
508 def tile_to_screen(self,pos):
509 """Convert a tile position to a screen position."""