Chapter 12: Alien Invasion - A Game Project

Part II begins. Build a complete game to apply everything from Part I.

Project Overview

Alien Invasion: A ship shoots aliens descending from the top. Classic arcade gameplay teaches:

  • Game loops and frame rates

  • Event handling (keyboard, mouse)

  • Collision detection

  • Game state management

  • Object-oriented design at scale

Installing Pygame

python -m pip install --user pygame

Verify:

>>> import pygame
>>> pygame.version.ver
'2.5.2'

Project Structure

alien_invasion/
├── alien_invasion.py    # Main game loop
├── settings.py          # Game settings
├── ship.py              # Player ship class
├── bullet.py            # Bullet class
├── alien.py             # Alien class
└── images/
    ├── ship.bmp
    └── alien.bmp

Keep settings separate. Easy to tune gameplay.

Starting the Game

Settings Class

# settings.py
class Settings:
    """Store all game settings."""

    def __init__(self):
        # Screen
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (30, 30, 30)  (1)

        # Ship
        self.ship_speed = 1.5

        # Bullets
        self.bullet_speed = 2.5
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (60, 180, 60)
        self.bullets_allowed = 5
1 RGB tuple - dark gray background

Main Game Class

# alien_invasion.py
import sys
import pygame

from settings import Settings
from ship import Ship

class AlienInvasion:
    """Main class to manage game assets and behavior."""

    def __init__(self):
        pygame.init()  (1)
        self.clock = pygame.time.Clock()  (2)
        self.settings = Settings()

        self.screen = pygame.display.set_mode(
            (self.settings.screen_width, self.settings.screen_height)
        )
        pygame.display.set_caption("Alien Invasion")

        self.ship = Ship(self)

    def run_game(self):
        """Main game loop."""
        while True:
            self._check_events()
            self.ship.update()
            self._update_screen()
            self.clock.tick(60)  (3)

    def _check_events(self):
        """Respond to keypresses and mouse events."""
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                self._check_keydown_events(event)
            elif event.type == pygame.KEYUP:
                self._check_keyup_events(event)

    def _check_keydown_events(self, event):
        if event.key == pygame.K_RIGHT:
            self.ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            self.ship.moving_left = True
        elif event.key == pygame.K_q:
            sys.exit()

    def _check_keyup_events(self, event):
        if event.key == pygame.K_RIGHT:
            self.ship.moving_right = False
        elif event.key == pygame.K_LEFT:
            self.ship.moving_left = False

    def _update_screen(self):
        """Update images and flip to new screen."""
        self.screen.fill(self.settings.bg_color)
        self.ship.blitme()
        pygame.display.flip()  (4)


if __name__ == '__main__':
    ai = AlienInvasion()
    ai.run_game()
1 Initialize pygame systems
2 Clock controls frame rate
3 60 FPS cap
4 Swap display buffers

The Ship Class

# ship.py
import pygame

class Ship:
    """Manage the ship."""

    def __init__(self, ai_game):
        self.screen = ai_game.screen
        self.settings = ai_game.settings
        self.screen_rect = ai_game.screen.get_rect()

        # Load image and get its rect
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()

        # Start at bottom center
        self.rect.midbottom = self.screen_rect.midbottom

        # Store float for position (rect uses integers)
        self.x = float(self.rect.x)

        # Movement flags
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """Update position based on movement flags."""
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.x += self.settings.ship_speed
        if self.moving_left and self.rect.left > 0:
            self.x -= self.settings.ship_speed

        self.rect.x = self.x

    def blitme(self):
        """Draw ship at current location."""
        self.screen.blit(self.image, self.rect)

Key concepts:

  • rect - Rectangle for positioning and collision

  • Float position - Smooth movement between pixels

  • Movement flags - Hold key for continuous movement

Pygame Rects

Rects are fundamental. Every sprite has one.

rect = image.get_rect()

# Position attributes
rect.top, rect.bottom
rect.left, rect.right
rect.centerx, rect.centery
rect.center
rect.topleft, rect.topright
rect.bottomleft, rect.bottomright
rect.midtop, rect.midbottom
rect.midleft, rect.midright

# Shortcut positioning
rect.midbottom = screen_rect.midbottom  # Center at bottom
rect.center = (600, 400)                 # Exact center

Adding Bullets

Bullet Class

# bullet.py
import pygame
from pygame.sprite import Sprite

class Bullet(Sprite):  (1)
    """Manage bullets fired from ship."""

    def __init__(self, ai_game):
        super().__init__()
        self.screen = ai_game.screen
        self.settings = ai_game.settings
        self.color = self.settings.bullet_color

        # Create rect at (0,0), then position correctly
        self.rect = pygame.Rect(
            0, 0,
            self.settings.bullet_width,
            self.settings.bullet_height
        )
        self.rect.midtop = ai_game.ship.rect.midtop

        self.y = float(self.rect.y)

    def update(self):
        """Move bullet up the screen."""
        self.y -= self.settings.bullet_speed
        self.rect.y = self.y

    def draw_bullet(self):
        """Draw bullet to screen."""
        pygame.draw.rect(self.screen, self.color, self.rect)
1 Sprite base class enables grouping and collision detection

Managing Bullets in Main

# In AlienInvasion.__init__
from pygame.sprite import Group
from bullet import Bullet

self.bullets = Group()  (1)

# In run_game loop
self.bullets.update()
self._update_bullets()

# New methods
def _fire_bullet(self):
    """Create new bullet and add to group."""
    if len(self.bullets) < self.settings.bullets_allowed:
        new_bullet = Bullet(self)
        self.bullets.add(new_bullet)

def _update_bullets(self):
    """Update bullets and remove old ones."""
    # Remove bullets that left screen
    for bullet in self.bullets.copy():  (2)
        if bullet.rect.bottom <= 0:
            self.bullets.remove(bullet)

# In _check_keydown_events
elif event.key == pygame.K_SPACE:
    self._fire_bullet()

# In _update_screen (before flip)
for bullet in self.bullets.sprites():
    bullet.draw_bullet()
1 Group manages multiple sprites
2 Copy list when removing during iteration

Refactoring: Helper Methods

Prefix private methods with underscore:

def run_game(self):
    while True:
        self._check_events()      (1)
        self.ship.update()
        self._update_bullets()
        self._update_screen()
        self.clock.tick(60)
1 Underscore = internal use only

Keep methods focused. run_game() stays readable.

Running the Game

cd alien_invasion
python alien_invasion.py

Controls: - Arrow keys: Move ship - Space: Fire - Q: Quit

Game Loop Architecture

                    ┌─────────────────┐
                    │   Game Loop     │
                    │   (60 FPS)      │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│ Check Events  │   │ Update State  │   │ Draw Screen   │
│ (input)       │   │ (physics)     │   │ (render)      │
└───────────────┘   └───────────────┘   └───────────────┘

Every game follows this pattern: 1. Handle input 2. Update game state 3. Render frame 4. Repeat

Quick Reference

Pygame Concept Code

Initialize

pygame.init()

Display

pygame.display.set_modew, h

Clock

pygame.time.Clock(); clock.tick(60)

Load image

pygame.image.load('path.bmp')

Get rect

image.get_rect()

Draw image

screen.blit(image, rect)

Fill color

screen.fillr, g, b

Draw rect

pygame.draw.rect(screen, color, rect)

Update display

pygame.display.flip()

Get events

for event in pygame.event.get():

Sprite group

Group(); group.add(sprite)

Exercises

12-1. Blue Sky

Create a pygame window with a blue background. Make it 800x600.

12-2. Ship Image

Find or create a ship image. Load and display it centered on screen.

12-3. Rocket

Create a rocket that moves up/down/left/right with arrow keys.

12-4. Keys

Create a program that prints the key attribute whenever a key is pressed.

Summary

  • Pygame provides game development tools: display, events, sprites

  • Game loop: check events → update state → draw screen

  • Rects handle positioning and collision detection

  • Sprite groups manage collections of game objects

  • Settings class centralizes configuration

  • Refactor large classes into helper methods

Next: Adding aliens and collisions.