Mapz's Blog

可以递归的函数指针

UE4学习:FPredictionKey

FPredictionKey 的基本信息

FPredictionKey 是 UE4 在 Gameplay Ability System 插件中提供的一个结构体

编写目的是为了在 GAS 系统中实现客户端预测从而不用等待服务器返回提前就提前做一些操作

达到提高游戏流畅性的目的

服务器返回通过或者拒绝后,客户端可以相应的 Confirm 或者在 Reject 后回滚此次操作所做的修改

除了在 GAS 本身中

你也可以在其他需要客户端预测的地方使用

例如动画的预测

但是需要依赖 GAS 中的 UAbilitySystemComponent

  • 一个 Predication Key 可以看做是一个行为在服务器和客户端的唯一标识
  • Predication Key 的 ID 是全局唯一的,参考:
1
2
3
4
5
6
7
8
9
10
11
void FPredictionKey::GenerateNewPredictionKey()
{
// GKey 作为静态局部变量,其值为全局唯一,每次都 ++
static KeyType GKey = 1;
Current = GKey++;
if (GKey < 0)
{
GKey = 1;
}
bIsStale = false;
}
  • 在服务器上创建的 PK 则只作为一个行为的标识,不作为客户端预测用

  • PK 可以通过 void FPredictionKey::GenerateDependentPredictionKey() 创建依赖上一个 PK 的 PK ,但是不能在服务器执行,因为没有意义(无需预测就无需确认和回滚)

  • PK 可以通过 bIsStale 设置为是否可以在一次确认或拒绝后继续使用

  • PK 可以通过 NewRejectedDelegate 和 NewCaughtUpDelegate 来创建确认或者拒绝的回调,即是服务器同意或者拒绝客户端预测的时候所调用的回调

  • PK 的网络序列化是自定义的

    • 第一位是 PK 是否在当前 Connection 可用,在序列化的时候设置,如果是服务器创建的,则都可用,否则只有 PK 的连接信息和当前连接一致的时候可用
      1
      ValidKeyForConnection = (PredictiveConnection == nullptr || (Map == PredictiveConnection) || bIsServerInitiated) && (Current > 0);
    • 第二位是是否有依赖的 PK (仅当当前 Connection 可用的时候可用)
    • 第三位是是否为服务器创建
    • 如果是可用连接,则写入当前的 PK ID
    • 如果有依赖的 PK 则写入依赖 PK ID
    • 如果是服务器创建的 PK 则设置当前 PK 的 Connection 为当前 Connection

从 Activate Ability 来看 PredicationKey 的使用

我们从 UAbilitySystemCompoment::TryActivateAbility 看起,这里仅讨论一种情况,就是客户端发起激活,服务器检查并确认的方式,略过其他方式的流程

  1. 先判断技能是否可以被释放(是否已经被 Give To Actor)
  2. 判断技能释放的 Actor 是否合法
  3. 模拟端不可以释放技能,return
  4. 检查技能是否是客户端远程释放的,并检查是否可以远程释放,如果可以释放则 创建新的 PK 并调用 CallServerTryActivateAbility 准备执行服务器 RPC ServerTryActivateAbility
  5. CallServerTryActivateAbility 中检查是否存在合批的 RPC 数据,如果合批的 RPC 数据未开始,则更新 PK 并开始,否则直接调用 服务器 RPC ServerTryActivateAbility ,并传入创建的 PK
  6. Server 调用 InternalServerTryActivateAbility
    a. Activate 失败的时候,调用客户端 RPC ,通知此 PK 调用失败,客户端调用此 PK 的拒绝委托,从而达到回滚等目的,并且执行 EndAbility
    b. Activate 成功的时候,调用客户端 RPC ClientActivateAbilitySucceed 通知客户端激活 Ability 成功
    c. 客户端和服务器在成功的时候,都会调用 CallActivateAbility 来启动 Activate Ability

这个里面,一个 PredicationKey 通过 RPC 在客户端和服务器之间传递并储存,并且通过 RPC 通知执行成功或者失败,在成功和失败的时候,执行通过 PK 对应起来的成功和失败的委托

Activate Ability 并没有直接用到回滚相关的内容,因为这里没有在客户端 Try Activate Ability 后做其他操作,而是等待服务器返回 Confirm 或者 Reject

我们在释放技能后,也可以会做过一些附加操作,这些操作可以在 Reject 委托中绑定自己的回滚操作

写其他一些功能的时候,也可以直接在客户端发送 RPC 后,直接开始处理自己的操作,等待服务器 Reject 后,用挂在 Reject 的委托上,用于回滚客户端操作,例如停止播放动画