(十六)把移动、碰撞、计分按次序推进
1. 同一个逻辑步中的完整动作
继续放入 World.step。输入是本步秒数和横向方向,返回这一步实际产生的动作消息:
# 续写同一文件:dodge_project/src/dodgegame/models.py
def step(self, dt, axis):
if not math.isfinite(dt) or dt < 0 or not math.isfinite(axis):
raise ValueError("时间与输入方向应为有限数,时间不能为负")
if self.mode != Mode.PLAYING:
return []
old_player = self.player.box()
target_x = self.player.x + max(-1, min(1, axis)) * PLAYER_SPEED * dt
target_x = max(LEFT, min(RIGHT - PLAYER_W, target_x))
dx = target_x - self.player.x
hits = []
for obstacle in self.obstacles:
# 障碍向下移动,等价于在它的旧位置观察玩家相对向上移动。
when = collision_time(old_player, obstacle.box(), dx, -obstacle.speed * dt)
if when is not None:
hits.append((when, obstacle.uid))
if hits:
when, self.hit_uid = min(hits)
self.player.x += dx * when
for obstacle in self.obstacles:
obstacle.y += obstacle.speed * dt * when
self.elapsed += dt * when
self.mode = Mode.GAME_OVER
return ["hit"]
self.player.x = target_x
for obstacle in self.obstacles:
obstacle.y += obstacle.speed * dt
# 先检查碰撞,再判断离开;撞上的障碍不能因为同一步已离屏而加分。
passed = [obstacle for obstacle in self.obstacles if obstacle.y >= BOTTOM]
self.obstacles = [
obstacle for obstacle in self.obstacles if obstacle.y < BOTTOM
]
self.score += len(passed)
self.best = max(self.best, self.score)
self.elapsed += dt
self.spawn_elapsed += dt
while self.spawn_elapsed + 1e-12 >= SPAWN_INTERVAL:
self.spawn_elapsed = max(0.0, self.spawn_elapsed - SPAWN_INTERVAL)
self.spawn()
return ["score"] * len(passed)先取得玩家旧位置,计算目标位置,再限制在游戏区域;最终 dx 用限制后的新旧位置相减。对每个障碍,玩家相对位移是 (dx, −下落距离),交给前面已经推导的 collision_time。
所有障碍的候选接触比例一起比较,选择本步最早的一次。发生碰撞时,玩家和全部障碍都只走到同一个比例,记录 hit_uid 并进入 GAME_OVER。这样画面中的其他障碍也不会在结束后又推进一整步。
2. 未发生碰撞时再结算离屏分数
没有碰撞就完成整步移动,然后找出顶部已经到 BOTTOM 的障碍。它们完整离开游戏区域,每块加 1 分,再从列表移除。下一步它们已经不在列表中,不会再次加分。
最后更新时间与生成累计器。顺序为“碰撞 → 离屏计分 → 生成下一批”:一块快速跨过玩家又离屏的障碍,会先进入碰撞分支结束本局,不会同时被算作躲避成功。