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.
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.
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_scaleship.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 - 28alien.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 = 3score_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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
