AI状态:复制决策结果,不要让客户端重跑决策

自动关联目录:AI状态:复制决策结果,不要让客户端重跑决策

核心判断:DS 负责感知、选敌和状态迁移;客户端只消费带版本的决策结果并恢复表现。同步 Idle/Chasing/Attacking 等状态,不等于把行为树、脚本计时器和迁移条件也复制到客户端执行。

一、业务冲突:AI 必须权威,又要让客户端及时演出

PVE 敌人的仇恨、攻击目标、伤害和死亡必须由 DS 决定,否则不同客户端会得到不同战局。但客户端若只等动画命令,又很难处理中途加入、相关性重建、丢包和状态覆盖。需要复制的不是“播放一次追击动画”,而是足够重建当前表现的状态事实。

二、实际实现:状态骨架、脚本逻辑和两条同步路径并存

工程中的一组 C++ 状态类型只声明状态 ID,公共基类在 OnEnter/OnUpdate/OnExit 中转调脚本或蓝图事件。底层状态基类又把非 DS 的进入、更新和退出回调留给特效、动画等表现逻辑。这种分层让状态枚举稳定、玩法迭代灵活。

同步层同时使用可靠 Multicast 传递即时状态变化,并用 Push Model 保存每个状态机的最新状态,供晚加入或断线恢复;负载包含状态机 ID、状态 ID、服务器时间以及少量通用字段。这个“事件加快照”的方向正确,但通用组件还保留 Autonomous Proxy 向 DS 上报状态的入口,服务端收到后可强制进入状态。对于服务器拥有的 AI,这条路径不应存在。

sequenceDiagram
    participant B as DS Brain
    participant S as DS State Machine
    participant R as Replicated Snapshot
    participant C as Client Presentation
    B->>S: RequestTransition(reason, target)
    S->>S: Validate + enter + revision++
    S->>R: StateId, Revision, EnterTime, ExecutionId
    R-->>C: OnRep / relevancy rebuild
    C->>C: Seek animation from server time
    Note over C: 不运行感知、选敌和迁移条件

三、值得保留:事件负责及时,快照负责恢复

只发 Multicast 无法服务晚加入,只发属性又可能让一次性表现显得迟钝。即时事件与最新快照并存,且客户端状态回调和 DS 逻辑回调分开,是值得保留的设计。状态组件关闭自身 Tick、由统一生命周期驱动,也比每个组件各自注册 Tick 更容易做预算。

四、结构性缺陷:状态 ID 有了,迁移所有权仍不清楚

脚本状态每帧更新,行为树也可能判断迁移,Ability 还拥有自己的完成与取消时机,容易形成三个时钟。通用字符串、浮点和整数负载缺少状态级 Schema;服务器毫秒时间若存入 float,运行时间增长后精度也会下降。最危险的是复用通用“客户端请求状态”链路:若 NPC 入口没有强制 HasAuthority,新状态可能绕过感知、冷却或攻击条件。

五、目标设计:DS 状态提交必须是单一事务

Brain 只能提交“意图”,状态机统一验证迁移图、目标代际、打断优先级和当前执行实例;成功后生成单调 Revision。客户端收到旧 Revision 直接丢弃,只根据 EnterServerTime 恢复动画进度。攻击伤害等权威结果由 Ability/战斗服务产生,状态只引用其 ExecutionId。

StateReceipt CommitAIState(AIHandle ai, TransitionIntent intent)
{
    if (!HasAuthority() || !ai.IncarnationMatches(intent.incarnation)) return Reject();
    if (!Graph.CanEnter(ai.state, intent.next, intent.reason)) return Reject();
    if (!Executions.IsCurrent(intent.executionId)) return Reject();

    CancelOwnedWork(ai.stateRevision);
    ai.state = intent.next;
    ai.stateRevision++;
    ai.enterServerTime = ServerClock::Now();
    PublishSnapshot(ai.PresentationState());
    return Accept(ai.stateRevision);
}

六、决策规则:复制最小可恢复状态

内容是否复制原因
行为树节点、感知列表否DS 决策内部状态,量大且会造成双重决策
状态 ID、Revision、进入时间是支持覆盖、Seek、晚加入和重连
当前目标稳定 ID 与代际按相关性客户端表现可能需要朝向、锁定和 UI
攻击 ExecutionId是把状态表现与权威 Ability 实例关联
脚本临时表、TimerHandle否不能跨端恢复,也不应成为协议
伤害、Buff、死亡结果是由权威战斗系统复制,而非客户端状态推导

这套实践的价值在于已经区分 DS 与客户端回调,并同时考虑即时通知和恢复;下一步必须收紧迁移入口,让“谁能切状态、谁取消旧任务、谁产生战斗结果”各自只有一个答案。