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 |
|
Display |
|
Clock |
|
Load image |
|
Get rect |
|
Draw image |
|
Fill color |
|
Draw rect |
|
Update display |
|
Get events |
|
Sprite group |
|
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.