经过第12和第13章,《外星人入侵》已经有了一个可玩游戏的完整骨架。但还缺几样东西让它更像一款正经游戏:
这一章,我们就来解决这三个问题。
01 Play按钮:让游戏优雅地开始和重置
游戏启动时,默认 game_active = False,光标要显示出来,告诉玩家"点击这里开始"。
Button类
Pygame 没有内置按钮,我们自己写一个:
# button.py
import pygame
from pygame.sprite import Sprite
class Button(Sprite):
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, 135, 0) # 绿色
self.text_color = (255, 255, 255)
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):
self.image = self.font.render(msg, True, self.text_color,
self.button_color)
self.image_rect = self.image.get_rect()
self.image_rect.center = self.rect.center
def draw_button(self):
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.image, self.image_rect)
prep_msg 把文字渲染成图像,draw_button 先填颜色再贴文字。
鼠标检测:单击开始
def check_play_button(stats, play_button, ship, aliens,
bullets, ai_settings, screen):
mouse_x, mouse_y = pygame.mouse.get_pos()
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
if button_clicked and not stats.game_active:
# 重置游戏状态
ai_settings.initialize_dynamic_settings()
stats.reset_stats()
stats.game_active = True
aliens.empty()
bullets.empty()
create_fleet(ai_settings, screen, aliens)
ship.center_ship()
pygame.mouse.set_visible(False) # 隐藏光标
pygame.mouse.get_pos() 获取鼠标位置,rect.collidepoint() 判断鼠标是否在按钮范围内。
游戏结束时 pygame.mouse.set_visible(True) 恢复光标,这样光标只在需要点击时才出现。
02 静态设置vs动态设置:难度递进的前提
游戏每次开始,速度都是一样的。随着等级提升,速度要越来越快。
怎么让"初始速度"和"当前速度"分开管理?把设置分成两类:
# settings.py
class Settings:
def __init__(self):
# 静态设置(永远不变)
self.screen_width = 1200
self.screen_height = 700
self.ships_limit = 3
self.speedup_scale = 1.1 # 每级速度倍率
self.score_scale = 1.5 # 每级分数倍率
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
# 动态设置(每局重置)
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 1.5
self.alien_speed_factor = 1
self.fleet_direction = 1
self.alien_points = 50
def increase_speed(self):
# 每升一级调用一次
self.ship_speed_factor *= self.speedup_scale
self.bullet_speed_factor *= self.speedup_scale
self.alien_speed_factor *= self.speedup_scale
self.alien_points = int(self.alien_points * self.score_scale)
关键设计:
speedup_scale = 1.1:每级速度乘以1.1,10级后就是1.1^10≈2.6倍score_scale = 1.5:每级击中外星人的分数增加50%
03 记分系统:Scoreboard
有了等级制度,分数也得配套跟上来。
GameStats:管理所有统计
class GameStats:
def __init__(self, ai_settings):
self.high_score = 0 # 最高分,任何情况都不重置
def reset_stats(self):
self.ships_left = self.ai_settings.ships_limit
self.score = 0
self.level = 1
Scoreboard:把数字画到屏幕上
class Scoreboard:
def __init__(self, ai_settings, screen, stats):
self.screen = screen
self.stats = stats
self.prep_score()
self.prep_high_score()
self.prep_level()
self.prep_ships()
def prep_score(self):
# 圆整到10的整数倍(街机风格)
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True,
self.text_color, self.ai_settings.bg_color)
def prep_ships(self):
# 用Ship的图形来显示剩余命数(左上角横排)
self.ships = Group()
for ship_number in range(self.stats.ships_left):
ship = Ship(self.ai_settings, self.screen)
ship.rect.x = 10 + ship_number * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
用飞船图形代替数字显示剩余命数——这是经典街机游戏的做法。
04 完整的计分流程
每当一颗子弹击中一群外星人:
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
for alien_list in collisions.values():
# 一颗宽子弹可能同时击中多个外星人,都要算分
self.stats.score += self.ai_settings.alien_points * len(alien_list)
self.check_high_score()
self.prep_score()
# 全灭了就升级
if len(aliens) == 0:
self.bullets.empty()
self.ai_settings.increase_speed()
self.stats.level += 1
self.prep_level()
create_fleet(self.ai_settings, self.screen, self.aliens)
注意这里的细节:宽子弹一次可以穿过多个外星人,所以要遍历 collisions.values(),把所有被击中的外星人都算上,不能只加一份分。
05 最高分:始终保留
最高分的逻辑很简单——每次得分变化就检查一次:
def check_high_score(self):
if self.stats.score > self.stats.high_score:
self.stats.high_score = self.stats.score
self.prep_high_score()
注意 high_score 存在 GameStats 里,而 GameStats 在游戏重启时不会被重新创建,所以最高分始终保持。
06 一个完整的游戏循环
四章的内容加在一起,《外星人入侵》的代码结构是:
alien_invasion/
├── alien_invasion.py # 主循环 + 创建Scoreboard/Button
├── settings.py # 静态/动态分离
├── game_stats.py # score/high_score/level/game_active
├── scoreboard.py # 记分牌(新增)
├── button.py # Play按钮(新增)
├── ship.py / alien.py / bullet.py
├── game_functions.py # 事件、碰撞、升级
└── images/
├── ship.bmp
└── alien.bmp
下章预告
从第6章到第14章,《Python编程:从入门到实践》这本书的前半部分(知识点)和项目章节(12-14章)就全部完成了。
下一章我们进入数据可视化,用 Python 的 matplotlib 库画图、模拟随机漫步、统计分析骰子结果。告别游戏,进入另一种有趣的数据探索。
关注公众号「Bug与灵光」,一起完成Python学习的最后一公里。