changeset 22:78492768ce73

fix the depth bug, add comments
author sheep@ghostwheel
date Sun, 25 Oct 2009 13:21:29 +0100
parents 7bd74f38be48
children 71ba52633428
files MgOpenModataBold.ttf cursor.png dialog.png qq.py
diffstat 4 files changed, 49 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
Binary file MgOpenModataBold.ttf has changed
Binary file cursor.png has changed
Binary file dialog.png has changed
--- a/qq.py	Sun Oct 25 13:10:22 2009 +0100
+++ b/qq.py	Sun Oct 25 13:21:29 2009 +0100
@@ -1,12 +1,27 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
+"""
+This is sample of how you can implement a tile-based game, not unlike
+the RPG games known from consoles, in pygame. It's not a playable game,
+but it can be turned into one. Care has been taken to comment it clearly,
+so that you can use it easily as a starting point for your game.
+
+The program reads a level definition from a "level.map" file, and uses the
+graphics referenced for that file to display a tiled map on the screen and
+let you move an animated player character around it.
+
+Note that a lot of additional work is needed to turn it into an actual game.
+
+@copyright: 2008, 2009 Radomir Dopieralski <qq@sheep.art.pl>
+@license: BSD, see COPYING for details
+
+"""
+
 import ConfigParser
 
 import pygame
 import pygame.locals as pg
-# from pygame.locals import *
-
 
 # Motion offsets for particular directions
 #     N  E  S   W
@@ -24,16 +39,19 @@
         self.height = height or width
 
     def __getitem__(self, filename):
+        """Return a table of tiles, load it from disk if needed."""
+
         key = (filename, self.width, self.height)
         try:
             return self.cache[key]
         except KeyError:
-            tile_table = self.load_tile_table(filename, self.width, self.height)
+            tile_table = self._load_tile_table(filename, self.width,
+                                               self.height)
             self.cache[key] = tile_table
             return tile_table
 
-    def load_tile_table(self, filename, width, height):
-        """Load the image and split into tiles."""
+    def _load_tile_table(self, filename, width, height):
+        """Load an image and split it into tiles."""
 
         image = pygame.image.load(filename).convert()
         image_width, image_height = image.get_size()
@@ -51,11 +69,9 @@
     """A sprite group that sorts them by depth."""
 
     def sprites(self):
-        """The list of sprites in the group."""
+        """The list of sprites in the group, sorted by depth."""
 
-        sprites = self.spritedict.keys()
-        sprites.sort(lambda a, b: a.depth - b.depth)
-        return sprites
+        return sorted(self.spritedict.keys(), key=lambda sprite: sprite.depth)
 
 
 class Shadow(pygame.sprite.Sprite):
@@ -87,7 +103,6 @@
         self.rect = self.image.get_rect()
         self.animation = self.stand_animation()
         self.pos = pos
-        self.depth = 0
 
     def _get_pos(self):
         """Check the current position of the sprite on the map."""
@@ -95,7 +110,7 @@
         return (self.rect.midbottom[0]-12)/24, (self.rect.midbottom[1]-16)/16
 
     def _set_pos(self, pos):
-        """Set the position of the sprite on the map."""
+        """Set the position and depth of the sprite on the map."""
 
         self.rect.midbottom = pos[0]*24+12, pos[1]*16+16
         self.depth = self.rect.midbottom[1]
@@ -134,8 +149,6 @@
         self.direction = 2
         self.animation = None
         self.image = self.frames[self.direction][0]
-        # Move it on top of all other sprites on the same tile
-        self.rect.move_ip((0, 1))
 
     def walk_animation(self):
         """Animation for the player walking."""
@@ -179,21 +192,14 @@
         self.map = parser.get("level", "map").split("\n")
         for section in parser.sections():
             if len(section) == 1:
-                # value = parser.get(section, "name")
                 desc = dict(parser.items(section))
                 self.key[section] = desc
         self.width = len(self.map[0])
         self.height = len(self.map)
-        y = 0
-        for line in self.map:
-            x = 0
-            for c in line:
-                if not self.is_wall(x, y):
-                    # floor
-                    if 'sprite' in self.key[c]:
-                        self.items[(x, y)] = self.key[c]
-                x += 1
-            y += 1
+        for y, line in enumerate(self.map):
+            for x, c in enumerate(line):
+                if not self.is_wall(x, y) and 'sprite' in self.key[c]:
+                    self.items[(x, y)] = self.key[c]
 
     def render(self):
         """Draw the level on the surface."""
@@ -203,11 +209,10 @@
         tiles = TileCache(tile_width, tile_height)[self.tileset]
         image = pygame.Surface((self.width*tile_width, self.height*tile_height))
         overlays = {}
-        map_y = 0
-        for line in self.map:
-            map_x = 0
-            for c in line:
+        for map_y, line in enumerate(self.map):
+            for map_x, c in enumerate(line):
                 if wall(map_x, map_y):
+                    # Draw different tiles depending on neighbourhood
                     if not wall(map_x, map_y+1):
                         if wall(map_x+1, map_y) and wall(map_x-1, map_y):
                             tile = 1, 2
@@ -226,6 +231,7 @@
                             tile = 2, 1
                         else:
                             tile = 3, 1
+                    # Add overlays if the wall may be obscuring something
                     if not wall(map_x, map_y-1):
                         if wall(map_x+1, map_y) and wall(map_x-1, map_y):
                             over = 1, 0
@@ -241,11 +247,10 @@
                         tile = self.key[c]['tile'].split(',')
                         tile = int(tile[0]), int(tile[1])
                     except (ValueError, KeyError):
+                        # Default to ground tile
                         tile = 0, 3
                 tile_image = tiles[tile[0]][tile[1]]
                 image.blit(tile_image, (map_x*tile_width, map_y*tile_height))
-                map_x += 1
-            map_y += 1
         return image, overlays
 
     def get_tile(self, x, y):
@@ -264,7 +269,7 @@
         """Tell if the specified flag is set for position on the map."""
 
         value = self.get_tile(x, y).get(name)
-        return value in ('true', 'yes', 'True', 'Yes', '1', 'on', 'On')
+        return value in (True, 1, 'true', 'yes', 'True', 'Yes', '1', 'on', 'On')
 
     def is_wall(self, x, y):
         """Is there a wall?"""
@@ -303,6 +308,7 @@
         self.sprites = SortedUpdates()
         self.overlays = pygame.sprite.RenderUpdates()
         self.level = level
+        # Populate the game with the level's objects
         for pos, tile in level.items.iteritems():
             if tile.get("player") in ('true', '1', 'yes', 'on'):
                 sprite = Player(pos)
@@ -311,9 +317,10 @@
                 sprite = Sprite(pos, TileCache()[tile["sprite"]])
             self.sprites.add(sprite)
             self.shadows.add(Shadow(sprite))
+        # Render the level map
         self.background, overlays = self.level.render()
-        for pos, image in overlays.iteritems():
-            x, y = pos
+        # Add the overlays for the level map
+        for (x, y), image in overlays.iteritems():
             overlay = pygame.sprite.Sprite(self.overlays)
             overlay.image = image
             overlay.rect = image.get_rect().move(x*24, y*16-16)
@@ -324,7 +331,7 @@
         keys = pygame.key.get_pressed()
 
         def pressed(key):
-            """Check if the key is pressed."""
+            """Check if the specified key is pressed."""
 
             return self.pressed_key == key or keys[key]
 
@@ -350,13 +357,16 @@
         """Run the main loop."""
 
         clock = pygame.time.Clock()
+        # Draw the whole screen initially
         self.screen.blit(self.background, (0, 0))
         self.overlays.draw(self.screen)
         pygame.display.flip()
+        # The main game loop
         while not self.game_over:
-            # Don't clear shadows and overlays.
+            # Don't clear shadows and overlays, only sprites.
             self.sprites.clear(self.screen, self.background)
             self.sprites.update()
+            # If the player's animation is finished, check for keypresses
             if self.player.animation is None:
                 self.control()
                 self.player.update()
@@ -366,10 +376,13 @@
             self.shadows.draw(self.screen)
             dirty = self.sprites.draw(self.screen)
             # Don't add ovelays to dirty rectangles, only the places where
-            # sprites are need to be updated.
+            # sprites are need to be updated, and those are already dirty.
             self.overlays.draw(self.screen)
+            # Update the dirty areas of the screen
             pygame.display.update(dirty)
+            # Wait for one tick of the game clock
             clock.tick(15)
+            # Process pygame events
             for event in pygame.event.get():
                 if event.type == pg.QUIT:
                     self.game_over = True
@@ -380,5 +393,4 @@
 if __name__ == "__main__":
     pygame.init()
     pygame.display.set_mode((424, 320))
-    # font = pygame.font.Font("MgOpenModataBold.ttf", 11)
     Game().main()