Richard Jones' Log: PyGame sample: drawing a map, and moving around it

Wed, 15 Sep 2004

I'm re-acquainting myself with PyGame prior to the next 48-hour game programming comp (yes, I'm probably going to be hosting it). I decided to start with something simple that I'd never actually gotten around to writing: a scrollable map that you navigate by clicking around it. So, here's:

  1. playmap.py — the source that does the work,
  2. tiles.png — map tile image source (with variants for most tiles),
  3. map.txt — map layout text file, and
  4. grid.png — 64x64 grid image, handy for using as a backdrop layer when drawing the map tiles.

The core of the code is the Map class:

# define where to get the tile images from in the tiles source image
tile_coords = {
    'a': (0,0),
    'b': [(64,0), (64, 128)],
    'c': (128,0),
    'd': [(0,64), (128, 64)],
    'e': (0,128),
    'f': (128,128),
    '.': None,
}

class Map:
    def __init__(self, map, tiles):
        self.tiles = pygame.image.load(tiles)

        l = [line.strip() for line in open(map).readlines()]
        self.map = [[None]*len(l[0]) for j in range(len(l))]
        for i in range(len(l[0])):
            for j in range(len(l)):
                tile = l[j][i]
                tile = tile_coords[tile]
                if tile is None:
                    continue
                elif isinstance(tile, type([])):
                    tile = random.choice(tile)
                cx, cy = tile
                if random.choice((0,1)):
                    cx += 192
                if random.choice((0,1)):
                    cy += 192
                self.map[j][i] = (cx, cy)

    def draw(self, view, viewpos):
        '''Draw the map to the "view" with the top-left of "view" being at
           "viewpos" in the map.
        '''
        sx, sy = view.get_size()
        bx = viewpos[0]/64
        by = viewpos[1]/64
        for x in range(0, sx+64, 64):
            i = x/64 + bx
            for y in range(0, sy+64, 64):
                j = y/64 + by
                try:
                    tile = self.map[j][i]
                except IndexError:
                    # too close to the edge
                    continue
                if tile is None:
                    continue
                cx, cy = tile
                view.blit(self.tiles, (x, y), (cx, cy, 64, 64))

    def limit(self, view, pos):
        '''Limit the "viewpos" variable such that it defines a valid top-left
           rectangle of "view"'s size over the map.
        '''
        x, y = pos
        # easy
        x = max(x, 0)
        y = max(y, 0)

        # figure number of tiles in a view, hence max x and y viewpos
        sx, sy = view.get_size()
        nx, ny = sx/64, sy/64
        mx = (len(self.map[0]) - nx) * 64
        my = (len(self.map) - ny) * 64
        print y, my

        return (min(x, mx), min(y, my))

To do:

  1. re-write the limit() method to use pygame's built-in Rect objects to do the clamping,
  2. make it smoother (currently moving around is done in 64 pixel jumps, and constant movement would be nice), and
  3. make it do less work (currently it re-draws the whole map each time - we should be able to blit() some of the existing map when we re-draw)
Comment by Sean O'Donnell on Tue, 14 Sep 2004

You seem to have left solids.png out of the list of files above. I assumed that it should be grid.png and changed it and it seems to work ok. Nice little script.

Thanks

Sean

Comment by Richard on Wed, 15 Sep 2004

Oops. Yeah, it's unused at the moment :)

I've fixed the code.

Comment by Leslie Owusu-Appiah on Thu, 18 May 2006

Dude, you may need to fix your hotlinking CGI/PHP. grid.png politely tells me to STEAL SOMEONE ELSE'S BANDWIDTH. P.S. That files 17kb! It could be smaller! Oh, yeah, thanks for the guide! (^_^;)

Comment by Richard Jones on Thu, 18 May 2006

I replaced that image because I had a *lot* of people linking to it.