Game Development 21 min read

Build a Classic Alien Invasion Game with Pygame: Full Source Code Walkthrough

This tutorial walks through creating a complete Alien Invasion game using Python and Pygame, covering the main program, game logic, settings, ship, alien, bullet, button, scoring, and game statistics modules, while showcasing screenshots of gameplay and emphasizing modular, object‑oriented design.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Build a Classic Alien Invasion Game with Pygame: Full Source Code Walkthrough

Overview

This article presents a step‑by‑step guide to building the classic "Alien Invasion" game with Python's Pygame library. It explains the overall architecture, the purpose of each module, and how they interact to create a functional shooting game.

Running the Game

After launching the program, the following screenshots illustrate the game flow: when all three ships are exhausted the game ends, and clicking the Play button restarts the session.

Game screenshot 1
Game screenshot 1
Game screenshot 2
Game screenshot 2
Game screenshot 3
Game screenshot 3

Main Program (alien_invasion.py)

"""该游戏主程序,尽量做到最简单"""
import pygame
from settings import Settings
from ship import Ship
from pygame.sprite import Group
from game_stats import GameStats
from button import Button
from score_board import Scoreboard
import game_function as gf

def run_game():
    """初始化背景设置"""
    pygame.init()
    ai_settings = Settings()
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
    pygame.display.set_caption('Alien Invasion')
    ship = Ship(ai_settings, screen)
    bullets = Group()
    aliens = Group()
    gf.creat_fleet(ai_settings, screen, ship, aliens)
    stats = GameStats(ai_settings)
    sb = Scoreboard(ai_settings, screen, stats)
    play_button = Button(ai_settings, screen, "Play")
    while True:
        gf.check_events(ai_settings, screen, ship, aliens, bullets, stats, play_button, sb)
        if stats.game_active:
            ship.update()
            gf.update_bullet(ai_settings, screen, ship, aliens, bullets, stats, sb)
            gf.update_aliens(ai_settings, screen, ship, aliens, stats, bullets, sb)
            gf.update_screen(ai_settings, screen, ship, aliens, bullets, stats, play_button, sb)
        else:
            gf.update_screen(ai_settings, screen, ship, aliens, bullets, stats, play_button, sb)

run_game()

Game Logic (game_function.py)

import sys
import pygame
from random import randint
from bullet import Bullet
from alien import Alien
from time import sleep

def fire_bullet(ai_settings, screen, ship, bullets, stats):
    """开火!"""
    for i in range(int(stats.level/5) + 1):
        if len(bullets) < ai_settings.bullet_allowed:
            new_bullet = Bullet(ai_settings, screen, ship)
            bullets.add(new_bullet)

def check_keydown(event, ai_settings, screen, ship, bullets, stats):
    """检查用户按键是否按下以及执行的任务"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True
    elif event.key == pygame.K_UP:
        ship.moving_up = True
    elif event.key == pygame.K_DOWN:
        ship.moving_down = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings, screen, ship, bullets, stats)

def check_keyup(event, ship):
    """检查用户释放按键"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False
    elif event.key == pygame.K_UP:
        ship.moving_up = False
    elif event.key == pygame.K_DOWN:
        ship.moving_down = False

def check_events(ai_settings, screen, ship, aliens, bullets, stats, play_button, sb):
    """响应键盘和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.K_q:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown(event, ai_settings, screen, ship, bullets, stats)
        elif event.type == pygame.KEYUP:
            check_keyup(event, ship)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(ai_settings, screen, ship, aliens, bullets, stats, play_button, mouse_x, mouse_y, sb)

def check_play_button(ai_settings, screen, ship, aliens, bullets, stats, play_button, mouse_x, mouse_y, sb):
    """在玩家单击Play按钮时开始游戏"""
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    if button_clicked and not stats.game_active:
        ai_settings.init_dynamic_settings()
        pygame.mouse.set_visible(False)
        stats.reset_stats()
        stats.game_active = True
        sb.prep_score()
        sb.prep_high_score(screen)
        sb.prep_level()
        sb.prep_ships(screen)
        aliens.empty()
        bullets.empty()
        creat_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship(ai_settings)

def update_screen(ai_settings, screen, ship, aliens, bullets, stats, play_button, sb):
    """每次循环都重绘屏幕"""
    screen.fill(ai_settings.bg_color)
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    ship.blitme()
    aliens.draw(screen)
    sb.show_score()
    if not stats.game_active:
        play_button.draw_button()
    pygame.display.flip()

Supporting Modules

The remaining files define settings, ship, alien, bullet, button, game statistics, and scoreboard classes. Each module follows object‑oriented principles, encapsulating related data and behavior, and they are imported by the main program and game_function.py to keep the codebase modular and maintainable.

settings.py

class Settings():
    def __init__(self):
        """设置长度和宽度以及背景色属性"""
        self.screen_width = 800
        self.screen_height = 700
        self.bg_color = (255, 255, 255)
        self.ship_limit = 2
        # 子弹设置
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (60, 60, 60)
        self.bullet_allowed = 8
        # 外星人移动设置
        self.fleet_drop_speed = 10
        self.speed_up_scale = 1.1
        self.init_dynamic_settings()
        self.score_scale = 1.5
        self.bullet_scale = 10
    def init_dynamic_settings(self):
        self.speed = 1.5
        self.bullet_speed = 3
        self.alien_speed = 0.2
        self.fleet_direction = -1
        self.alien_points = 10
    def increase_speed(self):
        self.speed *= self.speed_up_scale
        self.bullet_speed *= self.speed_up_scale
        self.alien_speed *= self.speed_up_scale
        self.alien_points = int(self.alien_points * self.score_scale)
    def increase_bullet_size(self):
        self.bullet_width += self.bullet_scale

ship.py

import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
    def __init__(self, ai_settings, screen):
        """初始化飞船并设置其初始位置"""
        super(Ship, self).__init__()
        self.screen = screen
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom
        self.center_x = float(self.rect.centerx)
        self.center_y = float(self.rect.centery)
        self.moving_right = self.moving_left = self.moving_up = self.moving_down = False
    def update(self):
        """根据移动标志更新飞船位置"""
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.center_x += self.ai_settings.speed
        if self.moving_left and self.rect.left > 0:
            self.center_x -= self.ai_settings.speed
        if self.moving_up and self.rect.top > 0:
            self.center_y -= self.ai_settings.speed
        if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
            self.center_y += self.ai_settings.speed
        self.rect.centerx = self.center_x
        self.rect.centery = self.center_y
    def blitme(self):
        self.screen.blit(self.image, self.rect)
    def center_ship(self, ai_settings):
        self.center_x = ai_settings.screen_width / 2
        self.center_y = ai_settings.screen_height - 28

alien.py

import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
    def __init__(self, ai_settings, screen):
        """初始化外星人并设置其初始位置"""
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings
        self.image = pygame.image.load('images/alien.bmp')
        self.rect = self.image.get_rect()
        self.rect.x = self.rect.width
        self.rect.y = self.rect.height
        self.x = float(self.rect.x)
    def check_edges(self):
        """如果外星人碰到屏幕边缘,则返回True"""
        screen_rect = self.screen.get_rect()
        return self.rect.right >= screen_rect.right or self.rect.left <= 0
    def update(self):
        """移动外星人"""
        self.x += self.ai_settings.alien_speed * self.ai_settings.fleet_direction
        self.rect.x = self.x
    def blitme(self):
        self.screen.blit(self.image, self.rect)

bullet.py

import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
    def __init__(self, ai_settings, screen, ship):
        super(Bullet, self).__init__()
        self.screen = screen
        self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)
        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top
        self.y = float(self.rect.y)
        self.color = ai_settings.bullet_color
        self.speed = ai_settings.bullet_speed
    def update(self):
        """向上移动子弹"""
        self.y -= self.speed
        self.rect.y = self.y
    def draw_bullet(self):
        pygame.draw.rect(self.screen, self.color, self.rect)

button.py

import pygame.font
class Button():
    def __init__(self, ai_settings, screen, msg):
        """初始化按钮属性"""
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.width, self.height = 200, 50
        self.button_color = (0, 255, 0)
        self.text_color = (60, 60, 60)
        self.font = pygame.font.SysFont(None, 48)
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center
        self.prep_msg(msg)
    def prep_msg(self, msg):
        """将msg渲染为图像,并使其在按钮上居中"""
        self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center
    def draw_button(self):
        self.screen.fill(self.button_color, self.rect)
        self.screen.blit(self.msg_image, self.msg_image_rect)

game_stats.py

class GameStats():
    """跟踪游戏的统计信息"""
    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.game_active = False
        self.reset_stats()
        self.high_score = 0
    def reset_stats(self):
        """初始化在游戏运行过程中可能变化的统计信息"""
        self.ship_left = self.ai_settings.ship_limit
        self.score = 0
        self.level = 1
        self.bullet_width = 3

score_board.py

import pygame.font
from pygame.sprite import Group
from ship import Ship
class Scoreboard():
    """显示得分的类"""
    def __init__(self, ai_settings, screen, stats):
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)
        self.prep_score()
        self.prep_high_score(screen)
        self.prep_level()
        self.prep_ships(screen)
    def prep_score(self):
        rounded_score = int(round(self.stats.score, -1))
        score_str = "Score:" + "{:,}".format(rounded_score)
        self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)
        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = self.screen_rect.right - 20
        self.score_rect.top = 20
    def prep_high_score(self, screen):
        high_score = int(round(self.stats.high_score, -1))
        high_score_str = "High Score:" + "{:,}".format(high_score)
        self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)
        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.left = self.screen_rect.left + 20
        self.high_score_rect.top = 20
    def prep_level(self):
        self.level_image = self.font.render("Level:" + str(self.stats.level), True, self.text_color, self.ai_settings.bg_color)
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.score_rect.right
        self.level_rect.bottom = self.score_rect.bottom + 650
    def show_score(self):
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)
        self.screen.blit(self.level_image, self.level_rect)
        self.ships.draw(self.screen)
    def prep_ships(self, screen):
        self.ships = Group()
        for ship_number in range(self.stats.ship_left + 1):
            ship = Ship(self.ai_settings, self.screen)
            ship.rect.x = 10 + ship_number * ship.rect.width
            ship.rect.y = self.screen_rect.top + 640
            self.ships.add(ship)

Personal Reflection

The author notes that building this game taught them modular thinking, object‑oriented programming, and extensive use of functions, which they found valuable beyond just this project.

Pythongame developmenttutorialObject-Oriented Programmingmodular designPygame
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.