Alright, so at this point, if you've been following along, you have a version of Pacman that you can play. Sure, it may not be the most fun version of the game you've played, but it works. From here on out we don't want to change anything we've done so far (for the most part). We don't want to change the way the game is played or anything. All we want to do now is make it look good. In order to do that we'll have to use graphics other than the primitives we've been using. Circles and lines are great for prototyping, but now that we have a working version of Pacman, we can use actual images. In order to use images, we'll have to understand how images work within pygame.
The first thing we need to understand when we're drawing images to the screen is how they are positioned. We were using pygame's circle to draw most of our graphics before. The position of a circle is measured from the center of the circle as shown in the blue circle where it's position is (x1, y1). For all of the images we're going to use, the position of the image is measured from the top left corner of the image as shown in the red square where it's position is (x2, y2). This is because all images are rectangles. It doesn't matter if you have an image of a circle, the computer will just see it as a rectangle. For now we just need to remember that images aren't positioned on the screen using the center of the image, but rather the top left corner of the image. That's why Pacman and the ghosts appear off-center in the image above, whereas before they were positioned correctly. When we start using images to draw Pacman and the ghosts, we need to remember this so we can make adjustments. I'll bring it up again when we get there.
For the red square though, if the position we've been using is (x1, y1) (when it was a circle) and the square has a width of W and a height of H, then we can find (x2, y2) by subtracting (W/2, H/2) from (x1, y1). Again, we'll revisit this.
We can go ahead and make all of the image files we want for all of our objects. Nothing wrong with that, it's just that's not the best practice. Even the simplest of games use a lot of images for various things. Not only that, but a lot of images have animation. If your character has 8 frames of animation while walking, then that's 8 separate images just for your one character, and that's just for walking. If you want to animate your character running, or jumping, or anything else, then that's even more images just for one character. Add up all of the animations you want to do for all of the characters in the game, and you're looking at possibly hundreds of images. That's a lot, and it's not very efficient to handle images as separate files because you have to open and close every image. It's better to group all of the images into one file, and just extract the image you want from this file. This file is referred to as a spritesheet, because it's a sheet that contains sprites.
On this sheet you'll see every image that we will use in our Pacman game. At least every image we'll use during the actual gameplay. I should begin by explaining each image and the reasoning behind them. You'll notice that I have several images for Pacman and Ms Pacman. I want the player to choose to play as either Pacman or Ms Pacman. You see that I have images of Pacman and Ms Pacman pointing in all four directions. This seems wasteful since I can just rotate a single image during the game. However, rotating an image is expensive and I generally try to avoid doing that unless it is absolutely necessary. So instead of bothering with rotation, I just provide the images pre-rotated. I'm sure in a simple game like Pacman, there wouldn't be any issue with rotation, but in general I try to avoid it. I have the images of all four ghosts and their FREIGHT mode and SPAWN mode images. I have all of the fruit, and Pacman's death sequence. You can use a different death sequence for Ms Pacman or just use this same one. Below that are all of the images we'll use to make all of the mazes in the game.
I numbered the columns and rows in this image if each column and row is 16 pixels. That means the Pacman and ghost sprites are 32x32 pixels. Let's say I want to extract the image of Pacman facing down with his mouth wide open. Since his position in the spritesheet is based on his upper left corner, he's at location (4, 2).
This isn't a large class, but it needs a lot of explaining.
We'll define two constants here since this is the only class that needs them. You can put them in the constants.py file if you want though, it was just easier for me to put them here. These define the base tile size of the spritesheet image. The spritesheet image I made has 16x16 tiles. If your image uses larger or smaller tiles, then you'll need to change these values here. You can still say that they can be a different size in your game though and I'll show you that in a second. But these two constants just define the tiles in your image and not your game. Remember, if you want to have bigger or smaller tiles in your game, then you need to adjust the TILEWIDTH and TILEHEIGHT in the constants.py file.
The first thing we need to do is "load" the image in so we can use it. We then need to set a transparent color. You'll notice that the background of the image is pink. That's going to be my transparent color. It's just a color that the game ignores when drawing the sprite to the screen. If it didn't have a transparent color, then all of the sprites would look like rectangles. I get the transparent color by sampling the image at the upper left corner because I know that that pixel is my transparent color. If you're using your own spritesheet you may need to pick another spot for your transparent color, or just define it explicitly.
Next, we modify the size of the spritesheet if we need to. If you defined different values for your TILEWIDTH and TILEHEIGHT other than what your image has as defined by BASETILEWIDTH and BASETILEHEIGHT then you need to modify the size of the spritesheet to reflect that. If the values are the same, as in our game, then the width and height of the spritesheet won't change.
The getImage method extracts an image from the spritesheet and returns it to whoever is asking for it. We need to specify the x and y location of the image on the spritesheet and how large it is with the width and height parameters.
Now that we have that class defined we can create classes that inherit from it. We'll create a class that will contain references to all of the Pacman sprites we'll need to use in our game. I'm making a new class instead of defining it in the Pacman class because it's good to decouple them. That way the Pacman class doesn't depend on a specific spritesheet file.
To get an image from the spritesheet we just need to define the column and row that image appears on. The image appears in the column and row that it's upper left corner pixel is in. So you can see with the getStartImage method (which returns the image we want Pacman to begin the game with), we get the image in column 8 and row 0. If you look on the spritesheet above you'll see that's the yellow circle.
We'll do the same thing for the ghosts, except we have 4 ghosts to worry about. That's not a problem, because the only difference between our ghosts in the spritesheet is the row their images appear in. Blinky's sprites are in row 4, Pinky's sprites are in row 6, and so on. So as long as we have the name of the ghost then we know which row to extract from. We'll just use their first image as their starting image for now.
And of course we'll need to do the same thing for the fruits as well. Currently we're only using one of the fruits. Given the coordinates you see that the fruit is the cherry's.
In the Entity class we'll create a new variable called image. This will hold whatever image we want to display from the spritesheet that represents this entity.
In the render method we'll draw the image instead of the pygame circle as long as the image isn't None. Otherwise we'll just draw what we've been drawing.
For these three classes we'll need to import their respective sprite classes, create an object from them and then set the initial image.
So running the program now and you should see some changes. Pacman still looks like a yellow circle, albeit larger and off center. The ghosts are there and the fruit shows up as cherries.
You can download the spritesheet using the link below.