4.5 预测模式推广:开火、交互与自定义预测系统

CMC 解决了「移动」这一块的预测。但游戏里还有大量「需要即时反馈、服务器又要权威」的操作:开火、开宝箱、放技能、上车。这些场景的预测思路是一模一样的四步。

预测的四步模板

① 本地执行:客户端立即执行操作(播动画/产生本地表现)
② 记录证据:保存「预测时的输入、时间戳、序列号」
③ 服务器裁决:服务器按权威逻辑计算结果
④ 对账校正:客户端比较预测结果与服务器结果,不一致则回滚重放

移动里的 ①②③④ 是 CMC 自动完成的。其他场景需要自己实现,但骨架相同。

例子:预测式开火

// 客户端:按下开火键
void UWeaponComponent::StartFire()
{
    if (GetOwnerRole() == ROLE_Authority) { Fire(); return; }
    // 客户端预测:
    PlayFireAnimation();                          // ① 立即播(手感)
    PredictedFireSeq++;                           // ② 记录序列号
    ServerFire(PredictedFireSeq, AimDir);         // ③ 发给服务器裁决
    SpawnLocalTracer(AimDir);                     // 弹道特效立即播
}

服务器:

void UWeaponComponent::ServerFire_Implementation(uint32 Seq, FVector Dir)
{
    if (!ConsumeAmmo()) { ClientFireResult(Seq, false); return; }
    const bool bHit = DoHitScan(Dir);             // 服务器权威判定
    ClientFireResult(Seq, true);                  // ④ 结果回客户端
}

客户端收到结果:

void UWeaponComponent::ClientFireResult_Implementation(uint32 Seq, bool bSuccess)
{
    if (Seq <= LastConfirmedSeq) return;          // 过时结果,忽略
    LastConfirmedSeq = Seq;
    if (!bSuccess) PlayDryFireSound();            // 预测错误:没子弹
}

注意:伤害数字不在预测范围内(必须服务器算),只有「开火这一下的动画和音效」做预测。

什么时候不该预测

预测有适用边界:

  • 结果有随机性 / 依赖服务器持久状态:开宝箱出什么、抽卡结果——必须等服务器。预测错了再改,玩家暴怒。
  • 操作会被别的玩家/世界状态否决:开门(门正在被别人开)、抢装备。预测成功但服务器拒绝 → 玩家看到「开了又关上」的抽搐。此时适合「短预测」——只播 0.1-0.2 秒的启动动画,结果仍由服务器驱动。
  • 纯表现高频(如持续激光渲染):预测即可,反正下一帧就校正。

对账频率与成本

每次预测都带一个 Seq → 服务器每次回结果 → 客户端对账。高频操作不要每帧对账:可以攒 N 帧一次,或只在「结果与预测不符」时才回。CMC 的做法可以参考:它不是每次都广播,而是有误差才校正。

失败检测与回滚的体感设计

预测失败时玩家会看到「我明显操作了但没生效」。缓解手段:

  • 校正动画:别直接瞬移/掐断动画,放一个小过渡(0.05s 的 blend)
  • 音效兜底:预测失败时播「被拒」音效(上膛空响),玩家心智上接受「是弹夹空了不是我卡了」
  • 结果延迟显示:伤害飘字永远以服务器值为准,不预测

一个反面案例

某项目的开门系统:客户端预测播放了开门动画 1.2 秒,结果 200ms 后服务器判定「你距离不够」(网络波动导致位置偏差)→ 门播了一半被服务器拉回。玩家看到门「抽搐」。修复:预测范围缩短到「手放上把手」的前 0.15 秒,门的开启动画完全由服务器的复制状态驱动。预测深度与「服务器能否可靠确认」成正比——越容易被世界状态否决的操作,预测得越浅。

心得:预测是个「度」的艺术。零预测=糊,全预测=抽搐。方法是在「操作 → 服务器确认」的平均往返时间窗口内做文章:只有「大概率会被确认」的部分才预测,不确定的部分留给服务器。CMC 能做到完美预测是因为移动的逻辑几乎只依赖玩家自己的输入——迁移到你的系统时,先问自己「这个操作的结果是不是几乎只由我自己的输入决定」。