Ready to take Python coding to a new level? Explore our Python Code Generator. The perfect tool to get your code up and running in no time. Start now!
In this tutorial, we'll walk through the process of creating a Pacman game from scratch using Python and the Pygame
library. Pacman is a classic arcade game loved by many, and recreating it can be a fun and educational project to sharpen your programming skills.
Throughout this guide, we'll cover everything you need to know, from setting up the game environment to implementing player controls, enemy behaviors, scoring mechanics, and more. By the end of this tutorial, you'll have a fully functional Pacman game you can play and share with others.
Table of Contents:
Let's start by making sure Pygame
is installed on your computer, head to your terminal and install pygame
module using pip
.
After that, create a directory for the game and create the following .py
file inside it; settings.py
, animation.py
, display.py
, main.py
, pac.py
, ghost.py
, cell.py
, berry.py
, and world.py
. Here's the assets
we're going to use in this game, by the way:
Now, we can start coding. Let's define our game variables and useful external functions in
settings.py
:
Next, let's make a class responsible for accessing the sprites or the images we will use. In animation.py
, import the pygame
and the walk
from the os
module. Then, define a function called import_sprites()
:
The import_sprite()
function takes one argument, path
, which is the directory path where the sprite images are located. The surface_list
is an empty list that will hold the loaded image surfaces.
The os.walk(path)
is used to traverse the directory specified by the path and returns a generator that yields a tuple for each directory it visits. We use _
and __
as throwaway variables to denote the current directory path and its subdirectories. img_file
is a list of filenames in the current directory.
For each image
filename in img_file
, the full path to the image file is constructed by combining the path with an image. The pygame.image.load()
function is then used to load the image from this full path into an image surface (img_surface
). The .convert_alpha()
method converts the image to a format that is optimized for display on the screen with per-pixel transparency, then the loaded image surface (img_surface
) is then appended to the surface_list
.
Let's create another code that will display the game information. In display.py
create a class named Display
:
The Display
class will render on our game screen the info, such as the player's remaining life (show_life
), current level (show_level
), and the player's current score (show_score
).
The Pacman Game has 2 different character types: the 4 ghosts and Pacman itself. For that, we are going to make 2 different classes for both character type:
In ghost.py
, create the Ghost
class. The Ghost
class in Python uses the Pygame
library to create a sprite representing a ghost character in a game.
The constructor (__init__
) initializes the ghost with its starting position (row
, col
) and color (color
). abs_x
and abs_y
store the absolute pixel position of the ghost based on its row and column multiplied by CHAR_SIZE
. The rect
defines the rectangular area occupied by the ghost on the screen using pygame. Rect
. The move_directions
is a list of tuples representing possible movement directions (left
, up
, right
, down
). The moving_dir
initializes the ghost's movement direction as "up". The img_path
and img_name
specify the path to the ghost's image file (img_name
is based on moving_dir
). The image loads the ghost's image and scales it to CHAR_SIZE
using pygame.image.load()
and pygame.transform.scale()
. The mask
creates a collision mask for the ghost's image using pygame.mask.from_surface()
.
The move_to_start_pos()
set the ghost's position back to its initial coordinates (abs_x
, abs_y
). The is_collide(x, y, walls_collide_list)
checks if the ghost collides with any walls based on a temporary movement (x
, y
).
Let's add 2 more methods inside our Ghost
class for animating and updating the ghost and its position:
The _animate()
updates the ghost's image (image
) based on its current movement direction (moving_dir
). The update(walls_collide_list)
updates the ghost's position and behavior based on collision with walls (walls_collide_list
). It checks available movement directions (available_moves
) by iterating through self.keys
(['left', 'right', 'up', 'down']
) and determining if they do not cause a collision. It randomizes the self.moving_dir
if available moves are limited or randomly chosen. Then moves the ghost (self.rect.move_ip(self. direction)
) if the movement direction is valid. It handles teleportation to the opposite side of the screen if the ghost reaches the screen boundary.
Let's create a pac.py
file now:
In pac.py
, create another class named Pac
. The __init__
initializes the Pacman's position (abs_x
, abs_y
) based on row and column values, along with animation-related attributes.
The _import_character_assets()
load Pacman's animations. The status
initializes Pacman's status, which is idle
as default, the life count (life
), and the score (pac_score
).
The _import_character_assets()
method imports Pacman's animation sprites from specified directories (assets/pac/
) for various actions (up
, down
, left
, right
, idle
, power_up
). The walls_collide_list
checks if Pacman collides with walls after a specified movement (x
, y
).
The move_to_start_pos()
resets Pacman's position to its initial coordinates. The animate(pressed_key, walls_collide_list)
manages Pacman's animation based on pressed keys and collision detection with walls, then updates Pacman's status and position based on movement direction and collisions. In the update()
method, the immune
manages Pacman's immunity state based on a timer (immune_time
) and updates Pacman's position rect based on its image.
Now that we have game characters, let's create classes for game components. In this game, we need a Berry
class, which represents the game objectives, and a Cell
class, which will serve as walls in our game.
Here's our short code for the Berry
class:
And here's our short code for the Cell
class.
Now that we have game characters, components, and certain requirements, we can start connecting and using all of them. In world.py
create a class and name it World
. This class will serve as the game world.
The World
class initializes the game world with the provided Pygame screen (screen
). Then, it creates sprite groups (player
, ghosts
, walls
, berries
) to manage game entities. The self.display
will be instantiated to Display object/s for rendering game information on the screen. Then it calls _generate_world()
to populate the game world with entities based on a predefined map (MAP
).
Now, let's add more methods for making our game world, making new levels, and restarting the game:
The _generate_world()
populates the game world based on a predefined map (MAP
). It adds walls, small berries, large berries (with power-up), ghosts, and the player (Pacman) to their respective sprite groups (walls
, berries
, ghosts
, player
) based on characters in MAP
. The walls_collide_list
creates a list containing rectangles of wall sprites for collision detection.
The generate_new_level()
regenerates berries (small and large with power-up) on the map to create a new game level. It pauses the game briefly to allow for a smooth transition to the new level.
The restart_level()
resets the current game level to its initial state after the player loses a life or restarts the game. It removes all berries from the level and adds new ones, resets ghost positions, and resets player attributes (score
, life
, position
, status
).
Now, let's also add a method called _dashboard
, which will be responsible for rendering the game information on the screen.
Add another method to the World
class, and name it _check_game_state
:
The _check_game_state
method verifies if the player's remaining lives (self.player.sprite.life
) have reached zero. If the player has no remaining lives (life == 0
), set self.game_over
to True, indicating that the game is over. If all berries (len(self.berries) == 0
) have been collected and the player still has lives remaining (self.player.sprite.life > 0
), increments the game_level
by one (self.game_level += 1
), indicating progression to the next level. The movement speed of ghosts increases (ghost.move_speed
) based on the current game level and resets the positions of ghosts and the player to their starting positions (move_to_start_pos()
). It generates a new level by regenerating berries using generate_new_level()
.
And for the final method for our World
class, let's create the update
method. The update
method manages player movement and interactions with game objects.
It checks to ensure the player sprite remains within the screen boundaries, teleporting the player to the opposite side if they reach the left or right edges for a wrap-around effect (self.player.sprite.rect.right <= 0
or self.player.sprite.rect.left >= WIDTH
).
During each update cycle, the method also detects interactions between the player sprite and berry sprites (self.berries.sprites()
). If a collision occurs (self.player.sprite.rect.colliderect(berry.rect)
), the player's score (pac_score
) is updated based on the berry type (small or large with power-up), and the berry sprite is removed from the berries
group using berry.kill()
.
Similarly, collisions between the player sprite and ghost sprites (self.ghosts.sprites()
) are monitored. It handles these interactions based on the player's immunity state (self.player.sprite.immune
). If the player is not immune, their life count (self.player.sprite.life
) is reduced, triggering a respawn (self.reset_pos = True
). If immune, the ghost's position resets, and the player's score increases.
To manage game state transitions, the update
method calls _check_game_state()
to determine if the game is over or if the level has been completed based on player actions. Rendering updates are then performed by iterating and updating wall sprites, berry sprites, and ghost sprites on the game screen.
The method also updates and draws the player sprite (self.player
) on the screen.
Lastly, the update
method handles user interface updates by displaying a game over the message (self.display.game_over()
) when the game ends and updating the dashboard (_dashboard()
) to show critical game information such as score, remaining lives, and level. If the game is over and the player presses the restart key (pygame.K_r
), the game state is reset by restarting the game level (self.restart_level()
).
To run our game, let's create our last class, which is the Main
class. This class simply runs the game.
And now, we are done coding!! To try our game, simply run python main.py
or python3 main.py
on yout terminal once you're inside our project directory. Here are some game snapshots:
In conclusion, creating a Pacman game in Python is not only a rewarding project but also a great way to explore game development concepts and enhance your programming abilities. Through this tutorial, you've learned how to use Python and Pygame to build a game that captures the essence of the classic Pacman experience. By implementing player controls, enemy AI, collision detection, and scoring mechanics, you've gained practical insights into game development techniques that can be applied to other projects.
You can check the complete code here.
Here are some relevant game development tutorials:
Happy coding ♥
Save time and energy with our Python Code Generator. Why start from scratch when you can generate? Give it a try!
View Full Code Switch My Framework
Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!