Q18: 什么是 Ghost / Shadow 机制?
核心结论
Ghost / Shadow 机制的本质,是把一个实体拆成不同职责的副本:
Real Entity负责权威逻辑Ghost Entity负责跨进程、跨分区的远端镜像Shadow State负责客户端的显示、预测与平滑
它解决的不是“怎么复制一个对象”,而是分布式世界里三个更关键的问题:
- 权威状态只能有一份
- 其他节点又必须能看到它
- 客户端需要一份适合渲染和补偿的本地表示
一、为什么需要这套机制
在单进程游戏里,一个实体通常只有一份状态;但在大世界或多 Cell 架构里,同一个玩家往往同时牵涉多个节点:
- 所在分区要运行它的真实逻辑
- 邻接分区要感知它的存在,处理边界交互和 AOI
- 登录、社交、任务等服务可能需要读到它的部分状态
- 客户端还要把它渲染出来,并对本地玩家做预测
如果所有节点都直接读写同一份对象,系统会立刻失控:
- 状态写入来源不清晰
- 迁移时很难切主
- 故障恢复边界模糊
- 客户端容易把显示状态误当权威状态
所以工程上通常会明确分层。
二、三类副本分别负责什么
1. Real Entity
Real Entity 是权威实体,通常存在于当前负责该实体的 Cell 或逻辑分区。
它负责:
- 移动与碰撞
- 技能、战斗、仇恨
- AOI 计算
- 状态变更的最终提交
核心约束只有一条:同一时刻只能有一个权威 Real。
2. Ghost Entity
Ghost Entity 是远端镜像,通常存在于:
- 邻近 Cell
- 需要观察该实体的其他逻辑节点
- 某些需要只读状态的服务进程
它的职责不是独立决策,而是:
- 接收 Real 的增量同步
- 提供局部可见状态
- 参与邻区 AOI、边界广播、迁移预热
Ghost 一般不应该自行推进权威逻辑,否则就会出现双写和分叉。
3. Shadow State
Shadow State 是客户端本地表示。这里刻意不用“客户端实体”去强调它不是服务端权威实体的平移版本,而是一层偏渲染和交互的状态包装。
它通常负责:
- 角色模型、动画、特效
- 插值和平滑
- 本地玩家预测
- 接收服务端校正
对于本地玩家,Shadow 可以先走一小步;对于远端实体,Shadow 更常见的是插值显示,而不是强预测。
三、典型数据流
一条常见的数据链路如下:
Real Entity -> Ghost Entity -> Client Shadow State
但这个链路不是固定只有一跳。实际工程里可能出现:
- Real 直接向客户端广播
- Real 同步给多个邻区 Ghost
- Ghost 再参与局部 AOI 或边界计算
无论链路怎么变化,原则不变:
- 逻辑最终来源是 Real
- Ghost 是受控镜像
- Shadow 是展示层状态
四、它和 AOI、分区迁移是什么关系
1. 和 AOI 的关系
AOI 决定“谁应该看到谁”,Ghost/Shadow 决定“看到了之后,用什么副本承载它”。
换句话说:
- AOI 负责筛选同步对象
- Ghost 负责在服务端跨节点保留远端镜像
- Shadow 负责在客户端把这些同步结果显示出来
没有 AOI,Ghost 数量会爆炸;没有 Ghost,跨分区感知会变得很重;没有 Shadow,客户端就只能生硬显示最新包。
2. 和迁移的关系
当实体跨分区移动时,系统通常不是“瞬间删除旧 Real,再创建新 Real”,而是先让目标分区准备好镜像,再完成主从切换。
一个更稳妥的迁移流程通常是:
- 目标分区提前创建该实体的 Ghost
- 持续接收权威状态,完成预热
- 到达切换条件后,目标分区提升为新的 Real
- 旧分区降级为 Ghost 或进入回收流程
- 周边 AOI 和客户端连接关系再更新
这样做的价值是降低切换抖动和状态丢失风险。
五、常见设计边界
1. Ghost 不是“第二个 Real”
Ghost 可以持有很多状态,但不应该独立得出最终战斗结果,也不应该私自改写关键属性。
如果 Ghost 也能执行关键逻辑,就会出现:
- 同一技能被结算两次
- 邻区和主区状态不一致
- 迁移时很难判断谁是真实结果
2. Shadow 不是服务端镜像的简单拷贝
客户端需要的是“适合渲染和交互的一份状态”,而不是把服务端对象逐字段原样照搬。
例如:
- 服务端位置可能以离散快照形式同步
- 客户端要做插值和预测
- 某些服务端内部字段根本不该暴露给客户端
所以 Shadow 通常会有自己的一层表示结构。
3. BaseApp 或其他服务是否一定要持有 Ghost
不一定,取决于架构。
如果某个服务只需要通过 RPC 查询当前权威状态,未必需要长期持有镜像;如果它需要高频读取、低延迟访问或参与复杂协作,镜像副本才更有价值。
关键不在“有没有 Ghost”,而在:
- 读取频率有多高
- 是否需要本地快速访问
- 是否能接受异步查询延迟
六、这套机制真正带来的收益
1. 控制写入来源
只有 Real 负责最终写入,系统更容易维持一致性。
2. 降低跨节点交互成本
邻区或其他进程不必每次都远程查询权威节点,可以先读本地 Ghost。
3. 支撑平滑迁移
分区切换前先预热 Ghost,迁移过程更稳。
4. 让客户端同步更可控
客户端只关心 Shadow 所需数据,不必直接承担服务端实体模型的复杂性。
七、常见问题
1. Ghost 越多越好吗
不是。Ghost 越多,意味着:
- 更多内存占用
- 更多同步链路
- 更多脏数据传播
- 更复杂的回收和故障处理
Ghost 的数量应该由 AOI、边界预热、服务依赖来约束,而不是无节制复制。
2. 所有实体都需要完整 Ghost 吗
也不需要。很多时候只需要“裁剪后的镜像”。
例如邻区只关心:
- 位置
- 朝向
- 阵营
- 生命周期
那就没必要同步整份背包、任务、脚本私有状态。
3. Shadow 一定要做预测吗
不一定。
- 本地玩家通常需要预测
- 远端玩家多用插值
- 怪物或低价值对象甚至只做简单平滑
预测是体验优化,不是必须给所有对象都上。
八、工程落地时更重要的点
如果要把 Ghost / Shadow 机制真正做稳,通常要同时回答这些问题:
- Real 和 Ghost 的字段边界怎么定义
- 同步是全量、增量还是脏字段
- Ghost 的生命周期由谁管理
- 实体迁移时如何做主从切换
- 客户端 Shadow 的校正阈值是多少
- AOI 收缩和扩张时如何创建、冻结、回收镜像
这些比单纯背术语更决定系统质量。
参考资料
- KBEngine 文档中的 Entity / Ghost / Shadow 设计说明
- BigWorld 架构资料中的 Real / Ghost 实体迁移机制
- Glenn Fiedler, Snapshot Interpolation
