虚幻插件GAS分析02-1 技能的网络同步之预测与结束
https://zhuanlan.zhihu.com/p/419741087
大家好,我是阿棍儿。上一篇如下,本篇总结一下GAS激活技能的预测机制与结束技能的网络同步流程。
技术宅阿棍儿:虚幻插件GAS分析02-0 技能网络同步之激活22 赞同 · 6 评论文章编辑
什么是预测?
代码中常出现Predict字样的东西,带这个字样基本会跟预测有关。
GAS中的技能激活预测(仅LocalPredicted策略支持)大致步骤是:
- Client可以先激活技能,同时请求Server激活技能
- Server会判断一下激活技能合不合理,如果合理,Server也激活,否则Server就不激活
- Server会把判断结果回复给Client
- Client根据Server的判断结果处理,如果不合理,就结束技能
也就是说,预测的通用概念是,Client先做事,同时请Server也做,Server决定做不做,并执行决定,同时Server把结果反馈给Client,Client根据Server的决定修正自己,以求与Server保持一致。
修正针对的是副作用(side effect),在Server反馈到达Client后,副作用应该被移除。本文只考虑技能激活,所以移除副作用的方法就是立即结束技能。
激活技能的预测机制的实现
预测机制的基础数据结构主要在Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayPrediction.h中定义,里面还有大量注释,是非常重要的参考。
为了尽量画到一张图,有些扭曲请见谅
预测Key
Server和Client的执行是跨越时空的,被发起和被同步发起的技能必须有匹配的方法,才能正确地修正副作用。例如,两端都在连续释放相同的攻击技能,在时间上,两端的技能可能是错位的,Client可能在打第5下,Server可能在打第1下,如果Server要拒绝第1下的执行,该怎么找到Client的第1个技能实例(假设实例化策略是Per Execution)呢?
预测Key的一个功能就是可以给两端对应的技能实例发相同的Unique ID。Server连同此ID一同反馈,Client就知道是哪个技能实例被拒绝了。
Key的创建
Client的激活过程
上图中的流程大致就是创建Key,传递Key,激活技能。
具体代码流程如下,FScopedPredictionWindow是一个“窗口”,在构造时窗口打开,同时生成新的预测Key,在析构时窗口关闭。在此期间,FPredictionKey UAbilitySystemComponent::ScopedPredictionKey是有效Key,可以用来预测。如上图,生成的Key连同激活请求会一起传到Server。
#Client UAbilitySystemComponent
TryActivateAbilityByClass->TryActivateAbility->InternalTryActivateAbility
{
{ // 用花括号建立局部作用域
FScopedPredictionWindow ScopedPrediction(this, true);
CallServerTryActivateAbility->ServerTryActivateAbility->#Server ...
UGameplayAbility::CallActivateAbility
} // ScopedPrediction析构
}
技能预测失败
Server反馈Fail的过程
如上图,当Server因某种原因拒绝激活技能时,就会通知Client激活失败,同时将对应的预测Key传给Client,Client根据Key找到对应的技能并结束之。
/*----------- RPC Client->Server---------*/
#Server UAbilitySystemComponent
ServerTryActivateAbility_Implementation->InternalServerTryActivateAbility
->ClientActivateAbilityFailed
/*----------- RPC Server->Client---------*/
#Client ClientActivateAbilityFailed_Implementation
UGameplayAbility::K2_EndAbility
结束技能的网络同步
CancelAbility VS EndAbility
先辨析一下这2个概念。
两者都可以结束技能,有什么区别呢?代码中注释如下:
// CancelAbility() - Interrupts the ability (from an outside source).
//
// EndAbility() - The ability has ended. This is intended to be called by the ability to end itself.
简单说就是Cancel倾向在技能外使用,EndAbility倾向在技能内使用。在实际功能上,Cancel会调用CanBeCancel来确定是否能被Cancel,所以调用Cancel有机会阻止技能结束。
两者在流程上差不多,最终都是调用底层的EndAbility,所以为了简化问题,只分析EndAbility。流程分析的起点是UGameplayAbility::K2_EndAbility(EndAbility蓝图节点),终点是UGameplayAbility::EndAbility。
EndAbility流程
技能结束的示意图看起来相对简单一些,不过,在简化掉目前没涉及到的概念的基础上,才能画得这么简单。
- 代码流程
Server:
UGameplayAbility::K2_EndAbility
EndAbility
UAbilitySystemComponent::ReplicateEndOrCancelAbility
if (LocalPredicted 或 ServerInitiated)
{
if (!Controlled)
{
ClientEndAbility
/*----------- RPC Server->Client---------*/
#Client ClientEndAbility_Implementation
RemoteEndOrCancelAbility
UGameplayAbility::EndAbility
}
}
Client:
UGameplayAbility::K2_EndAbility
EndAbility
UAbilitySystemComponent::ReplicateEndOrCancelAbility
if (LocalPredicted 或 ServerInitiated)
{
if (!Authority)
{
CallServerEndAbility
ServerEndAbility
/*----------- RPC Client->Server---------*/
#Server ServerEndAbility_Implementation
RemoteEndOrCancelAbility
UGameplayAbility::EndAbility
}
}