Fortnite通过使用自定义Bookkeeping而不是Cooldown GE的方法避免了该问题. Meta Attribute对于在”我们应该造成多少伤害?”和”我们该如何处理伤害值?”这种问题之中的伤害值和治疗值做了很好的解构, 这种解构意味着GameplayEffect和ExecutionCalculation无需了解目标是如何处理伤害值的. 如果你想Reticle保留在最后一个有效Target上, 就需要自定义TargetActor来记住最后一个有效Target并使Reticle保留在其上. 我称之为持久化Target, 因为其会持续存在直到TargetActor接收到确认或取消消息, TargetActor会在它的射线/Overlap中找到一个新的有效Target, 或者Target不再有效(已销毁). GASShooter对火箭筒二技能的制导火箭定位使用了持久化Target.
- GASShooter是该样例项目的姐妹项目, 其演示了基于多人FPS/TPS的高阶GAS技术.
- 简单来说,此系统可帮助你在任何现代RPG或MOBA游戏中设计、实现及高效关联各种游戏中的技能,既包括跳跃等简单技能,也包括你喜欢的角色的复杂技能集。
- 如果你的键盘没有小键盘, 比如笔记本, 那么你可以在项目设置(Project Settings)里修改键盘绑定.
- 样例项目包含了一个GameplayCueNotify_Actor用于眩晕和奔跑效果.
- 服务端同步的Cooldown GE将会存于客户端上, 并且任何对其绕过的尝试(例如使用Minimal同步模式)都会被服务端拒绝.
- 设置该选项就会一直向服务端同步输入的按下(Press)和抬起(Release)事件.
- 如果你错误预测地应用了伤害, 那么玩家将会看到敌人的生命值会跳动, 如果你尝试预测死亡, 那么这将会是特别尴尬和令人沮丧的, 假设你错误预测了某个Character的死亡, 那么它就会开启布娃娃模拟, 只有当服务端纠正后才会停止布娃娃模拟并继续向你射击.
对于只能用一次输入激活的GameplayAbility(它们总是像MOBA游戏一样存在于相同的”槽”中), 我倾向在UGameplayAbility子类中添加一个变量, 这样我就可以定义他们的输入, 之后在授予Ability的时候可以从ClassDefaultObject中读取这个值. 为了绑定输入到ASC, 你必须首先创建一个枚举来将输入事件名称转换为byte, 枚举名必须准确匹配项目设置中用于输入事件的名称, DisplayName就无所谓了. 在这种情况下, 你想要读取或者修改AttributeSet的值, 就需要调用ASC实例中的函数, 而不是AttributeSet中定义的宏. 这是一个虚幻引擎的bug, 当使用从一个存在的蓝图Actor类复制的方式来创建新的类, 这会让这个类中将AttributeSet指针设置为空指针. Reticle默认是不可同步的, 但是如果在你的游戏中向其他玩家展示本地玩家正在定位的目标是有意义的, 由其2025 那么它也可以被设计成可同步的. 来自Epic的Dave Ratti已经表达过对其修复的兴趣, 包括预测冷却时间时的延迟问题和高延迟玩家对低延迟玩家的劣势.
由其: 优化过程
其次, 该公式还有一些对于可以使用哪些值而未说明的的规则, 因为这是考虑Paragon的情况而设计的. 重命名GameplayTag会创建重定向, 因此仍引用原来GameplayTag的资源会重定向到新的GameplayTag. 由其2025 如果可以的话, 我更倾向于创建新的GameplayTag, 手动更新所有引用到新的GameplayTag, 之后删除旧的GameplayTag以避免创建新的重定向.
敌人身上的红色标识就是Reticle, 相似的白色图像是火箭筒的准星. 我们确实想要GameplayCueManager扫描并找到所有的GameplayCueNotify, 然而, 我们不想要它异步加载每一个, 因为这会将每个GameplayCueNotify和它们所引用的音效和粒子特效放入内存而不管它们是否在关卡中使用. 在像Paragon这样的大型游戏中, 内存中会放入数百兆的无用资源并造成卡顿和启动时无响应. 查看UAbilitySystemGlobals中用于填充GameplayCueParameters结构体的三个函数以获得更多信息. 设计师可以决定一个GameplayEffect能够授予哪些Ability, 授予的Ability等级, 将其绑定在什么输入键上以及该Ability的移除策略.
由其: 10 预测(Prediction)
有几种方法可以实现带有Attribute(武器弹药, 盔甲耐久等等)的可装备物品, 所有这些方法都直接在物品中存储数据, 这对于在生命周期中可以被多个玩家装备的物品来说是必须的. 作为选择, 你可以使用多个AttributeSet来表示按需添加到Actor的Attribute分组, 例如, 你可以有一个生命相关的AttributeSet, 一个魔法相关的AttributeSet等等. 在MOBA游戏中, 英雄可能需要魔法, 但是小兵并不需要, 因此英雄就需要魔法AttributeSet而小兵则不需要. Attribute是由FGameplayAttributeData结构体定义的浮点值, 其可以表示从角色生命值到角色等级再到一瓶药水的剂量的任何事物, 如果某项数值是属于某个Actor且游戏相关的, 你就应该考虑使用Attribute. Attribute一般应该只能由GameplayEffect修改, 这样ASC才能预测(Predict)其改变. GameplayAbility无论是由蓝图还是C++创建都没关系.
这里我们使用蓝图和C++混合创建, 意在展示每种方式的使用方法. AI控制的小兵没有预先定义的GameplayAbility. 对于GameplayAbility的命名, 我使用_BP后缀表示由蓝图创建的GameplayAbility逻辑, 没有后缀则表示由C++创建. 为了增加设计师友好的迭代次数, 特别是在为UI设计UMG Widget时, 可以创建蓝图AsyncTask(在C++中)以直接在UMG蓝图图表中绑定到ASC中常见的修改委托, 唯一的告诫就是其必须手动销毁(比如在widget销毁时), 否则就会永远存于内存中.样例项目包含三个蓝图AsyncTask.
由其: 优化
一般在眩晕状态时, 我们就要取消Character所有已激活的GameplayAbility, 阻止新的GameplayAbility激活, 并在整个眩晕期间阻止移动. 样例项目的陨石GameplayAbility会在击中的目标上应用眩晕效果. 该方法也可以在你的GameplayCue需要特别指定的参数时使用, 这些需要特别指定的参数不能由GameplayCueParameter提供, 并且你不想将它们添加到EffectContext, 像伤害数值, 暴击标识, 破盾标识, 处决标识等等. GameplayAbility只能在一帧中执行, 这本身并不能提供太多灵活性, 为了实现随时间推移而触发或响应一段时间后触发的委托操作, 我们需要使用AbilityTask. 初学者经常会问”我怎样才能获取激活的Ability?”, 也许是用来设置变量或取消它.
多个GameplayAbility可以在同一时刻激活, 因此没有”一个激活的Ability”, 相反, 你必须搜索ASC的ActivatableAbility列表(ASC拥有的已授予GameplayAbility)并找到一个与你正在寻找的资源或授予的GameplayTag相匹配的Ability. Epic希望在未来的GAS迭代版本中实现真正的冷却预测(玩家可以激活一个在客户端冷却完成但服务端仍处于冷却过程的GameplayAbility). 如果每个子组件都需要很多Attribute且子组件的数量可以是无限的, 或者子组件可以分离被其他玩家使用(比如武器), 或者出于其他原因上述方法不适用于你, 那么我建议就不要使用Attribute, 而是在组件中保存普通的浮点数. 尽管Meta Attribute是一个很好的设计模式, 但其并不是强制使用的. 样例项目和文档目前基于Unreal Engine 4.26. 该文档拥有可用于旧版本Unreal Engine的分支, 但是它们不再受支持, 并且可能存在bug和过时信息.
由其: 4 生命偷取(Lifesteal)
假设你有一个可以发射8枚弹丸的霰弹枪, 就会有8个轨迹和碰撞GameplayCue. GASShooter采用将它们联合成一个RPC的延迟(Lazy)方法, 其将所有的轨迹信息保存到EffectContext作为TargetData. 尽管其将RPC数量从8降为1, 然而还是在这一个RPC中通过网络发送大量数据(~500 bytes). 一个进一步优化的方法是使用一个自定义结构体发送RPC, 在该自定义RPC中你需要高效编码命中位置(Hit Location)或者给一个随机种子以在接收端重现/近似计算碰撞位置, 客户端之后需要解包该自定义结构体并重现客户端执行的GameplayCue.
由其: 样例项目
它也可以什么都不显示, 例如, GASShooter中的枪支命中判断, 要对目标的射线检测显示些什么就是没有意义的. GAS带有开箱即用的客户端预测功能, 然而, 它不能预测所有. GAS中客户端预测的意思是客户端无需等待服务端的许可而激活GameplayAbility和应用GameplayEffect. 它可以”预测”许可其可以这样做的服务端和其应用GameplayEffect的目标. 服务端在客户端激活之后运行GameplayAbility(网络延迟)并告知客户端它的预测是否正确, 由其2025 如果客户端的预测出错, 那么它就会”回滚”其”错误预测”的修改以匹配服务端.
由其: 模型简介
设置该选项就会一直向服务端同步输入的按下(Press)和抬起(Release)事件. Epic不建议使用该选项而是依靠创建在已有输入相关的AbilityTask中的Generic Replicated Event(如果你的输入绑定在ASC). 在实际游戏中导致的结果就是高延迟的玩家相比低延迟的玩家对冷却时间短的技能有更低的触发率, 从而处于劣势, Fortnite通过使其武器使用无需冷却GameplayEffect的自定义Bookkeeping而避免该现象. 样例项目通过在客户端预测的冷却开始时灰化陨石技能的图标, 之后在服务端校正的Cooldown GE到来时启动冷却计时器处理该问题. REPTNOTIFY_Always用于设置OnRep函数在客户端值已经与服务端同步的值相同的情况下触发(因为有预测), 默认设置下, 客户端值与服务端同步的值相同时, OnRep函数是不会触发的.
由其: 8 游戏暂停时生成TargetData
GameplayEffectContainer提供了一个可选的产生TargetData的高效方法. 因为我们不能预测GameplayEffect的移除, 所以就不能完全预测GameplayAbility的冷却时间, 并且也没有相反的GameplayEffect这种变通方案可供使用. 服务端同步的Cooldown GE将会存于客户端上, 由其 并且任何对其绕过的尝试(例如使用Minimal同步模式)都会被服务端拒绝. 这意味着高延迟的客户端会花费较长事件来告知服务端开始冷却和接收到服务端Cooldown GE的移除. 这意味着高延迟的玩家会比低延迟的玩家有更低的触发率, 从而劣势于低延迟玩家.
由其: 「尤其」或「由其」
GAS对此没有提供开箱即用的功能(SpawnActor AbilityTask只在服务端生成Actor). 每次GameplayCue触发都是一次不可靠的多播(NetMulticast)RPC. 在同一时刻触发多个GameplayCue的情况下, 有一些优化方法来将它们压缩成一个RPC或者通过发送更少的数据来节省带宽.
由其: 特别推荐
AttributeSet可以在运行时从ASC上添加和移除, 然而移除AttributeSet是很危险的, 例如, 如果某个AttributeSet在客户端上移除早于服务端, 而某个Attribute的变化又同步到了客户端, 那么Attribute就会因为找不到AttributeSet而使游戏崩溃. 有种方案是设置一个单一且巨大的AttributeSet, 共享于游戏中的所有Actor, 并且只使用需要的Attribute, 忽略不用的Attribute. 我使用一个简单的多人游戏模板项目来阐述对Unreal Engine 4中GameplayAbilitySystem(GAS)插件的理解. 这不是官方文档并且这个项目和我自己都不来自Epic Games.
由其: 特别推荐
GASShooter实现了一个按钮交互系统, 玩家可以按下或按住’E’键来和可交互对象交互, 像复活玩家, 打开武器箱, 打开或关闭滑动门. GA处理响应左Shift输入, 告知CMC开始和停止奔跑, 并且在左Shift按下时预测性地消耗耐力. GASShooter对火箭筒二技能制导火箭锁定的目标使用了Reticle.
Epic最近发起了一项倡议, 将使用新的网络预测插件替换CharacterMovementComponent, 该插件仍处于起步阶段, 但是在Unreal Engine Github上已经可以访问了, 现在说未来哪个引擎版本将首次搭载其试验版还为时尚早. 如果某个GameplayCue是客户端添加的, 那么它也应该自客户端移除. GameplayAbility的网络安全策略决定了Ability应该在网络的何处执行. CAR还有很多高阶功能, 像检查GameplayEffect实例是否已经位于目标上, 修改当前实例的持续时间而不是应用一个新实例(对于CanApplyGameplayEffect()返回false).
由其: 样例项目
多个GameplayAbility可以在同一时刻激活, 例如奔跑和射击. 例如, 在样例项目中, 我们在这里从生命值Attribute中减去了最终的伤害值Meta Attribute, 如果有护盾值Attribute的话, 我们也会在减除生命值之前从护盾值中减除伤害值. 样例项目也在这里应用了被击打反应动画, 显示浮动的伤害数值和为击杀者分配经验值和赏金. 通过设计, 伤害值Meta Attribute总是会传递给即刻(Instant)GameplayEffect而不是Attribute Setter. 在每个物品上都创建一个AbilitySystemComponent是种很极端的方案.
由其: 模型简介
如果你想TargetActor记住最后有效的Target, 就需要添加这项功能到一个自定义的TargetActor类. 样例项目包含了一个GameplayCueNotify_Actor用于眩晕和奔跑效果. 由其2025 其还含有一个GameplayCueNotify_Static用于枪支弹药伤害. 这些GC可以通过客户端触发来进行优化, 由其 而不是通过GE同步.
GASShooter是该样例项目的姐妹项目, 其演示了基于多人FPS/TPS的高阶GAS技术. 如果你需要同时发送很多GameplayCue, 由其 可以考虑将其批处理成一个RPC, 目标就是减少RPC数量(GameplayCue是不可靠的网络多播(NetMulticast))并以尽可能少的数据量发送. GameplayAbility在一帧中的的激活、选择性地向服务器发送TargetData、结束可以被批处理而将2-3个RPC压缩成一个RPC.
TargetActor是基于AActor的, 因此它可以使用任意种类的可视组件来表示其在何处和如何定位的, 例如静态网格物(Static Mesh)和贴花(Decal). 静态网格物(Static Mesh)可以用来可视化角色将要建造的物体, 贴花(Decal)可以用来表现地面上的效果区域. 样例项目使用带有地面贴花的AGameplayAbilityTargetActor_GroundTrace表示陨石技能的伤害区域效果.
GAS包含了大量不同详细级别的日志生成语句, 由其 你很可能见到的是ABILITY_LOG()这样的语句. 默认的详细级别是Display, 更高的默认不会显示在控制台里. 第三页显示了所有授予到你的GameplayAbility, 无论其是否正在运行, 无论其是否被阻止激活, 由其2025 和当前正在运行的AbilityTask的状态. 有两个GameplayCueNotify类, Static和Actor. 它们各自响应不同的事件, 并且不同的GameplayEffect类型可以触发它们.
在启动时异步加载每个GameplayCue的一种可选方法是只异步加载那些会在游戏中触发的GameplayCue, 这会在异步加载每个GameplayCue时减少不必要的内存占用和潜在的游戏无响应几率, 从而避免特定GameplayCue在游戏中第一次触发时可能出现的延迟效果. SSD不存在这种潜在的延迟, 我还没有在HDD上测试过, 如果在UE编辑器中使用该选项并且编辑器需要编译粒子系统的话, 就可能会在GameplayCue首次加载时有轻微的卡顿或无响应, 这在构建版本中不是问题, 因为粒子系统肯定是编译好的. 使用默认的TargetActor类时, Actor只有直接在射线/Overlap中时才是有效的, 如果它离开射线/Overlap(它移动开或你的视线转向别处), 就不再有效了.
由其: 10 预测(Prediction)
Gameplay技能系统 是一个高度灵活的框架,可用于构建你可能会在RPG或MOBA游戏中看到的技能和属性类型。 你可以构建可供游戏中的角色使用的动作或被动技能,使这些动作导致各种属性累积或损耗的状态效果,实现约束这些动作使用的”冷却”计时器或资源消耗,更改技能等级及每个技能等级的技能效果,激活粒子或音效,等等。 简单来说,此系统可帮助你在任何现代RPG或MOBA游戏中设计、实现及高效关联各种游戏中的技能,既包括跳跃等简单技能,也包括你喜欢的角色的复杂技能集。 GAS向Gameplay Debugger中添加了功能, 使用反引号(`)键以访问Gameplay Debugger. 按下小键盘的3键以启用Ability分类, 取决于你所拥有的插件, 分类可能是不同的. 如果你的键盘没有小键盘, 比如笔记本, 那么你可以在项目设置(Project Settings)里修改键盘绑定.
它的意思是如果客户端的GameplayAbility由于玩家取消或者自然完成时, 就会强制它的服务端版本结束而不管其是否完成. 最重要的是之后的问题, 特别是对于高延迟玩家所使用的客户端预测的GameplayAbility. 默认情况下, ASC处于Full Replication模式, 这会同步所有的GameplayEffect到每个客户端(对单人游戏来说很好). 无论是什么同步模式, GameplayTag仍会进行同步, GameplayCue仍会以不可靠的网络多播(NetMulticast)到所有客户端. 当所有客户端都不需要查看这些数据时, 这会减少从GE同步的网络数据.
由其: 10 预测(Prediction)
样例项目会在角色AttributeSet中的值受到致命一击时创建该GameplayEffect来将金币和经验点数返还给击杀者. 运行时创建的即刻(Instant)GameplayEffect也可以在客户端预测的GameplayAbility中调用. 一个普遍用法是当想要强制另一个玩家做某些事的时候, 像击退或拉取时移动他们, 由其 就会对它们应用一个GameplayEffect来授予其一个自动激活的Ability(查看被动Ability来了解如何在Ability被授予时自动激活它), 从而使其做出相应的动作. ASC一般在OwnerActor的构建函数中创建并且需要明确标记为Replicated. 该文档的目的是阐明GAS中的主要概念和相关类, 并结合我的经验提供一些附加说明. 由其2025 在社区用户中, 已经形成了大量有关GAS的”部落知识”, 而我致力于将我了解的全部在这里分享.