Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/pgu/vid.py
diff options
context:
space:
mode:
Diffstat (limited to 'pgu/vid.py')
-rw-r--r--pgu/vid.py560
1 files changed, 560 insertions, 0 deletions
diff --git a/pgu/vid.py b/pgu/vid.py
new file mode 100644
index 0000000..6890e5d
--- /dev/null
+++ b/pgu/vid.py
@@ -0,0 +1,560 @@
+"""Sprite and tile engine.
+
+<p>[[tilevid]], [[isovid]], [[hexvid]] are all subclasses of
+this interface.</p>
+
+<p>Includes support for:</p>
+
+<ul>
+<li> Foreground Tiles
+<li> Background Tiles
+<li> Sprites
+<li> Sprite-Sprite Collision handling
+<li> Sprite-Tile Collision handling
+<li> Scrolling
+<li> Loading from PGU tile and sprite formats (optional)
+<li> Set rate FPS (optional)
+</ul>
+
+<p>This code was previously known as the King James Version (named after the
+Bible of the same name for historical reasons.)</p>
+"""
+
+import pygame
+from pygame.rect import Rect
+from pygame.locals import *
+import math
+
+class Sprite:
+ """The object used for Sprites.
+
+ <pre>Sprite(ishape,pos)</pre>
+
+ <dl>
+ <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will
+ describe the shape of the image, used for collision
+ detection.
+ <dt>pos <dd>initial (x,y) position of the Sprite.
+ </dl>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>rect <dd>the current position of the Sprite
+ <dt>_rect <dd>the previous position of the Sprite
+ <dt>groups <dd>the groups the Sprite is in
+ <dt>agroups <dd>the groups the Sprite can hit in a collision
+ <dt>hit <dd>the handler for hits -- hit(g,s,a)
+ <dt>loop <dd>the loop handler, called once a frame
+ </dl>
+ """
+ def __init__(self,ishape,pos):
+ if not isinstance(ishape, tuple):
+ ishape = ishape,None
+ image,shape = ishape
+ if shape == None:
+ shape = pygame.Rect(0,0,image.get_width(),image.get_height())
+ if isinstance(shape, tuple): shape = pygame.Rect(shape)
+ self.image = image
+ self._image = self.image
+ self.shape = shape
+ self.rect = pygame.Rect(pos[0],pos[1],shape.w,shape.h)
+ self._rect = pygame.Rect(self.rect)
+ self.irect = pygame.Rect(pos[0]-self.shape.x,pos[1]-self.shape.y,
+ image.get_width(),image.get_height())
+ self._irect = pygame.Rect(self.irect)
+ self.groups = 0
+ self.agroups = 0
+ self.updated = 1
+
+ def setimage(self,ishape):
+ """Set the image of the Sprite.
+
+ <pre>Sprite.setimage(ishape)</pre>
+
+ <dl>
+ <dt>ishape <dd>an image, or an image, rectstyle. The rectstyle will
+ describe the shape of the image, used for collision detection.
+ </dl>
+ """
+ if not isinstance(ishape, tuple):
+ ishape = ishape,None
+ image,shape = ishape
+ if shape == None:
+ shape = pygame.Rect(0,0,image.get_width(),image.get_height())
+ if isinstance(shape, tuple):
+ shape = pygame.Rect(shape)
+ self.image = image
+ self.shape = shape
+ self.rect.w,self.rect.h = shape.w,shape.h
+ self.irect.w,self.irect.h = image.get_width(),image.get_height()
+ self.updated = 1
+
+
+class Tile:
+ """Tile Object used by TileCollide.
+
+ <pre>Tile(image=None)</pre>
+ <dl>
+ <dt>image <dd>an image for the Tile.
+ </dl>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>agroups <dd>the groups the Tile can hit in a collision
+ <dt>hit <dd>the handler for hits -- hit(g,t,a)
+ </dl>
+ """
+ def __init__(self,image=None):
+ self.image = image
+ self.agroups = 0
+
+ def __setattr__(self,k,v):
+ if k == 'image' and v != None:
+ self.image_h = v.get_height()
+ self.image_w = v.get_width()
+ self.__dict__[k] = v
+
+class _Sprites(list):
+ def __init__(self):
+ list.__init__(self)
+ self.removed = []
+
+ def append(self,v):
+ list.append(self,v)
+ v.updated = 1
+
+ def remove(self,v):
+ list.remove(self,v)
+ v.updated = 1
+ self.removed.append(v)
+
+class Vid:
+ """An engine for rendering Sprites and Tiles.
+
+ <pre>Vid()</pre>
+
+ <strong>Attributes</strong>
+ <dl>
+ <dt>sprites <dd>a list of the Sprites to be displayed. You may append and
+ remove Sprites from it.
+ <dt>images <dd>a dict for images to be put in.
+ <dt>size <dd>the width, height in Tiles of the layers. Do not modify.
+ <dt>view <dd>a pygame.Rect of the viewed area. You may change .x, .y,
+ etc to move the viewed area around.
+ <dt>bounds <dd>a pygame.Rect (set to None by default) that sets the bounds
+ of the viewable area. Useful for setting certain borders
+ as not viewable.
+ <dt>tlayer <dd>the foreground tiles layer
+ <dt>clayer <dd>the code layer (optional)
+ <dt>blayer <dd>the background tiles layer (optional)
+ <dt>groups <dd>a hash of group names to group values (32 groups max, as a tile/sprites
+ membership in a group is determined by the bits in an integer)
+ </dl>
+ """
+
+ def __init__(self):
+ self.tiles = [None for x in xrange(0,256)]
+ self.sprites = _Sprites()
+ self.images = {} #just a store for images.
+ self.layers = None
+ self.size = None
+ self.view = pygame.Rect(0,0,0,0)
+ self._view = pygame.Rect(self.view)
+ self.bounds = None
+ self.updates = []
+ self.groups = {}
+
+
+ def resize(self,size,bg=0):
+ """Resize the layers.
+
+ <pre>Vid.resize(size,bg=0)</pre>
+
+ <dl>
+ <dt>size <dd>w,h in Tiles of the layers
+ <dt>bg <dd>set to 1 if you wish to use both a foreground layer and a
+ background layer
+ </dl>
+ """
+ self.size = size
+ w,h = size
+ self.layers = [[[0 for x in xrange(0,w)] for y in xrange(0,h)]
+ for z in xrange(0,4)]
+ self.tlayer = self.layers[0]
+ self.blayer = self.layers[1]
+ if not bg: self.blayer = None
+ self.clayer = self.layers[2]
+ self.alayer = self.layers[3]
+
+ self.view.x, self.view.y = 0,0
+ self._view.x, self.view.y = 0,0
+ self.bounds = None
+
+ self.updates = []
+
+ def set(self,pos,v):
+ """Set a tile in the foreground to a value.
+
+ <p>Use this method to set tiles in the foreground, as it will make
+ sure the screen is updated with the change. Directly changing
+ the tlayer will not guarantee updates unless you are using .paint()
+ </p>
+
+ <pre>Vid.set(pos,v)</pre>
+
+ <dl>
+ <dt>pos <dd>(x,y) of tile
+ <dt>v <dd>value
+ </dl>
+ """
+ if self.tlayer[pos[1]][pos[0]] == v: return
+ self.tlayer[pos[1]][pos[0]] = v
+ self.alayer[pos[1]][pos[0]] = 1
+ self.updates.append(pos)
+
+ def get(self,pos):
+ """Get the tlayer at pos.
+
+ <pre>Vid.get(pos): return value</pre>
+
+ <dl>
+ <dt>pos <dd>(x,y) of tile
+ </dl>
+ """
+ return self.tlayer[pos[1]][pos[0]]
+
+ def paint(self,s):
+ """Paint the screen.
+
+ <pre>Vid.paint(screen): return [updates]</pre>
+
+ <dl>
+ <dt>screen <dd>a pygame.Surface to paint to
+ </dl>
+
+ <p>returns the updated portion of the screen (all of it)</p>
+ """
+ return []
+
+ def update(self,s):
+ """Update the screen.
+
+ <pre>Vid.update(screen): return [updates]</pre>
+
+ <dl>
+ <dt>screen <dd>a pygame.Rect to update
+ </dl>
+
+ <p>returns a list of updated rectangles.</p>
+ """
+ self.updates = []
+ return []
+
+ def tga_load_level(self,fname,bg=0):
+ """Load a TGA level.
+
+ <pre>Vid.tga_load_level(fname,bg=0)</pre>
+
+ <dl>
+ <dt>g <dd>a Tilevid instance
+ <dt>fname <dd>tga image to load
+ <dt>bg <dd>set to 1 if you wish to load the background layer
+ </dl>
+ """
+ if type(fname) == str: img = pygame.image.load(fname)
+ else: img = fname
+ w,h = img.get_width(),img.get_height()
+ self.resize((w,h),bg)
+ for y in range(0,h):
+ for x in range(0,w):
+ t,b,c,_a = img.get_at((x,y))
+ self.tlayer[y][x] = t
+ if bg: self.blayer[y][x] = b
+ self.clayer[y][x] = c
+
+ def tga_save_level(self,fname):
+ """Save a TGA level.
+
+ <pre>Vid.tga_save_level(fname)</pre>
+
+ <dl>
+ <dt>fname <dd>tga image to save to
+ </dl>
+ """
+ w,h = self.size
+ img = pygame.Surface((w,h),SWSURFACE,32)
+ img.fill((0,0,0,0))
+ for y in range(0,h):
+ for x in range(0,w):
+ t = self.tlayer[y][x]
+ b = 0
+ if self.blayer:
+ b = self.blayer[y][x]
+ c = self.clayer[y][x]
+ _a = 0
+ img.set_at((x,y),(t,b,c,_a))
+ pygame.image.save(img,fname)
+
+
+
+ def tga_load_tiles(self,fname,size,tdata={}):
+ """Load a TGA tileset.
+
+ <pre>Vid.tga_load_tiles(fname,size,tdata={})</pre>
+
+ <dl>
+ <dt>g <dd>a Tilevid instance
+ <dt>fname <dd>tga image to load
+ <dt>size <dd>(w,h) size of tiles in pixels
+ <dt>tdata <dd>tile data, a dict of tile:(agroups, hit handler, config)
+ </dl>
+ """
+ TW,TH = size
+ if type(fname) == str: img = pygame.image.load(fname).convert_alpha()
+ else: img = fname
+ w,h = img.get_width(),img.get_height()
+
+ n = 0
+ for y in range(0,h,TH):
+ for x in range(0,w,TW):
+ i = img.subsurface((x,y,TW,TH))
+ tile = Tile(i)
+ self.tiles[n] = tile
+ if n in tdata:
+ agroups,hit,config = tdata[n]
+ tile.agroups = self.string2groups(agroups)
+ tile.hit = hit
+ tile.config = config
+ n += 1
+
+
+ def load_images(self,idata):
+ """Load images.
+
+ <pre>Vid.load_images(idata)</pre>
+
+ <dl>
+ <dt>idata <dd>a list of (name, fname, shape)
+ </dl>
+ """
+ for name,fname,shape in idata:
+ self.images[name] = pygame.image.load(fname).convert_alpha(),shape
+
+ def run_codes(self,cdata,rect):
+ """Run codes.
+
+ <pre>Vid.run_codes(cdata,rect)</pre>
+
+ <dl>
+ <dt>cdata <dd>a dict of code:(handler function, value)
+ <dt>rect <dd>a tile rect of the parts of the layer that should have
+ their codes run
+ </dl>
+ """
+ tw,th = self.tiles[0].image.get_width(),self.tiles[0].image.get_height()
+
+ x1,y1,w,h = rect
+ clayer = self.clayer
+ t = Tile()
+ for y in range(y1,y1+h):
+ for x in range(x1,x1+w):
+ n = clayer[y][x]
+ if n in cdata:
+ fnc,value = cdata[n]
+ t.tx,t.ty = x,y
+ t.rect = pygame.Rect(x*tw,y*th,tw,th)
+ fnc(self,t,value)
+
+
+ def string2groups(self,str):
+ """Convert a string to groups.
+
+ <pre>Vid.string2groups(str): return groups</pre>
+ """
+ if str == None: return 0
+ return self.list2groups(str.split(","))
+
+ def list2groups(self,igroups):
+ """Convert a list to groups.
+ <pre>Vid.list2groups(igroups): return groups</pre>
+ """
+ for s in igroups:
+ if not s in self.groups:
+ self.groups[s] = 2**len(self.groups)
+ v = 0
+ for s,n in self.groups.items():
+ if s in igroups: v|=n
+ return v
+
+ def groups2list(self,groups):
+ """Convert a groups to a list.
+ <pre>Vid.groups2list(groups): return list</pre>
+ """
+ v = []
+ for s,n in self.groups.items():
+ if (n&groups)!=0: v.append(s)
+ return v
+
+ def hit(self,x,y,t,s):
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ t.tx = x
+ t.ty = y
+ t.rect = Rect(x*tw,y*th,tw,th)
+ t._rect = t.rect
+ if hasattr(t,'hit'):
+ t.hit(self,t,s)
+
+ def loop(self):
+ """Update and hit testing loop. Run this once per frame.
+ <pre>Vid.loop()</pre>
+ """
+ self.loop_sprites() #sprites may move
+ self.loop_tilehits() #sprites move
+ self.loop_spritehits() #no sprites should move
+ for s in self.sprites:
+ s._rect = pygame.Rect(s.rect)
+
+ def loop_sprites(self):
+ as_ = self.sprites[:]
+ for s in as_:
+ if hasattr(s,'loop'):
+ s.loop(self,s)
+
+ def loop_tilehits(self):
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+
+ layer = self.layers[0]
+
+ as_ = self.sprites[:]
+ for s in as_:
+ self._tilehits(s)
+
+ def _tilehits(self,s):
+ tiles = self.tiles
+ tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
+ layer = self.layers[0]
+
+ for _z in (0,):
+ if s.groups != 0:
+
+ _rect = s._rect
+ rect = s.rect
+
+ _rectx = _rect.x
+ _recty = _rect.y
+ _rectw = _rect.w
+ _recth = _rect.h
+
+ rectx = rect.x
+ recty = rect.y
+ rectw = rect.w
+ recth = rect.h
+
+ rect.y = _rect.y
+ rect.h = _rect.h
+
+ hits = []
+ ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
+ #nasty ol loops
+ y = ct/th*th
+ while y < cb:
+ x = cl/tw*tw
+ yy = y/th
+ while x < cr:
+ xx = x/tw
+ t = tiles[layer[yy][xx]]
+ if (s.groups & t.agroups)!=0:
+ #self.hit(xx,yy,t,s)
+ d = math.hypot(rect.centerx-(xx*tw+tw/2),
+ rect.centery-(yy*th+th/2))
+ hits.append((d,t,xx,yy))
+
+ x += tw
+ y += th
+
+ hits.sort()
+ #if len(hits) > 0: print self.frame,hits
+ for d,t,xx,yy in hits:
+ self.hit(xx,yy,t,s)
+
+ #switching directions...
+ _rect.x = rect.x
+ _rect.w = rect.w
+ rect.y = recty
+ rect.h = recth
+
+ hits = []
+ ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
+ #nasty ol loops
+ y = ct/th*th
+ while y < cb:
+ x = cl/tw*tw
+ yy = y/th
+ while x < cr:
+ xx = x/tw
+ t = tiles[layer[yy][xx]]
+ if (s.groups & t.agroups)!=0:
+ d = math.hypot(rect.centerx-(xx*tw+tw/2),
+ rect.centery-(yy*th+th/2))
+ hits.append((d,t,xx,yy))
+ #self.hit(xx,yy,t,s)
+ x += tw
+ y += th
+
+ hits.sort()
+ #if len(hits) > 0: print self.frame,hits
+ for d,t,xx,yy in hits:
+ self.hit(xx,yy,t,s)
+
+ #done with loops
+ _rect.x = _rectx
+ _rect.y = _recty
+
+
+ def loop_spritehits(self):
+ as_ = self.sprites[:]
+
+ groups = {}
+ for n in range(0,31):
+ groups[1<<n] = []
+ for s in as_:
+ g = s.groups
+ n = 1
+ while g:
+ if (g&1)!=0: groups[n].append(s)
+ g >>= 1
+ n <<= 1
+
+ for s in as_:
+ if s.agroups!=0:
+ rect1,rect2 = s.rect,Rect(s.rect)
+ #if rect1.centerx < 320: rect2.x += 640
+ #else: rect2.x -= 640
+ g = s.agroups
+ n = 1
+ while g:
+ if (g&1)!=0:
+ for b in groups[n]:
+ if (s != b and (s.agroups & b.groups)!=0
+ and s.rect.colliderect(b.rect)):
+ s.hit(self,s,b)
+
+ g >>= 1
+ n <<= 1
+
+
+ def screen_to_tile(self,pos):
+ """Convert a screen position to a tile position.
+ <pre>Vid.screen_to_tile(pos): return pos</pre>
+ """
+ return pos
+
+ def tile_to_screen(self,pos):
+ """Convert a tile position to a screen position.
+ <pre>Vid.tile_to_screen(pos): return pos</pre>
+ """
+ return pos
+
+# vim: set filetype=python sts=4 sw=4 noet si :