Python编程:从入门到实践
章节

第 13 章 外星人

从单个对象走向对象群,碰撞与舰队逻辑开始出现

这一章把游戏复杂度往上推:批量生成外星人、组织舰队、移动对象和处理碰撞。

章节主张

项目进入中段后,真正的挑战不再是语法,而是对象群的组织和状态协调。

中文释读

如果第 12 章让你体验‘有一个会动的系统’,这一章就是让系统开始变拥挤。外星人不只是一张图,它们成组出现、批量移动、发生碰撞,还要与飞船和子弹交互。程序复杂度因此第一次明显上升。作者安排这章,就是为了让你感受‘程序结构是否清晰’会直接决定后面还能不能继续加功能。

本章知识路线

  1. 一旦对象成群出现,程序结构就必须升级
  2. 碰撞问题本质上是在处理对象关系和状态更新

关键论点

  • 对象群管理是游戏与仿真程序的典型难点。
  • 坐标与碰撞逻辑会迅速放大代码复杂度。
  • 批量对象出现后,良好的结构比语法熟练更重要。

行动清单

  • 把对象生成逻辑与对象行为逻辑分开。
  • 理解群组与单实例之间的差别。
  • 用分步验证处理碰撞与移动问题。

源章节深读

第 13 章 外星⼈ 本章将为游戏《外星⼈⼊侵》添加外星⼈。我们将先在屏幕的上边缘 附近添加⼀个外星⼈,再⽣成⼀个外星舰队。然后让这群外星⼈向两 侧和向下移动,并删除被⼦弹击中的外星⼈。最后,显⽰玩家拥有的 ⻜船数量,并在玩家的⻜船⽤完后结束游戏。 通过阅读本章,你将更深⼊地了解 Pygame 和⼤型项⽬的管理,还将学习如 何检测游戏元素之间的碰撞,如⼦弹和外星⼈之间的碰撞。检测碰撞有助 于定义游戏元素之间的交互。例如,可以将⾓⾊限制在迷宫的墙壁之间或 让两个⾓⾊相互传球。我们将不时地查看游戏开发计划,确保编程⼯作不 偏离轨道。 着⼿编写在屏幕上添加外星舰队的代码前,不妨回顾⼀下这个项⽬,并更 新开发计划。 13.1 项⽬回顾 在开发⼤型项⽬时,进⼊每个开发阶段前都要回顾⼀下开发计划,牢记接 下来要通过编写代码完成哪些任务。本章将完成以下开发计划。 在屏幕左上⾓添加⼀个外星⼈,并指定合适的边距。 沿屏幕上边缘添加⼀⾏外星⼈,再不断地添加成⾏的外星⼈,直到填 满屏幕的上半部分。 让外星⼈向两侧和向下移动,直到外星舰队被全部击落、有外星⼈撞 到⻜船或有外星⼈抵达屏幕的下边缘。

13.1 项目回顾

13.1 项⽬回顾 在开发⼤型项⽬时,进⼊每个开发阶段前都要回顾⼀下开发计划,牢记接 下来要通过编写代码完成哪些任务。本章将完成以下开发计划。 在屏幕左上⾓添加⼀个外星⼈,并指定合适的边距。 沿屏幕上边缘添加⼀⾏外星⼈,再不断地添加成⾏的外星⼈,直到填 满屏幕的上半部分。 让外星⼈向两侧和向下移动,直到外星舰队被全部击落、有外星⼈撞 到⻜船或有外星⼈抵达屏幕的下边缘。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.1 项目回顾 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.2 创建第一个外星人

13.2 创建第⼀个外星⼈ 在屏幕上放置外星⼈与放置⻜船类似。每个外星⼈的⾏为都由 Alien 类控 制,我们将像创建 Ship 类那样创建这个类。简单起⻅,这⾥还是使⽤位 图来表⽰外星⼈。你既可以⾃⼰寻找表⽰外星⼈的图像,也可使⽤图 13-1 所⽰的图像,它可在本书的源代码⽂件中找到 ( chapter_13/creating_first_alien/images/alien.bmp )。这幅图像的背景为灰 ⾊,与屏幕的背景⾊⼀致。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.2 创建第一个外星人 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.3 创建外星舰队

13.3 创建外星舰队 要绘制外星舰队,需要确定如何使⽤外星⼈填充屏幕的上半部分,同时避 免游戏窗⼝过于拥挤。实现这个⽬标的⽅式有很多,我们将采取如下⽅ 法:沿屏幕上边缘⽔平向右不断地添加外星⼈,直到填满⼀整⾏;然后重 复这个过程,直到没有⾜够的垂直空间供我们再添加⼀⾏为⽌。 13.3.1 创建⼀⾏外星⼈ 现在可以创建⼀整⾏外星⼈了。我们⾸先创建⼀个外星⼈,以便能够访问 其宽度。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.3 创建外星舰队 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.4 让外星舰队移动

13.4 让外星舰队移动 下⾯来让外星舰队在屏幕上向右移动,到达屏幕右边缘后下移⼀定的距 离,再向左移动,依此类推。这样不断地移动所有的外星⼈,直到外星⼈ 都被击落、有外星⼈撞上⻜船或有外星⼈抵达屏幕的下边缘。下⾯先来让 外星⼈向右移动。 13.4.1 向右移动外星舰队 移动外星舰队需要使⽤ alien.py 中的 update() ⽅法。对于外星舰队中的 每个外星⼈,都要调⽤它。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.4 让外星舰队移动 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.5 击落外星人

13.5 击落外星⼈ 我们创建了⻜船和外星舰队,但⼦弹在击中外星⼈时,将穿过外星⼈,因 为还没有检查碰撞。在游戏编程中,碰撞指的是游戏元素有重叠。为了让 ⼦弹能够击落外星⼈,我们将使⽤函数 sprite.groupcollide() 检测 两个编组的成员之间的碰撞。 13.5.1 检测⼦弹和外星⼈的碰撞 当⼦弹击中外星⼈时,我们需要⻢上知道,以便在碰撞发⽣后让⼦弹⽴即 消失。为此,将在更新所有⼦弹的位置后(绘制⼦弹前)⽴即检测碰撞。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.5 击落外星人 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.6 结束游戏

13.6 结束游戏 如果玩家根本不会输,游戏还有什么趣味和挑战性可⾔?因此,如果玩家 没能在⾜够短的时间内将整个外星舰队全部击落,导致有外星⼈撞到了⻜ 船或到达屏幕的下边缘,⻜船将被摧毁。与此同时,我们还会限制玩家可 使⽤的⻜船数。在玩家⽤光所有的⻜船后,游戏将结束。 13.6.1 检测外星⼈和⻜船的碰撞 ⾸先检查外星⼈和⻜船之间的碰撞,以便能够在外星⼈撞上⻜船时做出合 适的响应。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.6 结束游戏 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.7 确定应运行游戏的哪些部分

13.7 确定应运⾏游戏的哪些部分 我们需要确定游戏的哪些部分在所有情况下都应运⾏,哪些部分仅在游戏 处于活动状态时才运⾏: alien_invasion.py def run_game(self): """ 开始游戏的主循环 """ while True: self._check_events() if self.game_active: self.ship.update() self._update_bullets() self._update_aliens() self

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.7 确定应运行游戏的哪些部分 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

13.8 小结

13.8 ⼩结 在本章中,你⾸先通过创建外星舰队学习了如何在游戏中添加⼤量相同的 元素,如何使⽤嵌套循环来创建成⾏成列的整⻬元素,以及如何通过调⽤ 每个元素的 update() ⽅法移动⼤量的元素。接着学习了如何控制对象在 屏幕上的移动⽅向,以及如何响应特定的情形,如有外星⼈到达屏幕边 缘。然后学习了如何检测并响应⼦弹和外星⼈的碰撞以及外星⼈和⻜船的 碰撞。最后,你学习了如何在游戏中跟踪统计信息,以及如何使⽤标志 game_active 来判断游戏是否结束。

这节解决什么:这一节主要解决《第 13 章 外星人》里一个更具体的子问题:13.8 小结 该怎样落到实际代码或项目动作上。

怎么用到自己的系统:不要只把它当书中的小节标题,最好把它改写成一个你能亲手运行、修改或调试的微练习。

提醒:常见误区是只看懂例子表面流程,却没有把这一节抽成自己能迁移的规则。

辨析题

为什么项目一进入‘批量对象’阶段,代码就容易失控?

本章图版学习路径

一旦对象成群出现,程序结构就必须升级

碰撞问题本质上是在处理对象关系和状态更新