Blogroll

Forums

python

Creating a Game in Python Using PyGame – Part Two – Creating a level

All right in Part one we actually created a semi-working almost-game, in part two we’re going to go a bit further, in part two we’re going to add the walls that will make help make PyMan (our python based PacMan clone) an actual game.

Note: Part one has been mistakenly half-deleted by me, so it is not fully available at this time. I am working to re-write it so it should be up soon.

Note: I also changed the images that I used in Part one so now we have a new smaller snake.

What we are going to do is base some of our level code on a great PyGame tutorial over at DevShed specifically the idea of the level layout. This is something that has always puzzled me about games, how they create their levels? I’ve read some articles and beginnings of books but I’ve never really be able to understand it, but the multidimensional array approach discussed in the DevShed article is nothing if not simple.

So the idea is that our game world, or game board, will basically be a grid of 24×24 pixel items, and we will have a multidimensional array or list that we will used to describe it.

So for example lets say we have a grid like this:

0 0 0 0 0
1 1 1 1 1
1 0 0 0 1
1 1 1 1 1
0 0 0 0 0

Now that grid may result in the following:

Where 0 is nothing and where 1 is a blue square. We could then add 2’s if we wanted to and make those red squares, or anything else that we wanted. So that is basically how our levels are going to be defined.

The first thing we are going to do is add a new file to our project called levelBase.Py, in that we will define a class called level which is as follows:

[code lang=”python”]
class Level:
“””The Base Class for Levels”””
def getLayout(self):
“””Get the Layout of the level”””
“””Returns a [][] list”””
pass
def getImages(self):
“””Get a list of all the images used by the level”””
“””Returns a list of all the images used. The indices
in the layout refer to sprites in the list returned by
this function”””
pass
[/code]

Pretty simple not much going on there, getLayout returns a multidimensional list of numbers that represents the level, and getImages returns a list of the images used by the level.

It may seem complicated but once you see our first level it will start to make some sense. The next thing to do is add a file called level001.py to our project. I waffled on this a bit, I was unsure if it was better to have one file called levels.py that contained classes called level001, or make each level a separate file. In then end I went with each level being a separate file, just because it separates things, but I don’t really know if one method is superior over the other. Here is the code for level001.py:

You can see that in the __init__ function the level defines three values: BLOCK, SNAKE, and PELLET. These values correspond to the numbers in the list returned by getLayout(). So wherever there is a 1 in the list, that will correspond to a BLOCK, a 0 is a PELLET, and a 2 is our snake. In this example I use 9 to represent a blank square.

You’ll also notice that the getImages() function returns a list of images that are indexed by those same values. So in the list returned by getImages(), position [BLOCK] is the block image.

So far so good, the next thing we are going to do is add a base class for all of our sprites. In Part one we didn’t need to do this since we only had two sprites, but in this lesson (as we can see above) we are going to have at least three and that number will continue to grow. So we will add another file to our project called basicSprite.py, this will be the base class that we use for all of our sprites. It doesn’t do much besides add a few initialization features to pygame.sprite.Sprite class. The code for basicSprite.py is as follows:

As you can see all that basicSprite.Sprite does is really add a few initialization values: centerPoint and image. Image is the image that the sprite will use and centerPoint is the center point of the rect, it controls where the image will be placed. The nice thing about using the center point is that it is correct regardless of the images size. So if you have a 16×16 image and a 24×24 image the center point will be the same, but the topleft point (which we used in part one) will be different for both.

Because of the addition of the basicSprite.Sprite we are going to have to change our snake sprite from part one, we are also going to move the sprite into it’s own file since it will start getting a bit complicated as time goes on. So we will add the file snakeSprite.py to our project. snakeSprite.py will contain a class called Snake, which will be based off of our basicSprite.Sprite class:

You’ll notice that the initialization is similar to the initialization in part one except this time we are using the basicSprite.Sprite class to set up our image and rect. Then we initialize the same values, except we have introduce two new ones, self.xMove and self.yMove. The reason for this is we are going to change the way that we move our snake.

If you tried out the last tutorial you’ll see that just moving the character on the key down events didn’t work all that well. It worked well enough but if you start pressing more then one key are the same time pyGame seemed to get a bit confused.

In order to combat this we will use another tip taken from the DevShed tutorial and pay attention to both the DOWN and UP events associated with keystrokes. We will also switch to using the update() function to update the snake.

So what we are going to is add two functions to the Snake class MoveKeyUp() and MoveKeyDown(). What these functions will do is adjust the xMove and yMove variables depending on what key has been pressed. Then when the update function is called the Snake will update it’s positions according the values of xMove and yMove. It may seem a bit complicated but it’s actually pretty simple:

[code lang=”python”]
def MoveKeyDown(self, key):
“””This function sets the xMove or yMove variables that will
then move the snake when update() function is called. The
xMove and yMove values will be returned to normal when this
keys MoveKeyUp function is called.”””

def MoveKeyUp(self, key):
“””This function resets the xMove or yMove variables that will
then move the snake when update() function is called. The
xMove and yMove values will be returned to normal when this
keys MoveKeyUp function is called.”””

As you can see when we press the K_LEFT key down we add -x_dist pixels to the xMove variable. This will then be used to move the snake in the update() function. Then when we let up the K_LEFT key we remove the -x_dist values that we added to xMove. For example if you hold down K_LEFT for a moment xMove will before -3 (move three pixels to the left every time the update() function is called), then when you let the K_LEFT key up xMove will become 0 (don’t move).

Now the update() function is function in pyGames Sprite class. It really doesn’t do anything besides providing a convenient way to update a group of sprites. It will also pass the same parameters to all of your sprites in the group. So if you have a bunch of sprites that need to know the current time when you update them it’s a good idea to add them to the same group and then update them all at once by calling the group.update() function and pass the time.

You’ll also notice that there is a second parameter passed to this function called block_group, this is the group of all of our block sprites. We use that group to do a hit test. If the snake has collided with a block after we have adjusted it’s rect, we reverse the movement.

No we are going to go back to our PyManMain class and edit the LoadSprites() function. We are going to use this function to load the level that we created in level001.py and all of the sprites that we need:

Since this function is a bit more complicated I will try to explain it in more detail. The first thing that we do is calculate the x_offset and the y_offset, using the value BLOCK_SIZE. BLOCK_SIZE is simple a global created in pyMan.py that refers to the size that each block will be. So in this case, BLOCK_SIZE = 24. The x_offset and y_offsets are the center points of a block. We use these offsets to position the sprites that we will create.

After that we load the level: we create an instance of the level001 class, and then get it’s layout and it’s image list. Then we create our pellet and block groups, these will be sprite groups that contain all the pellet or block sprites.

The next step is to loop through the layout of the level and create the necessary sprites at each position. We first calculate the current blocks center point, just to make the code a bit cleaner, and then we check to see what the current position in the layout is. It it’s a BLOCK position we create new block sprite and add it to the block_sprites group. If it’s a PELLET position, we create a new pellet sprite and add it to the pellet_sprites group.

The only sprite that we treat a bit differently is the snake sprite, since we will only have one snake per level, we only create one. And if the level designer mistakenly added two snakes, we will use the second snake as the snakes position.

You’ll also notice that both the pellet and the block sprites are simply basicSprites.Sprite’s since they don’t really do anything else besides get drawn to the screen.

Now we simply have to adjust our MainLoop() function to take into account all of the changes that we have made to our game:

[code lang=”python”]
def MainLoop(self):
“””This is the Main Loop of the Game”””

“””Load All of our Sprites”””
self.LoadSprites();

“””Create the background”””
self.background = pygame.Surface(self.screen.get_size())
self.background = self.background.convert()
self.background.fill((0,0,0))
“””Draw the blocks onto the background, since they only need to be
drawn once”””
self.block_sprites.draw(self.background)

It’s a lot of code, but it’s not really that different then the code was in part one. An obvious difference is the way in which we adjust the snakes position, using the MoveKeyUp, MoveKeyDown, and update function. Everything else is basically identical to the way that it was in part one.

The only other difference is the fact that we have to draw the block sprites. Since the blocks are static and will never be updated, we are going to draw then to the background surface. This will save us a bit of drawing time when we draw.

When we run the code now we are greeted with the simple PyMan level that we saw at the beginning of this post. Now this isn’t close to being a finished game as there is no level two, and there are no bad guys, but our simple game it starting to take shape.

In the next part of this pyGame series I hope to add the bad guys to the level and the idea of the super pellets. If you want to download the code for this post you can do so here.

when I run this program, the following message apeears:
Traceback (most recent call last):
File “C:\Documents and Settings\GUILHERME\Desktop\game.py”, line 9, in ?
ball = image.load(“ball.bmp”)
error: Couldn’t open ball.bmp

actually, it depends; on your system it could just as well be that the BMP is expected in the working directory from which you run the script. which is bad … that code is just wrong, overly simplistic. of course if you run it from a shell and first cd to where the file is, no problem. but it’s wrong to assume that this will always be the case.

Have you ever used ‘surfarray’? And have you ever made a program where the background scrolls under the character in the center? I’m still getting used to a lot of pygame functions and I’ve been trying to pull bits and pieces of ideas from your ‘pyman’ tutorials. My head hurts after a while, especially when I think my code should do something but it doesn’t.

Do find that it works well for you to use separate files? I’m just not sure how values get shared between files because I’m not seeing a lot of return functions.

Thanks for the kind words. I would consider going into more depth on how to use Eclipse, except for the fact that I no longer use Eclipse! I liked Eclipse well enough, it’s just that I found it to be slow after a while.

I’ve been doing most of my current programming on Linux and have started using Geany as my IDE.

Thanks for the information I just tried this out on my system here and I am getting a CRC error as well. I think the contents of most of the project should be all right though. I’ll try to look for the original zip file.

Hi, I compiled the game and ran it, but the snake was going too fast. I even made x_dist and y_dist to 1, but it was still moving very fast. When I had a look at your one I noticed there was no frame rate set on it.

can you please help with the idea of creating a reflex action using pacman to eat all the food in the pellet and yet don.t get beaten by the ghost, say two ghost. I would need your assistance in python.