← 返回 Day 89 正文

Day 89 · 图解

Day 089 — 游戏循环与事件:图解

游戏循环架构图

                    ┌─────────────────────┐
                    │     pygame.init()    │
                    │   创建窗口、加载资源   │
                    └──────────┬──────────┘
                               ▼
                ┌──────────────────────────────┐
                │         while running:        │◄─────────────────────┐
                └──────────────┬───────────────┘                      │
                               ▼                                      │
              ┌────────────────────────────────┐                      │
              │     for event in events:       │                      │
              │       处理 QUIT → running=False │                      │
              │       处理 KEYDOWN → 响应输入    │                      │
              │       处理 MOUSE → 响应鼠标     │                      │
              └───────────────┬────────────────┘                      │
                              ▼                                       │
              ┌────────────────────────────────┐                      │
              │         update(dt)              │                      │
              │   移动物体 / 碰撞检测 / AI 逻辑   │                      │
              └───────────────┬────────────────┘                      │
                              ▼                                       │
              ┌────────────────────────────────┐                      │
              │         render(screen)          │                      │
              │   清屏 → 绘制 → display.flip()  │                      │
              └───────────────┬────────────────┘                      │
                              ▼                                       │
              ┌────────────────────────────────┐                      │
              │       clock.tick(60)            │                      │
              │   控制帧率 / 获取 dt 时间间隔     │                      │
              └───────────────┬────────────────┘                      │
                              │                                       │
                              ├── running=True ───────────────────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │    pygame.quit()     │
                    │      程序退出        │
                    └─────────────────────┘

事件处理流程图

                    ┌─────────────────┐
                    │  用户操作触发     │
                    │  (键盘/鼠标/...)  │
                    └────────┬────────┘
                             ▼
                    ┌─────────────────┐
                    │  事件入队到       │
                    │  pygame 事件队列  │
                    └────────┬────────┘
                             ▼
              ┌──────────────────────────────┐
              │    for event in events:      │
              │    pygame.event.get()        │
              └──────────────┬───────────────┘
                             │
            ┌────────────────┼────────────────┐
            ▼                ▼                ▼
    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
    │  KEYDOWN     │ │  MOUSEBUTTONDOWN │ │  QUIT       │
    │  按键按下     │ │  鼠标点击      │ │  窗口关闭    │
    └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
           ▼                ▼                ▼
    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
    │  改变方向     │ │  发射子弹     │ │  running     │
    │  触发动作     │ │  选择目标     │ │  = False     │
    └──────────────┘ └──────────────┘ └──────────────┘

两种输入模式对比

┌─────────────────────────────────────────────────────────────┐
│                    KEYDOWN 模式(事件驱动)                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  按下键的瞬间:                                               │
│  ├─ 触发 KEYDOWN 事件                                        │
│  ├─ 只执行一次                                               │
│  └─ 适合: 跳跃、射击、菜单选择(一次性动作)                     │
│                                                             │
│  时间线:                                                     │
│  ──按下──|──────按住期间──────|──松开──                        │
│     ↑                                              ↑        │
│  KEYDOWN                                      KEYUP        │
│  (触发一次)                                    (触发一次)    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│               get_pressed() 模式(状态轮询)                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  每一帧都检查:                                               │
│  ├─ 返回 True/False 的状态数组                               │
│  ├─ 按住期间每帧都为 True                                     │
│  └─ 适合: 移动、持续攻击(持续性动作)                          │
│                                                             │
│  时间线:                                                     │
│  ──按下──|──────按住期间──────|──松开──                        │
│     ┌──── True ────────────────┐                            │
│     │  每帧都检测到 True         │                            │
│     │  → 每帧都执行移动          │                            │
│     └──────────────────────────┘                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

帧率控制原理

Clock.tick(60) 的工作原理:

目标: 每帧至少 1/60 秒 (约 16.67ms)

帧 1: 处理(5ms) + 更新(3ms) + 渲染(4ms) = 12ms
       tick(60) 等待 4.67ms → 实际 16.67ms ✓

帧 2: 处理(5ms) + 更新(3ms) + 渲染(4ms) = 12ms
       tick(60) 等待 4.67ms → 实际 16.67ms ✓

帧 3: 处理(5ms) + 更新(20ms) + 渲染(4ms) = 29ms  ← 慢帧!
       tick(60) 等待 0ms (已超时) → 实际 29ms ✗
       → FPS 降低,但游戏不会加速追赶

┌──────┐ ┌──────┐ ┌──────────────┐ ┌──────┐
│ 12ms │ │ 12ms │ │    29ms      │ │ 12ms │ ...
└──────┘ └──────┘ └──────────────┘ └──────┘
  60FPS    60FPS    ~34FPS           60FPS

贪吃蛇游戏逻辑流程

┌─────────────────────────────────────┐
│          初始化蛇和食物              │
└──────────────┬──────────────────────┘
               ▼
    ┌─────────────────────┐
    │   等待输入事件        │◄─────────────────┐
    └──────────┬──────────┘                  │
               ▼                             │
    ┌─────────────────────┐                  │
    │  计算新蛇头位置       │                  │
    │  head + direction   │                  │
    └──────────┬──────────┘                  │
               ▼                             │
    ┌─────────────────────┐    是             │
    │  蛇头撞墙或撞自己?   │─────────────→ GAME OVER
    └──────────┬──────────┘
               │ 否
               ▼
    ┌─────────────────────┐    是
    │  蛇头 == 食物位置?   │──────→ 分数+10, 吃掉食物
    └──────────┬──────────┘        (不缩短蛇身)
               │ 否
               ▼
    ┌─────────────────────┐
    │  添加新蛇头           │
    │  移除蛇尾            │
    └──────────┬──────────┘
               │
               ▼
         渲染画面 ──────────────────→ 回到等待输入