UE4 GameplayAbilitySystem Prediction

本文深入探讨了UE4 GameplayAbilitySystem的预测机制——GameplayPrediction。解释了PredictionKey的概念及其实现方式,介绍了不同技能实例化策略和网络执行策略,并详细分析了Ability激活预测行为和GameplayEffect预测流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UE4 GameplayAbilitySystem Prediction

https://zhuanlan.zhihu.com/p/143637846

UE4 GamplayAbilitySystem(简称GAS)处理了很多网络优化问题。其中本文介绍的GamplayAbilitySystem背后成熟的预测机制---Gameplay Prediction实现了各种游戏效果预逻辑。什么是Gameplay Prediction,简单来说客户端和服务器保持一定的帧率运行各自的逻辑,客户端先执行各种Gameplay逻辑,然后再透过服务端同步过来的状态去矫正客户端(服务端是权威)。比如客户端准备去执行某一个技能时候,不需要等待服务端验证的结果后告诉我要不要去执行,而是先“尝试”执行,此时服务端也在执行这个技能逻辑,如果服务端执行失败/成功后再反馈到客户端,如若失败了可将技能给终止。当然,这只是基本的预测思路,GamplayAbilitySystem系统背后还处理掉了非常多的网路预测逻辑。

一、PredictionKey介绍

这个系统的一个基本概念是Prediction Key(FPredictionKey)。预测密钥本质是一个唯一的ID,它只是在客户端端生成(一般来说是主控端),参考FScopedPredictionWindow预测窗口:

客户端生成PredictionKey

客户端每生成一个Key时候在一个16位的全局整形变量加一。生成Key之后客户端可以绑定预测操作或副作用。引擎提供了NewRejectOrCaughtUpDelegate等关联方法,在接下来要说的Ability激活预测、Montage播放等地方都大量用到了副作用绑定,作用是为了做“回滚”操作,如果预测结果失败,客户端通过这个Key找到关联的副作用。

接下来客户端端会把这个预测密钥发送(RPC)到服务器上, 服务器会通过过这个Key响应预测密钥。比如服务端操作失败,需要告诉客户端去回滚操作,就是通过这个Key告诉客户端,然后客户端找到这个Key关联的预操作/副作用并执行,其中Play Montage的预测就是通过这种方式来进行“回滚”的。

同时服务端也能向发送预测密钥到服务器的客户端同步PredictionKey(见下图)

ReplicatedPredictionKeyMap

其中就是通过FReplicatedPredictionKeyMap一个Repliction变量来实现,当服务端的预测窗口销毁后,会往把客户端发过来的PredictionKey存储到这个Map上,然后同步回客户端并执行NewCaughtUpDelegate关联的绑定。

CaughtUp

2.1、GAS的Ability activation预测行为

在说激活Ability预测之前,先了解Ability以下几个参数配置。

Ability 配置

InstancingPolicy

技能执行是后,通常会有一个生成技能的新对象,用于定位追踪该技能。但是大多数时候,技能需要频繁的创建使用,可能需要会出现快速实例技能对象对性能产生一定的性能影响。AbilitySystem提供给了三种实例化技能的策略:

  • 按执行实例化:(Instanced per Execution)这就是前面提到的每执行一个技能时候都会实例化一个技能对象,但是如果技能被频繁的调用时候,该技能就会有一定的运行开销。但是优点在于由于这个技能重新运行时候会重新生成一个实例对象,因而该技能中的变量也会初始化,结束技能时候不必考虑重置变量、状态等问题。如果你的技能不是很频繁使用的化可以考虑使用这种的执行策略。
  • 按Actor实例化:(Instanced per Actor)当技能首次被执行后,后面每次执行这个技能时候都不会被实例化,会重复使用这一个对象。这个和上面的Instanced per Execution策略相反,每次执行这个技能后需要清理这个技能的变量和状态工作。这种策略适用于频繁使用某个技能使用使用,可能提要执行效率。并且因为技能具有可处理变量和RPC的复制对象,而不是浪费网络带宽和CPU时间,在每次运行时产生新对象。
  • 非实例化:(Non-Instanced)故名思意,该策略的技能被执行时候不会实例化技能对象,而是技能的CDO对象。这种策略是优越于上面的两种策略的,但是也有一个很大限制---这个策略要求这个技能完全都是由C++编写的,因为蓝图创建图标时候需要对象实例,并且即使是C++编写这个技能,该技能也不能更改其成员变量、不能绑定代理、不能复制变量、不能RPC。因此这个技能在游戏中应用的相对较少,但是一些简单的AI技能可以用到。

NetExecutionPolicy

  1. 本地预测:(Local Predicted)本地预测是指技能将立即在客户端执行,执行过程中不会询问服务端的正确与否,但是同时服务端也起着决定性的作用,如果服务端的技能执行失败了,客户端的技能也会随之停止并“回滚”。如果服务端和客户端执行的结果不矛盾,客户端会执行的非常流畅无延时。如果技能需要实时响应,可以考虑用这种预测模式。下文会着重介绍这个策略。
  2. 仅限本地:(Local Only)仅在本地客户端运行的技能。这个策略不适用于专用服务器游戏,本地客户端执行了该技能,服务端任然没有被执行,在有些情况下可能出现拉扯情况,原因在于服务端技能没有被执行,技能中的某个状态和客户端不一致。
  3. 服务器启动:(Server Initiated)技能将在服务器上启动并PRC到客户端也执行。可以更准确地复制服务器上实际发生的情况,但是客户端会因缺少本地预测而发生短暂延迟。这种延迟相对来说是比较低的,但是相对于Local Predicted来说还是会牺牲一点流畅性。之前我遇到一种情况,一个没有预测的技能A,和一个带有预测的技能B,A执行了后B不能执行, 现在同时执行技能A和技能B, 由于A需要等待服务器端做验证,B是本地预测技能所以B的客户端会瞬间执行,导致B的客户端被执行过了,服务端失败,出现了一些不可预料的问题了,这种情况需要将技能B的网络策略修改为Server Initiated,这样就会以服务端为权威运行,虽然会牺牲点延迟,但是也是可以在游戏接收的范围内,有时候需要在此权衡。
  4. 仅限服务器:(Server Only)技能将在只在服务器上运行,客户端不会。服务端的技能被执行后,技能修改的任何变量都将被复制,并会将状态传递给客户端。缺点是比较服务端的每个影响都会由延迟同步到客户端端,Server Initiated只在运行时候会有一点滞后。

下图是执行策略为Local Predicated的Ability Activation流程图

主要的工作流水在UAbilitySystemComponent::InternalTryActivateAbility函数上,在这个过程中将不能销毁Ability,需要等这个流程走完再执行Ability的销毁。首先执行的是CanActivateAbility,主要处理Ability当前是否在CD中、是否有足够的花费(比如能量不够了?),以及是否是被其他Ability Block,比如其他技能在运行状态下,该技能不能使用,可以通过Tag配置,同时蓝图可以重写K2_CanActivateAbility函数来判断是否需要执行该技能。

接下来创建预测窗口FScopedPredictionWindow,用于生成本文的主角PredictionKey,生成Key后将Key和Handle(用于表示Ability的句柄)RPC通知服务端激活技能,并关联捕抓事件,当服务端执行完Ability后网络复制FReplicatedPredictionKeyMap回客户端后该事件将被执行。也就是服务端创建预测窗口FScopedPredictionWindow析构时候执行,注意服务端创建FScopedPredictionWindow不是创建RedictionKey,而是存储这个RedictionKey,之后为了和客户端的RedictionKey进行配对。

通知服务端后接着做自己的预表现,客户端开始CallActivateAbility。

同时服务端也在执行Abilitym,最终也会调用到UAbilitySystemComponent::InternalTryActivateAbility函数上,但有些分支和客户端不尽相同。服务端通过InternalTryActivateAbility来确定是否执行成功。如果失败了将执行Reject,并终止预测失败的客户端Ability。

 

2.2 GameplayEffect预测

下图是执行ApplyGameplayEffectToSelf的流程图:

ApplyGameplayEffectToSelf流程图

GameplayEffect需要UAbilitySystemComponent::ScopedPredictionKey是一个有效的预测值时候才能进行预测否则客户端将无法进行GameplayEffect预测。如果预测了GameplayEffectGameplayEffect,那么其携带的属性、GameplayCues和GameplayTags也都会被随之预测。服务端在执行GameplayEffect时候需要获取此时客户端的最新预测秘钥。客户端和服务端都执行Effect之后将创建一个FActiveGameplayEffect,它将存储PredictionKey,这个对象作为一个数组元素存储到 UAbilitySystemComponent::ActiveGameplayEffects,ActiveGameplayEffects是一个服务端往多个客户端同步的容器。

ActiveGameplayEffects

当服务端的Effect被执行后,ActiveGameplayEffects的修改将会被同步,此时客户端会同步来自服务端的ActiveGameplayEffects

FActiveGameplayEffect

以服务端执行一个Effect为例,服务端创建了带有FActiveGameplayEffect的对象后将把这个对象同步到客户端,此时客户端(准确来说是主控端)因为此前有做预表现,也同样创建了相同预测秘钥的的FActiveGameplayEffect,如果有匹配(见下图),则将忽略表现上的处理,如GameplayCue,其余将正常执行。

PostReplicatedAdd

此时会发现,主控端里FActiveGameplayEffectsContainer会有两个相同的FActiveGameplayEffect,原因就是刚刚提到的一个是预表现创建的另外一个是服务端同步下来的。但是两个相同的FActiveGameplayEffect同时存在的时间是很短暂的,当服务端的GameplayEffect执行完成后将预测窗口也将销毁。

FScopedPredictionWindow

此时客户端将同步服务端过来的PredictionKey,并执行预先绑定了关联的副作用。

// Once replicated state has caught up to this prediction key, we must remove this gameplay effect.
		InPredictionKey.NewRejectOrCaughtUpDelegate(FPredictionKeyEvent::CreateUObject(Owner, &UAbilitySystemComponent::RemoveActiveGameplayEffect_NoReturn, AppliedActiveGE->Handle, -1));

对改预测的GameplayEffect进行删除。但是这种情况下的Effect删除将会再次检查PredictionKey并确实是否执行GameplayCue移除逻辑。这就是为什么一个瞬间执行的Effect也需要将将其是一个持续执行的Effect。

预测Effect的移除栈

这套GameplayAbilitySystem中,伤害的预测、Effect的移除、周期性执行的GameplayEffect在没有做相关的预测。Effect的移除的话可以通过另外一个Effect来移除需要的移除的Effect,然后两个Effect都进行删除。伤害的预测并不是很推荐进行预测,比如如果预测结果是觉得死亡,然而可能这个预测是失败的,将进行回滚角色从死亡状态回到健康的状态。

 

 

 

 

编辑于 2020-06-06

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值