Python and Pygame: Building a Tower Defense Game

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setup
  4. Creating the Game Window
  5. Drawing the Game Grid
  6. Adding Towers
  7. Enemies and Waves
  8. Collision Detection
  9. Game Over and Score
  10. Conclusion

Introduction

In this tutorial, we will be building a tower defense game using Python and the Pygame library. Tower defense games involve strategically placing defensive structures (towers) to prevent waves of enemies from reaching their target. By the end of this tutorial, you will have a complete tower defense game that you can play and expand upon.

Prerequisites

To follow along with this tutorial, you should have basic knowledge of Python programming. Familiarity with object-oriented programming concepts will also be helpful. Additionally, you will need to have Python and Pygame installed on your system.

Setup

First, let’s make sure that Python and Pygame are installed on your system. Open your terminal or command prompt and run the following commands: python python --version This command will check if Python is installed and display its version. If Python is not installed, you can download and install it from the official Python website.

Next, let’s install Pygame. Run the following command: python pip install pygame This command will install Pygame via pip, the package installer for Python.

Creating the Game Window

Let’s start by creating the game window using Pygame. We will import the necessary modules and initialize the Pygame library. Create a new Python file and add the following code: ```python import pygame

pygame.init()

# Set up the game window
window_width, window_height = 800, 600
window = pygame.display.set_mode((window_width, window_height))
pygame.display.set_caption("Tower Defense Game")

# Game loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Update game logic here

    # Draw game elements here

    pygame.display.flip()

pygame.quit()
``` In this code, we first import the `pygame` module and initialize it using `pygame.init()`. We then set up the game window by specifying its width and height, creating a display surface, and setting the window caption.

Next, we create a game loop that will keep the window open until we quit the game. Inside the game loop, we handle the quit event by setting the running variable to False when the player closes the window.

To see the game window, run the Python script. You should see a window with the title “Tower Defense Game”. However, it will be empty because we haven’t added any game elements yet.

Drawing the Game Grid

Now let’s draw the game grid, which will serve as the foundation for placing towers and moving enemies. We’ll use a grid-based system, where each cell represents a position in the game world. ```python # Set up the game grid grid_width, grid_height = 10, 10 cell_size = min(window_width // grid_width, window_height // grid_height)

def draw_game_grid():
    for x in range(0, window_width, cell_size):
        pygame.draw.line(window, (128, 128, 128), (x, 0), (x, window_height))
    for y in range(0, window_height, cell_size):
        pygame.draw.line(window, (128, 128, 128), (0, y), (window_width, y))

# Inside the game loop
window.fill((255, 255, 255))  # Clear the window
draw_game_grid()  # Draw the game grid
``` In this updated code, we have defined `grid_width` and `grid_height` variables to determine the size of the game grid. We have also defined a `cell_size` variable to calculate the size of each cell based on the grid size and window dimensions.

The draw_game_grid() function loops over the range of window dimensions and draws vertical lines for each column and horizontal lines for each row of the game grid. We use the pygame.draw.line() function to draw the lines on the window surface.

Inside the game loop, we first clear the window by filling it with white color. Then, we call the draw_game_grid() function to draw the game grid.

When you run the script now, you should see a grid overlaying the game window.

Adding Towers

Now that we have the game grid, let’s add the ability to place towers on it. We’ll allow the player to click on a cell in the grid to place a tower at that position. ```python # Inside the game loop for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1: # Left mouse button clicked mouse_pos = pygame.mouse.get_pos() grid_x = mouse_pos[0] // cell_size grid_y = mouse_pos[1] // cell_size tower_x = grid_x * cell_size tower_y = grid_y * cell_size

        # Place tower at (tower_x, tower_y)
        pygame.draw.rect(window, (255, 0, 0), (tower_x, tower_y, cell_size, cell_size))
``` In this code, we have added an additional event handler for the `MOUSEBUTTONDOWN` event. We check if the left mouse button was clicked (`event.button == 1`) and retrieve the current mouse position using `pygame.mouse.get_pos()`. 

We then calculate the grid position based on the mouse position and cell size. Next, we calculate the actual position of the tower by multiplying the grid position with the cell size.

Finally, we draw a red square representing the tower on the game window using pygame.draw.rect().

When you run the script now, you should be able to click on the game grid to place towers.

Enemies and Waves

A tower defense game wouldn’t be complete without enemies. Let’s add enemies to our game and create waves of enemies that spawn at regular intervals. ```python # Inside the game loop current_wave = 0 wave_interval = 2000 # Time in milliseconds between waves next_wave_time = pygame.time.get_ticks() + wave_interval

while running:
    # ...

    # Enemy waves
    if pygame.time.get_ticks() >= next_wave_time:
        current_wave += 1
        next_wave_time += wave_interval

        # Spawn enemies for the current wave

    # ...

pygame.quit()
``` In this code, we have introduced several new variables. `current_wave` keeps track of the current wave number, `wave_interval` defines the time in milliseconds between waves, and `next_wave_time` determines when the next wave should start.

Inside the game loop, we check if the current time (obtained using pygame.time.get_ticks()) is greater than or equal to the next_wave_time. If so, we increment the current_wave number, update the next_wave_time by adding the wave_interval, and start spawning enemies for the current wave.

At this point, we haven’t implemented the enemy spawning mechanism yet, but you can print or display the current_wave value to verify that the wave system is working.

Collision Detection

In a tower defense game, towers usually attack or damage enemies that come within their range. To achieve this, we need to implement collision detection between towers and enemies. ```python # Inside the game loop

# Check tower-enemy collisions
for tower in towers:
    for enemy in enemies:
        if (
            tower.x <= enemy.x <= tower.x + cell_size and
            tower.y <= enemy.y <= tower.y + cell_size
        ):
            # Tower attacks enemy
``` In this code, we assume that we have lists of tower and enemy objects (`towers` and `enemies`). We loop over each tower and each enemy, and check if their positions overlap by comparing their x and y coordinates.

If a collision is detected, we can perform any action we want, such as applying damage to the enemy or removing the enemy from the game.

Game Over and Score

Finally, we need to implement a game over condition and keep track of the player’s score. Let’s start with tracking the score. ```python # Inside the game loop score = 0

while running:
    # ...

    # Increase score when an enemy reaches the target
    for enemy in enemies:
        if enemy.x == target_x and enemy.y == target_y:
            score += enemy.score_value

    # ...

pygame.quit()
``` In this code, we introduce a new variable `score` that starts at 0. Inside the game loop, we iterate over the `enemies` list and check if an enemy has reached the target (represented by the coordinates `target_x` and `target_y`). If so, we add the `score_value` of the enemy to the player's score.

Next, let’s implement the game over condition. ```python # Inside the game loop game_over = False

while running and not game_over:
    # ...

    # Game over when all enemies have reached the target
    if all(enemy.x == target_x and enemy.y == target_y for enemy in enemies):
        game_over = True

    # ...

pygame.quit()
``` In this code, we introduce a new variable `game_over` that starts as `False`. Inside the game loop, we check if all enemies have reached the target by using a generator expression and the `all()` function. If the condition is satisfied, we set `game_over` to `True`.

You can control the game’s difficulty by adjusting the wave_interval and score_value of enemies. Additionally, you may want to display the current score and game over message on the game window.

Conclusion

In this tutorial, we have learned how to build a tower defense game using Python and the Pygame library. We started by setting up the game window and drawing a game grid. Then, we added the ability to place towers and implemented enemy waves. We also covered collision detection between towers and enemies, as well as keeping track of the player’s score and implementing a game over condition.

Feel free to expand upon this tutorial by adding additional features, such as different types of towers, power-ups, or a graphical user interface. With the knowledge gained from this tutorial, you can create your own unique tower defense game with Python and Pygame.