10. Update Method

10. Update Method:让每个活跃对象把长行为切成逐帧可恢复的更新片段,通过可复位因果实验和反例证据验收。

10. Update Method

学习目标

  • 能说明 update(dt) 的逐帧推进
  • 能管理跨帧状态书签
  • 能处理遍历中增删实体

为什么"10. Update Method"从问题证据开始

游戏里每个活着的实体——敌人巡逻、弹幕飞行、粒子消散——都有"每一帧都在推进"的行为。最直觉的实现是把这些行为写进游戏循环:for each enemy: enemy.advance(); for each bullet: bullet.fly(); ...。代码今天能跑,但每个新实体类型都要改游戏循环;更糟的是,行为一旦跨多帧(巡逻 3 秒后追击),就要在循环里手工记录"巡逻到第几帧了",状态管理散落各处。

本页把"无模式基线"定义为"游戏循环直接驱动所有行为"的代码,然后引入更新方法作为候选机制,验证它是否真的让"新增实体类型不动循环代码、跨帧行为可管理"。通过条件是实体各自负责自己的 update(dt),循环只负责遍历——而不是循环认识每个实体类型。

🔮 先预测:实体 A 的 update 能看到实体 B 本帧已更新的结果吗?

来源、版本与独立重写边界

本页用作者完整在线正文核对正式标题、设计分叉和时代语境,并以作者源码仓库交叉检查结构。仓库许可证明确正文、HTML与样式为 CC BY-NC-ND 4.0,示例程序等其他文件为 MIT;因此下列中文解释、图示、交互和代码均为独立教学重写,不翻译、拼接或改写受 ND 限制的原文表达。

本章机制与术语

理解 Update Method 需要四个核心概念:

  • :实体上的 update(dt) 方法——每帧被调用一次,推进该实体一段行为。
  • :更新方法的参数,表示本次更新推进的模拟时间量——保证行为与帧率无关。
  • :持有实体列表、每帧遍历并逐个调用 update 的容器。
  • :把"巡逻 3 秒"这类长行为切成每帧一小段——update 里推进一小步,跨帧累积。

四个概念共同约束"让每个活跃对象把长行为切成逐帧可恢复的更新片段":任何结论都必须回到"新增实体是否不动循环、行为是否与帧率无关、状态是否可恢复"三个可观察量。

Sequencing Pattern · Update Method

Update Method — 让每个实体自己决定每帧做什么

▷ 可交互
世界只负责喊"这一帧开始了",实体各自响应长行为(巡逻→追击→攻击)被切成逐帧可恢复的更新片段GameWorld实体列表:敌人 ×2 · 弹幕 ×1 · 粒子 ×1update(): for each entity → entity.update(dt)敌人 Aupdate(dt) ✓敌人 Bupdate(dt) ✓弹幕update(dt) ✓粒子update(dt) ✓每帧遍历注意:遍历中增删实体列表会踩空;休眠/停用对象应跳过 update

第 1 / 5 步 · ① GameWorld:持有实体列表,每帧遍历调用 update(dt)

把 AI 行为、物理、动画都做成 update(dt):世界循环简单,实体各自负责自己的那一帧。

💡 对着动画看:动画顶部是 GameWorld 容器——它每帧做一件事:遍历实体列表,逐个调 entity.update(dt)。下方四个实体(敌人/弹幕/粒子)各自响应。注意"每帧遍历"小框——世界不认识具体实体类型,只认识 update() 接口。读"实体子类化"一节时,想想四个实体是如何各自实现这个接口的。

官方结构逐项深读

10. Update Method

Update Method 的主张:给每个活跃实体一个 update(dt) 方法,游戏世界每帧遍历调用。世界不关心实体内部在做什么(AI、物理、动画都是 update 内部的细节),只负责"喊这一帧开始了"。新增实体 = 实现 update() 的类 + 加入世界列表,循环代码一行不改。

Intent

意图:让游戏循环保持简单,把行为复杂度下放到每个实体。循环只做三件事——遍历、调 update、传给 dt。实体自己决定"这一帧我推进多少、要不要转换状态、要不要死亡"。这解决了"循环认识所有实体类型"的耦合,也让行为可以优雅地跨帧。

Motivation

想象一个"女巫施法"的行为:挥杖 0.5 秒 → 蓄力 1 秒 → 释放。如果写成一个阻塞函数 castSpell(),游戏循环会卡住 1.5 秒(期间世界冻结)。Update Method 把施法切成三段的 update:每帧推进当前阶段,阶段完成切下一段——长行为被切成逐帧可恢复的片段,世界永不冻结。

The Pattern

模式结构:抽象实体基类声明 virtual void update(float dt) = 0;具体实体实现各自的 update;游戏世界持 Entity* 列表,每帧 for (e : entities) e->update(dt)。dt 从游戏循环传入(与 Game Loop 模式衔接)。更新顺序 = 列表顺序,同一帧内实体按序更新——这不是并发,是顺序模拟。

When to Use It

几乎所有活跃实体都适合:敌人 AI、弹幕、粒子、动画播放器、环境物件。不适用的场景:不随帧变化的东西(静态地形)、由事件驱动的行为(一次性交互,用命令或事件更合适)。判断标准:这个对象"每帧都有事做"吗?是 → Update Method;否 → 别让它进更新列表。

Keep in Mind

四个注意点:单帧切片让代码更复杂(长行为被拆成状态机);必须保存跨帧状态(update 结束时把进度记下来,下帧继续);实体不是真正并发(同一帧按序执行,A 的 update 能看到 B 本帧已更新的结果);遍历中修改列表危险(实体死亡时不能直接删,要延迟处理)。

Splitting code into single frame slices makes it more complex

把"施法 1.5 秒"写成一段顺序代码很直观,但 update 只能推进一帧——你必须把它改写成"阶段机":每帧检查当前阶段、推进该阶段的进度、满了就切阶段。代码从"线性叙述"变成"状态机",理解成本上升。作者的建议:先用简单方式(每帧一步到位),行为真的需要跨帧时再切帧。

You have to store state to resume where you left off each frame

跨帧行为必须把进度存起来:castTimer_(施法已进行多久)、currentPhase_(当前阶段)。update 里读这些字段决定"这一帧做什么",写回更新后的值。状态字段就是行为的"书签"——没有书签,下帧不知道从哪继续。这是 Update Method 与 State 模式衔接的地方:复杂跨帧行为常配状态机。

Objects all simulate each frame but are not truly concurrent

重要澄清:所有实体每帧都更新,但不是并行——按列表顺序逐个执行。这意味着实体 A 的 update 能看到实体 B 在本帧已更新后的状态。这既是特性(实体间可直接交互)也是陷阱(更新顺序影响结果——"先移动 A 还是先移动 B"可能改变碰撞结果)。需要确定性时,更新顺序必须固定。

Be careful modifying the object list while updating

实体在 update 里死亡、生成,直接修改列表会让遍历踩空(删了当前项、迭代器失效)。标准解法:延迟修改——死亡实体先标记"待移除",遍历结束后统一清理;生成的新实体先进"待加入"队列,下帧再并入。这是所有"遍历 + 动态增删"场景的通则。

Sample Code

示例代码:Entity 基类(纯虚 update)、Skeleton/PatrolBot 等子类实现各自行为、World 持列表每帧遍历。一个完整的"巡逻兵"例子:PatrolBot::update(dt) 里累计移动距离,到点转向——行为被切成逐帧的位移片段,跨帧状态用成员变量保存。

Subclassing entities?!

实体用继承树组织:Entity 基类 → AnimalDogFish…… 每层加行为。问题:多维度变化时继承爆炸("会飞的会游泳的狗"要多少层?)。Update Method 本身不强制继承,但实践中常与 Component 模式结合——实体 = 组件容器,每个组件有自己的 update。作者提示:继承适合"种类稳定",组合适合"能力多变"。

Defining entities

两种定义实体的方式:代码定义(C++ 类层次,编译期固定——类型安全但改种类要重编译)与数据定义(实体由数据/脚本描述,update 逻辑从数据读取——策划可动态配置,与 Prototype/Type Object 呼应)。现代引擎(Unity/Unreal)走数据驱动:update 是引擎调用的钩子,行为由挂载的组件与脚本决定。

Passing time

update(dt) 的 dt 从哪来?来自 Game Loop:固定步长(每帧 dt 恒定,如 16ms——行为确定可复现)或可变步长(dt = 实际耗时——行为平滑但不确定)。作者建议:用固定步长保证确定性(配合插值),这在 Update Method 与 Game Loop 两章间是同一决策。dt 传下去,实体才知道"这帧推进多少"。

Design Decisions

两个关键决策:update 方法放哪个类(基类虚函数 vs 组件接口 vs 自由函数回调)和 休眠对象怎么处理(仍在列表但跳过 vs 移出列表)。前者决定继承结构,后者决定性能与代码复杂度——下面两节展开。

What class does the update method live on?

update 放哪:实体基类(所有实体继承一个带 update 的基类——简单直观,但继承树僵化);组件接口(每个组件有自己的 update,实体组合多个组件——灵活,配合 Component 模式);自由函数/回调(update 是独立函数,实体持回调——无继承依赖,但状态管理要自理)。选择依据:你的实体是"继承树"还是"组件组合"。

How are dormant objects handled?

休眠对象(暂停的敌人、休眠的陷阱)怎么办:仍在列表但 update 里检查标志跳过(实现简单,但每帧仍有遍历开销);移出更新列表(省遍历,但恢复时要重新加入,管理复杂);用轻量"激活计数"(引用计数决定是否跳过——介于两者之间)。作者倾向:多数情况"标志跳过"够用,列表很长时再考虑移出。

See Also

  • Game Loop:Update Method 是 Game Loop 的天然搭档——循环调 update,实体响应。
  • Component:把 update 拆到组件上,实体成为组件容器——复杂实体免于继承爆炸。
  • State:跨帧行为的"阶段切换"用状态机管理——update 里查状态、按状态推进。

可迁移实现或计算骨架

initial state -> 实体实现 update(dt) -> 世界列表每帧遍历 -> 行为逐帧推进 -> 跨帧状态字段保存
fault injection -> 游戏循环直接驱动每种实体行为
pass condition -> 新增实体不动循环、行为与帧率无关、跨帧状态可恢复
reset -> initial state

对应实现要点:实体基类声明纯虚 update(dt);世界持列表每帧遍历;跨帧行为用状态字段存进度;死亡/生成走延迟修改。该骨架只保存实验合同;真实项目还要固定更新顺序、休眠策略与列表修改规则,并保留基线实现以便回退。

本章练习与节点验证矩阵

练习

问题 1:新增实体。

操作:在动画实体列表中"加入"一个"追踪弹",观察世界代码是否需要改

问题 2:帧率无关验证。

操作:用固定 dt 与可变 dt 分别跑同一实体行为,比较结果

问题 3:跨帧状态。

操作:模拟"巡逻 3 秒后追击",记录跨帧状态字段

问题 4:列表修改。

操作:模拟实体在 update 中死亡(直接删列表)vs 延迟移除

术语复核

名词解释

本章出现的专业名词,用大白话再讲一遍。

更新方法
时间步
逐帧片段

练习答案参考

练习判据即答案:每个练习的"验证判据"列给出了通过标准——先自己动手,再对照判据核验。

术语复核与本章回顾

掌握"10. Update Method"意味着能从"游戏循环直接驱动所有行为让循环认识每个实体"出发,解释更新方法、时间步、游戏世界与逐帧片段四者的关系,再用"新增实体是否不动循环、行为是否与帧率无关、状态是否可恢复"三个可观察量推翻或保留实现。若三个判据不能同时满足,本章仍未通过。

一句话回顾:世界只喊"这一帧开始了",实体各自 update(dt)——长行为切成逐帧片段,跨帧状态存书签,游戏循环从此只有三行。

阅读导航

← 上一页:9. Game Loop · 下一页:IV. Behavioral Patterns →

← 上一页:9. Game Loop · 下一页:IV. Behavioral Patterns →

资料与写作方式声明

本章以Robert Nystrom《Game Programming Patterns》(游戏编程模式)权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…