nimbuscode.dev/blog/posts/game-development-with-pygame
C:\> cat BLOG/PYGAME_GUIDE.md

Game Development with PyGame: A Beginner's Guide

Introduction

Game development is a rewarding journey that combines creativity with technical skills. While professional game engines like Unity and Unreal Engine offer powerful capabilities, they can be overwhelming for beginners. This is where PyGame comes in – a Python library designed specifically for creating 2D games that's perfect for those taking their first steps in game development.

PyGame provides simple yet effective tools for handling graphics, sound, input devices, and game physics, all while leveraging the readability and ease of use that Python is known for. With PyGame, you can create engaging games while focusing on the fun parts of game design rather than getting lost in complex engine details.

In this guide, we'll walk through the fundamentals of PyGame and build our knowledge step by step, culminating in a simple but complete game. Whether you're a student, hobbyist, or professional programmer looking to try game development, this tutorial will give you the foundation you need to start creating your own games.

Setting Up PyGame

Before we dive into game development, let's get PyGame installed and ready to use.

Prerequisites

You'll need:

  • Python 3.6 or higher installed on your system
  • Basic knowledge of Python programming
  • A code editor or IDE of your choice (like VS Code, PyCharm, or even IDLE)

Installation

Installing PyGame is straightforward using pip:

pip install pygame

To verify your installation, you can run a simple test:

import pygame
pygame.init()
print(f"PyGame version: {pygame.version.ver}")

If you see the version number printed without errors, you're ready to start developing!

Creating Your First Window

Let's start by creating a simple window – the canvas where our game will live:

import pygame
import sys

# Initialize pygame
pygame.init()

# Set up display
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My First PyGame Window")

# Main loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # Fill the screen with a color (RGB)
    screen.fill((0, 0, 0))  # Black background
    
    # Update the display
    pygame.display.flip()

# Clean up
pygame.quit()
sys.exit()

When you run this code, you should see a black window with the title "My First PyGame Window". You can close it by clicking the X in the corner, which will trigger the pygame.QUIT event.

Let's break down what's happening:

  1. We initialize PyGame with pygame.init()
  2. We create a window with pygame.display.set_mode(), specifying the width and height
  3. We set a window title with pygame.display.set_caption()
  4. We enter the main game loop where we:
    • Check for events (like closing the window)
    • Fill the screen with a color
    • Update the display to show changes
  5. Finally, we clean up when the game ends

The Game Loop

Every game needs a game loop – a continuous cycle that keeps the game running until the player quits. A typical game loop has three main phases:

  1. Process Input: Detect and handle user input from keyboard, mouse, or other devices
  2. Update Game State: Update the positions, states, and properties of game objects
  3. Render: Draw everything to the screen

Let's expand our previous code to include these distinct phases and add a clock to control the game speed:

import pygame
import sys

# Initialize pygame
pygame.init()

# Set up display
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game Loop Demo")

# Set up clock for controlling frame rate
clock = pygame.time.Clock()
FPS = 60  # Target frames per second

# Game variables
player_x, player_y = WIDTH // 2, HEIGHT // 2  # Start player in center
player_speed = 5  # Pixels per frame

# Main game loop
running = True
while running:
    # PROCESS INPUT
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    keys = pygame.key.get_pressed()
    
    # UPDATE GAME STATE
    if keys[pygame.K_LEFT]:
        player_x -= player_speed
    if keys[pygame.K_RIGHT]:
        player_x += player_speed
    if keys[pygame.K_UP]:
        player_y -= player_speed
    if keys[pygame.K_DOWN]:
        player_y += player_speed
    
    # Keep player on screen
    player_x = max(0, min(WIDTH, player_x))
    player_y = max(0, min(HEIGHT, player_y))
    
    # RENDER
    screen.fill((0, 0, 0))  # Clear screen
    
    # Draw player as a rectangle
    pygame.draw.rect(screen, (255, 0, 0), (player_x - 25, player_y - 25, 50, 50))
    
    # Update display
    pygame.display.flip()
    
    # Control frame rate
    clock.tick(FPS)

# Clean up
pygame.quit()
sys.exit()

This example introduces several important concepts:

  • Using a clock to control frame rate with clock.tick(FPS)
  • Checking the state of all keys with pygame.key.get_pressed()
  • Moving an object based on keyboard input
  • Keeping an object within screen boundaries
  • Drawing a simple shape (rectangle) to represent the player

Run this code and use the arrow keys to move the red square around the screen. This demonstrates the fundamental interaction loop of most games.

Drawing Shapes and Images

PyGame offers multiple ways to render content on screen. Let's explore both shape drawing and image loading.

Drawing Shapes

PyGame provides functions for drawing basic shapes:

# Drawing a circle
pygame.draw.circle(screen, (0, 255, 0), (400, 300), 50)  # Green circle

# Drawing a rectangle
pygame.draw.rect(screen, (0, 0, 255), (100, 100, 150, 80))  # Blue rectangle

# Drawing a line
pygame.draw.line(screen, (255, 255, 255), (0, 0), (WIDTH, HEIGHT), 5)  # White line

# Drawing a polygon
pygame.draw.polygon(screen, (255, 255, 0), [(600, 200), (700, 250), (650, 350), (550, 300)])  # Yellow polygon

Loading and Drawing Images

For most games, you'll want to use images rather than simple shapes. Here's how to load and display images:

# Load an image
player_image = pygame.image.load('player.png').convert_alpha()  # convert_alpha preserves transparency

# Scale the image if needed
player_image = pygame.transform.scale(player_image, (64, 64))

# Draw the image
screen.blit(player_image, (player_x, player_y))

The blit function is used to draw one surface onto another. In this case, we're drawing our player image onto the screen surface.

Tip: Using convert() or convert_alpha() after loading images significantly improves performance by converting them to the same format as the display surface.

Handling User Input

Responsive controls are essential for any game. PyGame offers several ways to handle user input.

Event-Based Input

For one-time actions (like firing a weapon or jumping), event-based input works best:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            fire_bullet()  # Call a function to fire when Space is pressed
        elif event.key == pygame.K_r:
            reload_weapon()  # Reload when R is pressed
    elif event.type == pygame.MOUSEBUTTONDOWN:
        if event.button == 1:  # Left mouse button
            select_item(event.pos)  # Select item at mouse position

State-Based Input

For continuous actions (like moving), state-based input is more appropriate:

keys = pygame.key.get_pressed()

# Player movement
if keys[pygame.K_a]:  # A key for left movement
    player_x -= player_speed
if keys[pygame.K_d]:  # D key for right movement
    player_x += player_speed
if keys[pygame.K_w]:  # W key for up movement
    player_y -= player_speed
if keys[pygame.K_s]:  # S key for down movement
    player_y += player_speed

# Check if Shift is held for sprint
if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
    player_speed = 10  # Increased speed
else:
    player_speed = 5  # Normal speed

Mouse Input

For mouse position and button state:

# Get mouse position
mouse_x, mouse_y = pygame.mouse.get_pos()

# Calculate angle between player and mouse for aiming
dx = mouse_x - player_x
dy = mouse_y - player_y
angle = math.atan2(dy, dx)

# Check if mouse buttons are pressed
mouse_buttons = pygame.mouse.get_pressed()
if mouse_buttons[0]:  # Left button
    shoot_towards(angle)
if mouse_buttons[2]:  # Right button
    use_special_ability()

Working with Sprites

Sprites are one of the most fundamental concepts in 2D game development. In PyGame, a sprite is essentially an image that can move around the screen and interact with other elements.

PyGame provides a Sprite class that makes it easier to manage game objects. Let's create a simple player sprite:

import pygame

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        # Load the image
        self.image = pygame.image.load('player.png').convert_alpha()
        self.image = pygame.transform.scale(self.image, (64, 64))
        # Create a rectangle for positioning
        self.rect = self.image.get_rect()
        self.rect.center = (400, 300)  # Start in the middle of screen
        # Add movement attributes
        self.speed = 5
        self.health = 100
    
    def update(self):
        # Handle movement
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]:
            self.rect.x += self.speed
        if keys[pygame.K_UP]:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN]:
            self.rect.y += self.speed
        
        # Keep player on screen
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.right > 800:
            self.rect.right = 800
        if self.rect.top < 0:
            self.rect.top = 0
        if self.rect.bottom > 600:
            self.rect.bottom = 600
    
    def draw(self, surface):
        surface.blit(self.image, self.rect)

Using this class in your game loop:

# Initialize the player
player = Player()

# In the game loop
while running:
    # Process events...
    
    # Update game state
    player.update()
    
    # Render
    screen.fill((0, 0, 0))
    player.draw(screen)
    pygame.display.flip()
    
    # Control frame rate
    clock.tick(FPS)

For multiple sprites of the same type (like enemies or bullets), you can use sprite groups:

# Create sprite groups
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()

# Add sprites to groups
player = Player()
all_sprites.add(player)

for i in range(5):  # Create 5 enemies
    enemy = Enemy()
    all_sprites.add(enemy)
    enemies.add(enemy)

# In the game loop
while running:
    # Process events...
    
    # Update all sprites
    all_sprites.update()
    
    # Render
    screen.fill((0, 0, 0))
    all_sprites.draw(screen)  # Draws all sprites in the group
    pygame.display.flip()
    
    # Control frame rate
    clock.tick(FPS)

Comments (0)

Sort by: