(十九)将事件与连续输入交给同一状态逻辑
1. 输入层只组合动作
Controller 接收前面已经建立的 World、Stepper、Renderer 与 SoundBank。先定义开始或恢复的共同入口:
# 项目文件:dodge_project/src/dodgegame/controller.py
# 离散事件改变场景与开关,连续轴值交给固定逻辑步;绘制由另一层负责。
import pygame
from .models import Mode
class Controller:
def __init__(self, world, stepper, renderer, sounds):
self.world, self.stepper = world, stepper
self.renderer, self.sounds = renderer, sounds
self.running = True
self.debug = False
self.changed = False
def activate(self):
if self.world.mode in {Mode.TITLE, Mode.GAME_OVER}:
self.world.start()
elif self.world.mode == Mode.PAUSED:
self.world.toggle_pause()activate 让 TITLE、GAME_OVER 进入一局,让 PAUSED 恢复;PLAYING 中按开始键不重置本局。键盘和鼠标继续调用同一个动作。
2. 离散事件更新状态与开关
# 续写同一文件:dodge_project/src/dodgegame/controller.py
# 把键盘、鼠标和失焦事件转成状态变化;切换状态时同时清掉累计的步进时间。
def handle_events(self, events):
for event in events:
before = self.world.mode
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.running = False
elif event.key == pygame.K_p:
self.world.toggle_pause()
elif event.key in {pygame.K_RETURN, pygame.K_SPACE}:
self.activate()
elif event.key == pygame.K_r and self.world.mode == Mode.GAME_OVER:
self.activate()
elif event.key == pygame.K_m:
self.sounds.toggle()
elif event.key == pygame.K_F1:
self.debug = not self.debug
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if self.renderer.pause_button.collidepoint(event.pos):
self.world.toggle_pause()
elif (
self.world.mode != Mode.PLAYING
and self.renderer.menu_button.collidepoint(event.pos)
):
self.activate()
elif (
event.type == pygame.WINDOWFOCUSLOST and self.world.mode == Mode.PLAYING
):
self.world.toggle_pause()
if self.world.mode != before:
self.changed = True
self.stepper.reset()
if not self.running:
break每项事件处理前后比较场景状态。状态发生变化就清空逻辑余量,并标记这一帧刚切换过。失去窗口焦点时,正在进行的游戏也进入暂停,下一次需要玩家主动恢复。
菜单暂停只停止 World 的运动,事件循环继续执行。退出事件则让 running 为 False,并停止处理本轮余下事件;不会在退出之后又响应一次暂停或重开。
3. 恢复时不补算菜单停留时间
# 续写同一文件:dodge_project/src/dodgegame/controller.py
def advance(self, seconds, axis):
if not self.running:
return []
if self.changed:
# 开始或恢复这一帧,不把刚才停在菜单里的时间加到游戏运动中。
self.changed = False
seconds = 0
events = self.stepper.advance(self.world, seconds, axis)
for event in events:
self.sounds.play(event)
return events开始或恢复这一帧,把传入的经过秒数置为 0,下一帧再正常推进。Stepper 返回的 score、hit 消息只在本步消费一次,再交给声音层。
先用一份已知状态检查结束与重开:
# 项目文件:demos/round_states.py
# 先用已知分数和相交障碍检查结束,再重开,观察本局清空而最高分保留。
from dodgegame.models import Obstacle, World
world = World(seed=7, best=5)
print(world.mode.name)
world.start()
world.score = 3
world.toggle_pause()
print(world.mode.name, world.score)
world.toggle_pause()
world.obstacles = [Obstacle(1, world.player.x, world.player.y - 20, 48, 24, 200)]
print(world.step(0.01, 0), world.mode.name, world.score)
world.start()
print(world.mode.name, world.score, world.best, len(world.obstacles))示例把本局分数设为 3、最高分设为 5,作为检查输入。状态先由 TITLE 进入一局,暂停保留 3 分;放入相交障碍后得到 ['hit'] GAME_OVER 3;再次开始得到 PLAYING 0 5 0,分别表示场景、本局分数、最高分和障碍数。