Python 现代开发学习笔记

(十四)从暂停与重开得到游戏状态

1. 不同画面共享一个场景状态

开始画面要等待操作,进行中才移动和生成,暂停要保留本局,结束则展示分数。用四个状态表达这些差异,比为“已经开始”“暂停了”“撞到了”分别维护多个布尔量更容易跟踪。

状态 继续响应的操作 是否推进游戏运动
TITLE 开始、声音、退出 否
PLAYING 移动、暂停、声音、退出 是
PAUSED 恢复、声音、退出 否
GAME_OVER 重开、声音、退出 否
开始进入进行中,暂停与恢复往返,碰撞进入结束,重开清空本局后再次进行

2. 先确定单位与数据,再连接操作

完整游戏使用宽 800、高 600 的窗口;顶部留给标题和分数,玩家在游戏区域底部左右移动:

# 项目文件:dodge_project/src/dodgegame/config.py
# 距离用像素,速度用像素/秒;游戏区域位于标题栏下方。
WIDTH, HEIGHT = 800, 600
LEFT, RIGHT = 32, 768
TOP, BOTTOM = 108, 568
PLAYER_W, PLAYER_H = 48, 36
PLAYER_Y = 508
PLAYER_SPEED = 320.0
SPAWN_INTERVAL = 0.75
FIXED_STEP = 1 / 120

模型中用 Enum 为四个状态取明确名称:Mode.PLAYING 表示进行中,.name 可以显示 PLAYING。它把场景状态集中到一组名字中,避免字符串拼写散落在不同分支。

# 项目文件:dodge_project/src/dodgegame/models.py
# 状态和坐标不依赖窗口:可以单独计算,也能在测试里准确复盘。
from dataclasses import dataclass
from enum import Enum
import math
import random

from .config import (
    BOTTOM,
    LEFT,
    PLAYER_H,
    PLAYER_SPEED,
    PLAYER_W,
    PLAYER_Y,
    RIGHT,
    SPAWN_INTERVAL,
    TOP,
)
from .geometry import collision_time


class Mode(Enum):
    TITLE = "title"
    PLAYING = "playing"
    PAUSED = "paused"
    GAME_OVER = "game_over"


@dataclass
class Player:
    x: float = (LEFT + RIGHT - PLAYER_W) / 2
    y: float = PLAYER_Y
    width: int = PLAYER_W
    height: int = PLAYER_H

    def box(self):
        return self.x, self.y, self.width, self.height


@dataclass
class Obstacle:
    uid: int
    x: float
    y: float
    width: int
    height: int
    speed: float

    def box(self):
        return self.x, self.y, self.width, self.height

Player 和 Obstacle 继续使用第四章的数据类。box 方法交回四个浮点几何字段;障碍额外保存 uid 和下落速度,用于连接显示对象与本次碰撞。

3. 重置本局与保留最高分

# 续写同一文件:dodge_project/src/dodgegame/models.py
# 每轮重开都重置障碍、计分和计时;历史最高分仍由同一个 World 对象保留。
class World:
    def __init__(self, seed=None, best=0):
        self.seed = seed
        self.best = best
        self.mode = Mode.TITLE
        self.reset_round()

    def reset_round(self):
        self.rng = random.Random(self.seed)
        self.player = Player()
        self.obstacles = []
        self.score = 0
        self.elapsed = 0.0
        self.spawn_elapsed = 0.0
        self.next_uid = 1
        self.hit_uid = None

    def start(self):
        self.reset_round()
        self.mode = Mode.PLAYING

    def toggle_pause(self):
        if self.mode == Mode.PLAYING:
            self.mode = Mode.PAUSED
        elif self.mode == Mode.PAUSED:
            self.mode = Mode.PLAYING

reset_round 清空障碍、分数、累计时间与碰撞记录;best 留在 World 中。start 调用重置后进入 PLAYING;toggle_pause 只在 PLAYING 与 PAUSED 之间切换,不重建当前玩家和障碍。

Random 是本局自己的随机数发生器。给定相同 seed 会重新得到同样的障碍序列,方便复盘;默认没有指定 seed 时,每局重新取得随机序列。

笔记正文与全部题解 · Ctrl / ⌘ K