本篇文章主要是针对GE的一些细节进行深入的解读,需要具备GAS的相关基础,我也会尽量减少贴代码,有需要的读者可以去看源码,结合着本文可以有更深入的理解,本文很多地方都会相对简述。我认为一个好的学习方式是从问问题开始的,从一个问题开始,回答,然后继续提问,回答,直到终点为止,当绝大多数问题得到解答以后,对相关的知识也就能够掌握了。
本篇文章由四个部分组成:解析Duration Effect,解析GE的预测,Stack Effect的原理,扩展GE功能。本篇文章也将从一个问题开始,首先就是
Duration Effect是如何生效的?
对GAS有一个基础的了解后,我们会知道,Effect本身并不会实例化,当ApplyEffect后,会生成一个实例,即FActiveGameplayEffect,它会储存在ASC的ActiveContainer中。游戏内所有的GE效果都会储存在这里,同时模拟端的GE也是通过它进行网络同步的(在默认模式下,只有主控端的ActiveEffect会同步)
对这个流程感兴趣的可以自己去看函数UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf
,会调用ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE)
生成实例化GE,具体的过程我就不赘述了,有一个概念就行。
我们已经有了实例化的GE——ActiveEffect了,它可以被认为是Duration Effect本身,它已经被Add进ActiveEffectContainer中了,但此时它并没有任何实际的效果,很自然地就会需要提问,Duration Effect是在什么地方开始生效的,这关联了以下两个的问题:
GE又是如何激活并生效的,它的激活与关闭是如何实现的的?
这两个问题是相互关联的,当ActiveEffectContainer创建GE时,会调用函数InternalOnActiveGameplayEffectAdded
,在这里激活GE让其生效,这边函数实际意思为,关闭AcitiveGE,输入为true关闭GE,false激活GE,方便理解我就直接把这里的行为称作激活了GE。
需要注意的是,这里的Inhibit指的是让GE激活和关闭效果,并不会将GE移除,和Add和Remove的概念是不一样的。可以理解为GE首先生成ActiveEffect实例,被添加Add到ActiveEffectContainer中,然后通过激活(!Inhibit)GE产生实际的效果,包括添加tag,修改属性等。
有了激活,我们很容易就会想到一个问题,GE里可以通过配置,让ActiveGE在某些tag下生效,没有这些tag的时候无效,是怎么实现的呢?没错,同样是通过调用InhibitGE这个函数。
如果看过我之前解析GAS在5.3改动的那篇文章,就会知道GE在5.3之后,功能是通过EffectComponent去实现的,而TargetTagRequirementGameplayEffectComponent的作用是,在满足特定Tag条件后,会添加移除,或者激活关闭GE。
它的实现方式为,在GE被添加时,监听ASC中tag变化的消息,当tag发生变化时,会触发函数UTargetTagRequirementsGameplayEffectComponent::OnTagChanged
,然后判断是否满足GE激活的条件,如果不满足,则调用Inhibit函数去关闭GE。
有了这个例子作为参考,我们可以很衍生出去,是否可以对GE地功能进行扩展,能不能在特定属性的条件下添加移除GE,或者激活关闭GE呢?
答案是YES,我会在文章的最后,实现这个功能扩展。感谢UE5.3的更新,让类似的功能可以比以前更加方便优雅地实现了。
到了这里,我们了解到GE在被Add到Container中后,会被激活(!Inhibit),GE的效果是在这里生效的,接下来的问题是:
GE激活后,会影响哪些属性呢?
GE生效的函数为AddActiveGameplayEffectGrantedTagsAndModifiers
在这里只考虑非Period的Duration GE,Period GE的实现是通过设置一个Timer,周期性地调用ExecutePeriodicEffect
去生效,可以简单的理解为周期性执行的Instant GE,这里就不详细讨论了。
GE的效果大致有以下几个部分组成:Attribute,Tag,Ability,Cue
Attribute
首先从Attribute开始,Duration GE首先影响的自然就是属性,GE会修改角色的Attribute的Current值,相信读这篇文章的应该都对Attribute的base值和current值有所了解,两者可以大致理解为基础值,和受到各种GE影响后的实际值。base值比较好理解,因为修改后和GE本身就无关了,但是current值是如何实现的呢?这个问题等下会深入的讲解,现在暂时放下不表,我们继续看GE还有什么效果。
Tag
Tag的修改比较简单,获取GE Def和Spec中的Grant Tags,然后调用ASC的UpdateTagMap对CountTagContainer进行修改,比较简单,不赘述了。
然后通过ASC对Tag进行网络的属性同步
这里需要注意的是,在模拟端,默认情况下ActiveEffect并不会同步,可以理解为在模拟端,GE是不存在的,GE效果是通过服务器上Tag, Attribute,Cue等效果的同步,在模拟端产生效果的。
Ability
给予Ability,此功能在此函数里已经被废弃,5.3以后是通过GE Component去给予Ability的。
读者可以通过在GE中配置UAbilitiesGameplayEffectComponent
让GE具有Give Ability的功能。
GE对Ability的影响还有一个,可以block ability,依据Ability中配置的BlockTags条件,停止当前正在激活的技能。
Cue
最后一个GE的效果是触发Gameplay Cue,逻辑大致为,遍历配置需要触发的GameplayCues,然后修改角色的Tags,让其添加对应的Cue Tag,然后调用InvokeGameplayCueEvent
让Cue在本地生效,最后让Cue通过同步在模拟端生效。
从这里可以看出,Cue Tag虽然添加到ASC上,但它实际并没有被网络同步,是一个Local Tag。