第二章

游戏循环:让世界“动起来”的心脏

第一层 · 帧循环/Update/坐标系 第一次让英雄在屏幕上动了起来

一、游戏和普通软件的本质区别

"小陈,你知道游戏和'记事本'这类软件最大的区别是什么吗?"老周问。

小陈想了想:"……游戏好玩?"

"哈哈哈,也对,但技术上更本质的区别是:普通软件是'事件驱动'的——你点一下,它响应一下;你不点,它就闲着。游戏是'连续驱动'的——哪怕你不碰它,它也在不停'动'。"

普通软件(记事本/浏览器):
  等待事件(点击/键盘)→ 处理 → 再等待
  用户不动 → 程序不动(CPU 基本闲着)

游戏:
  永远在循环:更新状态 → 渲染画面 → 更新状态 → 渲染画面
  哪怕屏幕上什么都没变,它也在以 60 次/秒的速度"活着"
  这个永不停歇的循环,就是 游戏循环(Game Loop)

"游戏循环是游戏的心脏——没有它,英雄就不会走、血条不会回、小兵不会动。它是游戏开发里第一个必须吃透的概念。"


二、游戏循环长什么样:三步骤永动

老周画出一个经典的游戏循环:

游戏循环(Game Loop):
      ┌─────────────────────────┐
      ▼                         │
  ┌──────────────┐              │
  │ 处理输入 Input │──┐          │
  └──────────────┘  │          │
  ┌──────────────┐  │          │
  │ 更新状态 Update│──┼──► 无限循环
  └──────────────┘  │          │
  ┌──────────────┐  │          │
  │ 渲染画面 Render│──┘          │
  └──────────────┘              │
      │                         │
      └─────────────────────────┘

  每循环一次 = 一帧(Frame)
  每秒循环次数 = 帧率(FPS,Frames Per Second)
  60 FPS = 每秒 60 帧 = 每帧约 16.7 毫秒
# 游戏循环的伪代码(理解本质用)
def game_loop():
    while game_running:
        handle_input()      # 1. 处理输入(键盘/触屏/鼠标)
        update()            # 2. 更新世界状态(移动、碰撞、AI)
        render()            # 3. 渲染画面(把世界画到屏幕)

"记住黄金三步:输入 → 更新 → 渲染。 所有游戏,无论多复杂,核心都是这个循环。王者荣耀的 5v5 对战、英雄的技能、小兵的行进——全在这个循环里每秒转 60 次。"


三、Update vs FixedUpdate:两种"心跳"

"小陈,你在 Unity 里会看到两个很眼熟的函数:UpdateFixedUpdate。它们都是循环的一部分,但节奏不同。"老周说:

Update(每帧调用,频率不固定):
  - 每秒可能被调用 30 次、60 次、120 次(取决于帧率)
  - 适合:渲染相关、输入响应、动画
  - 问题:帧率不稳定时,移动速度会"忽快忽慢"

FixedUpdate(固定频率调用,如每秒 50 次):
  - 不管帧率多少,每秒固定调用 N 次
  - 适合:物理计算、需要"确定性"的逻辑
  - 好处:移动/物理计算的结果不依赖帧率(稳定)

例子(英雄移动):
  错误写法:在 Update 里 position += speed * 1
    → 60 FPS 时每秒走 60 步,30 FPS 时走 30 步(快慢不一)
  正确写法:position += speed * deltaTime
    → deltaTime = 上一帧到这一帧的时间(秒)
    → 不管多少帧,每秒走的距离一样!
# deltaTime 的作用:让移动速度与帧率无关
def update(delta_time):
    # delta_time = 上一帧耗时(秒),如 0.0167
    hero.position.x += hero.speed * delta_time
    # speed = 每秒移动多少像素,如 100
    # 每帧移动 = 100 * 0.0167 ≈ 1.67 像素
    # 无论 30 FPS 还是 60 FPS,每秒都移动约 100 像素 ✓

"deltaTime 是游戏开发第一个'必背'的概念——它把'每帧移动多少'换算成'每秒移动多少',让游戏在不同性能的手机上表现一致。没有它,好手机上的英雄跑得飞快,烂手机上爬不动。"


四、坐标系:游戏世界的"地图"

"英雄要动,就得有'世界'——世界要有坐标系。"老周说:

2D 游戏坐标系(Unity):
        y 轴(向上)
        │
        │   英雄 (3, 2)
        │      ●
        │
  ──────┼─────────── x 轴(向右)
        │
   原点 (0, 0)

  屏幕坐标系(UI,注意不同!):
  左上角是 (0, 0),y 轴向下
  → 游戏世界坐标 vs 屏幕坐标要区分
  (引擎会帮你转换,但要心里有数)

  Unity 中:
    世界坐标(场景里的位置)
    屏幕坐标(显示在屏幕上的位置)
    局部坐标(相对父物体的位置)

  例子:英雄在峡谷里移动
    hero.position = new Vector2(3, 2)
    hero.position += new Vector2(0.1f, 0)  # 每帧向右移一点

"坐标系是'世界在哪'的问题——英雄、小兵、防御塔、技能特效,都要在世界坐标里定位。2D 游戏用二维坐标,3D 加一个 Z 轴。"


五、固定帧率 vs 可变帧率:两种游戏循环风格

"再深入一点,游戏循环有两种设计流派。"老周说:

① 固定时间步(Fixed Timestep):
   每帧固定推进 1/60 秒,慢机器就"慢动作"
   优点:逻辑确定(适合需要精确同步的游戏,如格斗)
   缺点:低帧率时游戏变慢

② 可变时间步(Variable Timestep):
   每帧按实际时间推进(deltaTime)
   优点:任何机器上都"同样速度"
   缺点:物理/网络同步时逻辑不确定

  实践:大多数现代游戏混合使用
    - 渲染:可变(跟随帧率)
    - 物理:固定(FixedUpdate 每秒 50 次)
    - 逻辑:按需求

  对"云间对决"的意义:
    对战逻辑要求"所有玩家看到的世界一致"
    → 逻辑用固定步长(后面第八章网络同步会讲)
    → 这是 MOBA 类游戏的核心要求之一

六、章末:老周的第一层总结

第一层:游戏循环
├── 游戏 vs 普通软件:连续驱动 vs 事件驱动
├── 循环三步:输入 → 更新 → 渲染(每帧重复)
├── 帧率 FPS:每秒循环次数(60 FPS = 每帧 16.7ms)
├── Update(每帧,不固定)vs FixedUpdate(固定频率)
├── deltaTime:让速度与帧率无关(必背!)
├── 坐标系:世界坐标/屏幕坐标/局部坐标
└── 固定步长 vs 可变步长(对战逻辑要确定)
下一章预告:
  循环有了,坐标有了——但屏幕上还什么都没有!
  怎么把"云间峡谷"搭出来?英雄的模型怎么放进去?
  —— 引擎与场景搭建:场景、预制体、资源。

"小陈,游戏循环是'心跳',但光有心跳,人还是空的。"老周说,"下一章,我们用 Unity 把'云间峡谷'真正搭出来——场景、预制体、资源管理。从这一章开始,你屏幕上会出现第一个会动的英雄。 那是所有游戏开发者最激动的一刻。"

✌ 语言