Let’s Build a Simple Maze Game with Pygame!

Hello aspiring game developers and Python enthusiasts! Have you ever wanted to create your own game, even a simple one? Today, we’re going to dive into the exciting world of Pygame and build a fun, basic maze game. Don’t worry if you’re new to game development or even Python; we’ll break down every step into easy-to-understand chunks.

What is Pygame?

Before we start, let’s quickly explain what Pygame is.
Pygame is a popular set of Python modules designed for writing video games. Think of it as a toolkit that provides functions and classes to handle graphics, sounds, user input (like keyboard presses or mouse clicks), and other common game development tasks. It makes it much easier to create games without having to worry about the really low-level details.
A module or library is simply a collection of pre-written code that you can use in your own Python programs to perform specific actions, saving you time and effort.

What Will We Build?

Our goal is to create a simple maze game where:
* You control a player character (a colored square).
* You navigate through a static maze.
* The player cannot pass through walls.
* The game ends when you reach a specific “exit” point.

This project is fantastic for beginners because it covers fundamental game development concepts like drawing shapes, handling user input, creating a game loop, and basic collision detection.

Getting Started: Prerequisites

Before we write any code, you’ll need two things:

  1. Python: Make sure you have Python installed on your computer. You can download it from the official Python website (python.org). Python 3.6 or newer is recommended.
  2. Pygame: Once Python is installed, you can install Pygame using pip, Python’s package installer. Open your terminal or command prompt and type:

    bash
    pip install pygame

    If you’re using a specific Python version, you might use pip3 install pygame.

That’s it for the setup! Now, let’s get into the code.

The Foundation: Setting Up Our Pygame Window

Every Pygame application starts by initializing Pygame and setting up a display window.

Step 1: Import Pygame and Initialize

First, we import the Pygame library and initialize all its modules.

import pygame

pygame.init()

Step 2: Define Game Constants

It’s a good practice to define constants for things like screen dimensions, colors, and player speed. This makes your code cleaner and easier to modify.

Constants are values that don’t change throughout the program’s execution. We often write them in UPPER_CASE to distinguish them from regular variables.

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

CELL_SIZE = 40

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0) # For our player!

PLAYER_SIZE = CELL_SIZE - 10 # Slightly smaller than a cell
PLAYER_SPEED = CELL_SIZE # Player moves one cell at a time

WALL_THICKNESS = 5

Step 3: Create the Game Window

Now, we create the actual window where our game will be displayed.

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

pygame.display.set_caption("Simple Maze Game")

Designing Our Maze

How do we represent a maze in code? A common and easy way for simple grid-based games is to use a 2D list (or “list of lists”) where each element represents a cell in the maze. We can use numbers to signify different types of cells:
* 0: Path (empty space)
* 1: Wall
* 2: Player start position
* 3: Exit point

Let’s define a simple maze layout. Remember, each 1 (wall) will be CELL_SIZE by CELL_SIZE pixels.

maze_layout = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
    [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1],
    [1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
    [1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
    [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
    [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]

MAZE_WIDTH_CELLS = len(maze_layout[0])
MAZE_HEIGHT_CELLS = len(maze_layout)

SCREEN_WIDTH = MAZE_WIDTH_CELLS * CELL_SIZE
SCREEN_HEIGHT = MAZE_HEIGHT_CELLS * CELL_SIZE
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) # Re-set screen after recalculating

The Game Loop: The Heart of Every Game

Every game has a game loop. This is a while loop that runs continuously, doing three main things:
1. Event Handling: Checking for user input (like keyboard presses) or system events (like closing the window).
2. Updating Game State: Changing positions of objects, checking for collisions, etc.
3. Drawing: Redrawing everything on the screen in its new position.

player_grid_x = 0
player_grid_y = 0

for y in range(MAZE_HEIGHT_CELLS):
    for x in range(MAZE_WIDTH_CELLS):
        if maze_layout[y][x] == 2:
            player_grid_x = x
            player_grid_y = y
            break # Found player, no need to search more in this row
    if maze_layout[player_grid_y][player_grid_x] == 2: # If player found, break outer loop too
        break

player_pixel_x = player_grid_x * CELL_SIZE + (CELL_SIZE - PLAYER_SIZE) // 2
player_pixel_y = player_grid_y * CELL_SIZE + (CELL_SIZE - PLAYER_SIZE) // 2

game_over = False
running = True # This variable controls our game loop

while running:
    # 1. Event Handling
    for event in pygame.event.get():
        if event.type == pygame.QUIT: # If the user clicks the 'X' to close the window
            running = False # Stop the game loop

        if event.type == pygame.KEYDOWN and not game_over: # If a key is pressed and game is not over
            # Try to move the player based on arrow keys
            new_player_grid_x = player_grid_x
            new_player_grid_y = player_grid_y

            if event.key == pygame.K_LEFT:
                new_player_grid_x -= 1
            elif event.key == pygame.K_RIGHT:
                new_player_grid_x += 1
            elif event.key == pygame.K_UP:
                new_player_grid_y -= 1
            elif event.key == pygame.K_DOWN:
                new_player_grid_y += 1

            # Check for collision with walls
            # "Collision detection" is the process of figuring out if two objects in a game are overlapping or touching.
            if 0 <= new_player_grid_x < MAZE_WIDTH_CELLS and \
               0 <= new_player_grid_y < MAZE_HEIGHT_CELLS and \
               maze_layout[new_player_grid_y][new_player_grid_x] != 1: # 1 is a wall

                player_grid_x = new_player_grid_x
                player_grid_y = new_player_grid_y

                # Update player's pixel position
                player_pixel_x = player_grid_x * CELL_SIZE + (CELL_SIZE - PLAYER_SIZE) // 2
                player_pixel_y = player_grid_y * CELL_SIZE + (CELL_SIZE - PLAYER_SIZE) // 2

                # Check if player reached the exit
                if maze_layout[player_grid_y][player_grid_x] == 3:
                    game_over = True
                    print("You won!")

    # 2. Drawing
    # Clear the screen by filling it with a background color
    screen.fill(BLACK)

    # Draw the maze
    for y in range(MAZE_HEIGHT_CELLS):
        for x in range(MAZE_WIDTH_CELLS):
            cell_type = maze_layout[y][x]
            rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE) # A `Rect` object represents a rectangular area.

            if cell_type == 1: # Wall
                pygame.draw.rect(screen, BLUE, rect)
            elif cell_type == 3: # Exit
                pygame.draw.rect(screen, GREEN, rect)

    # Draw the player
    player_rect = pygame.Rect(player_pixel_x, player_pixel_y, PLAYER_SIZE, PLAYER_SIZE)
    pygame.draw.rect(screen, YELLOW, player_rect)

    # If game is over, display a message
    if game_over:
        font = pygame.font.Font(None, 74) # `pygame.font.Font` creates a font object; `None` uses default font.
        text = font.render("YOU WIN!", True, GREEN) # `render` creates a surface with the text. `True` is for anti-aliasing.
        text_rect = text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2))
        screen.blit(text, text_rect) # `blit` draws one image onto another.

    # Update the full display Surface to the screen
    # This makes everything we've drawn actually appear on the window.
    pygame.display.flip()

pygame.quit()

How the Code Works (Detailed Explanations)

Let’s break down some key parts of the code:

  • pygame.init(): This function initializes all the Pygame modules necessary for game development. Without it, many Pygame functions won’t work.
  • pygame.display.set_mode((width, height)): This creates the actual window where your game will run. The (width, height) tuple specifies the size in pixels. A pixel is the smallest unit of a digital image or display.
  • pygame.display.set_caption("Title"): This sets the text that appears in the title bar of your game window.
  • while running:: This is our main game loop. The running variable (a boolean – true/false value) controls whether the loop continues or stops. When running becomes False, the loop finishes.
  • for event in pygame.event.get():: Pygame collects all user actions (like key presses, mouse clicks, closing the window) as “events.” This loop goes through each event that happened since the last frame.
  • event.type == pygame.QUIT: This checks if the specific event was the user clicking the ‘X’ button to close the window.
  • event.type == pygame.KEYDOWN: This checks if a key was pressed down.
  • event.key == pygame.K_LEFT: This checks which key was pressed (e.g., the left arrow key). Pygame provides constants for many keys (e.g., K_RIGHT, K_UP, K_DOWN).
  • maze_layout[new_player_grid_y][new_player_grid_x] != 1: This is our simple collision detection. Before moving the player, we check the maze_layout at the target cell. If it’s 1 (a wall), we don’t allow the move.
  • screen.fill(BLACK): Before drawing anything new in each frame, we “clear” the screen by filling it with a background color (black in our case). This prevents “ghosting” effects where old drawings remain.
  • pygame.Rect(x, y, width, height): Pygame uses Rect objects to represent rectangular areas. These are very useful for drawing and for collision detection. x and y are the coordinates (position on the screen).
  • pygame.draw.rect(surface, color, rect_object): This function draws a rectangle on a given surface (our screen), with a specified color, and at the position and size defined by the rect_object.
  • pygame.display.flip(): This command updates the entire screen to show everything that has been drawn since the last flip() or update(). Without this, you wouldn’t see anything!
  • pygame.quit(): This uninitializes all Pygame modules and cleans up resources, which is good practice when your program finishes.

Running Your Game!

Save the code above as a Python file (e.g., maze_game.py). Then, open your terminal or command prompt, navigate to the directory where you saved the file, and run it using:

python maze_game.py

You should see a window pop up with your maze! Use the arrow keys to move your yellow square player through the maze. Try to reach the green exit square.

Ideas for Improvement

This is a very basic maze game, but it’s a great starting point! Here are some ideas to make it even better:

  • More Complex Mazes: Implement an algorithm to generate random mazes.
  • Timer/Score: Add a timer to see how fast the player can complete the maze, or a score.
  • Sound Effects: Add sounds for movement, reaching the exit, or hitting a wall.
  • Different Player Graphics: Instead of a square, use an image for the player.
  • Multiple Levels: Create an array of maze_layouts and switch between them.
  • Smoother Movement: Instead of moving one CELL_SIZE at a time, move a few pixels per frame for smoother animation. (This requires more complex collision detection).
  • Start Screen/End Screen: Add proper title screens and game over screens.

Conclusion

Congratulations! You’ve just built your very own simple maze game using Pygame. You’ve learned about setting up a game window, handling user input, drawing shapes, and the essential game loop. These are fundamental skills that will serve you well in any future game development adventures. Keep experimenting, keep coding, and most importantly, have fun!


Comments

Leave a Reply