
第37课做完,Tyree的陨石已经会左摇右摆地飘移,不再是直线下降了。他玩得不亦乐乎,但他很快又提出了新问题:
“每次运行程序就直接开始游戏,我想先看看规则、调整一下心情都不行。而且玩得正激烈,想暂停抽个空上个厕所也没有哪个键?”
这个问题提得很好!一个正常的游戏就应该有开始菜单和暂停功能。今天我们就把它加上去。
他兴奋地说:“那我要做一个开始画面,背景是星空,上面写着‘太空避障’,按任意键开始。暂停的时候屏幕变暗,显示‘暂停’两个字。”
今天课程就来实现这两个重要的“状态”。下面我们来看看如何用状态机管理游戏的不同阶段(开始、游戏中、暂停、游戏结束),
如何让游戏暂停时停止所有运动逻辑,以及如何设计一个简单的开始菜单。
这节课的代码会稍微多一点点,但我会一步一步拆解清楚,跟着敲就能懂。
01. 什么是“状态”?为什么要用状态机?
在之前的版本里,我们用了一个`game_over` 变量来判断游戏是否结束。
但一个完整的游戏通常有多个阶段:开始界面、游戏中、暂停、游戏结束。
如果只用一堆布尔变量(比如 `is_paused`, `is_game_over`, `is_start`)会非常混乱,容易出错。
状态机就是用一个变量`game_state` 来记录当前处于哪个阶段,比如:
`START`:显示开始菜单,等用户按开始键
`PLAYING`:正常游戏,飞船移动、陨石下落、碰撞检测
`PAUSED`:暂停,画面静止,等用户按恢复键
`GAME_OVER`:显示结束画面,等用户选择重玩或退出
这样程序逻辑非常清晰:每个状态只干自己该干的事,切换状态只需要改变`game_state` 的值。
# 定义状态常量(用数字代表,方便比较)

为什么用数字?因为比字符串快一点点,而且方便。你也可以用字符串`"start"`, `"playing"` 等,但初学者用数字更直观。
状态变量就像红绿灯:绿灯(PLAYING)走,红灯(PAUSED)停,黄灯(START)准备,红灯闪(GAME_OVER)结束。
02. 开始菜单的绘制
当`game_state == START` 时,我们需要显示一个漂亮的界面,告诉玩家按什么键开始。
代码如下:

`get_width()` 获取文字的像素宽度,除以2得到半宽,再用窗口半宽400减去它,文字就居中了。
你可以自己尝试加更多装饰,比如画几颗闪烁的星星(随机画几个白点)。
开始菜单里不需要更新游戏逻辑,只需要画界面和等待按键。
如果用户按空格或回车,我们就调用`reset_game()` 重置所有变量,然后把 `game_state` 改为 `PLAYING`,游戏就开始了。
03. 暂停功能的实现
在游戏中,按ESC 键应该进入暂停状态。此时游戏逻辑(飞船移动、子弹、陨石等)必须停止,但画面要保持住(不能黑屏)。
同时显示一个半透明遮罩和“暂停”文字,提示按 ESC 继续。

`pygame.Surface((800,600), pygame.SRCALPHA)` 创建了一个可以带透明度的画板。
`overlay.fill((0,0,0,128))` 用半透明黑色填充,这样背后的游戏画面还能隐约看到。
在暂停状态下,主循环中不要更新任何游戏数据(不移动飞船、不移动陨石、不更新子弹、不检测碰撞),
只绘制这个暂停界面,然后等待用户按ESC 恢复。
恢复时,直接将
`game_state` 改回 `PLAYING`,游戏继续。
04. 修改主循环结构(状态分发)
原来的主循环里面只有游戏逻辑。现在我们需要根据`game_state` 来执行不同的代码块。这是最关键的部分。


每个状态只处理自己该干的事,互不干扰。
`reset_game()` 必须重置所有游戏变量(分数、生命、子弹、陨石、飞船位置等),并且不要重置状态(因为状态由外面决定)。
注意在`PLAYING` 状态中,如果生命值归零,我们设置 `game_state = GAME_OVER`,而不是直接退出。
在`PAUSED` 状态中,没有更新游戏逻辑,只是等待按键,所以游戏画面会“冻住”。
这种结构非常清晰,以后你想加一个“商店”状态也很容易。
05. 整合后的完整代码(可直接运行)
由于代码较长,我会在第37课基础上的完整代码进行修改,给出完整的第38课代码。
你需要把之前所有的函数
(如 `load_highscore`, `save_highscore`, `reset_game`, `draw_health_bar`, `draw_score`, `draw_difficulty`, `draw_game_over` 等)都保留,
并且按照上面的状态机结构重新组织主循环。
为了不使回复过于冗长,后面会在评论区里贴上修改后的完整代码,
你可以直接复制保存为`.py` 文件运行(确保有 `ship.png` 图片和音效文件,没有也没关系)。

下面来看一下运行后的界面,是不是正规了好多吖:




如何使用:
复制代码到新文件,保存为 `.py`。
将 `ship.png` 图片和音效文件(`shoot.wav`, `explode.wav`)放在同一目录(没有也没关系,会自动生成默认图形)。
运行后首先看到开始菜单,按空格或回车开始游戏。
游戏中按 ESC 进入暂停,再按 ESC 恢复。
生命归零后显示游戏结束画面,按 R 重玩,按 Q 退出。
06. 今天学到了什么
状态机:用一个变量控制游戏处于开始、进行、暂停、结束等状态,代码更清晰。
开始菜单:游戏启动时显示标题和提示,按开始键进入游戏。
暂停:按ESC 后停止所有逻辑更新,显示半透明层和“暂停”文字。
游戏结束:“生命”归零后显示分数和最高分,提供重玩或退出选项。
事件分流:不同状态下只处理相关按键,避免混乱。
Tyree看到自己的游戏终于有了像样的开始画面和暂停功能,得意地说:“现在我可以先看看最高分,再开始挑战,中途还能去喝水了!”
好了,第38课完成!
下一节课:添加分数和生命值HUD美化——把数字换成漂亮的图标和进度条,让界面更精致。
————热门推荐————
少儿自学编程第35课:做个更像样游戏的计分板,再加个游戏结束画面
自学编程第7课:turtle画图入门(画一个正方形,五角形,螺旋形,三角形)
自学编程第2课:用input让电脑问你名字(做一个打招呼程序)
自学编程第一步:安装Python和Thonny(零基础图文教程)
(本系列教程每天更新,欢迎关注收藏)