目录
4.3.1 属性的定义 - Attribute Definition
Attribute
是由结构体FGameplayAttributeData
定义的一系列浮点值。它们能够表示角色拥有的生命值、角色等级以及药水的充能数等等。只要是从属于Actor
的游玩相关的数值,就可以考虑将其设为一项Attribute
。Attribute
通常应该由GameplayEffect
来负责修改,这样ASC
就能够predict预测相应的变化。
Attributes
是由AttributeSet
来进行定义的并保存在其中。AttributeSet
负责处理那些被标记要进行复制的Attributes
。参考AttributeSets
的相关小节,来获取更多关于如何定义Attributes
的内容。
小贴士: 如果你不想要某个Attribute
显示在编辑器的Attributes
列表中,可以使用Meta = (HideInDetailsView)
这样的属性说明符(Property Specifier
)。
4.3.2 基本值和当前值 - BaseValue vs CurrentValue
每个属性Attribute
都由两个值组成——基本值BaseValue
和当前值CurrentValue
。BaseValue
是Attribute
的一个恒定值,而CurrentValue
则是BaseValue
再叠加上来自GameplayEffects
的临时修改后的结果。例如,你的Character
可能会有一个移动速度Attribute
,其BaseValue
为600u/s(译者注:单位虚幻距离每秒)。此时还没有施加任何的影响移动速度相关的GameplayEffects
,CurrentValue
也就还是600u/s。如果角色被施加了一个50u/s的移速buff,BaseValue
还仍然是600u/s,而CurrentValue
此时则是600 + 50 = 650u/s。当移速的buff消失后吗,CurrentValue
会恢复到BaseValue
的值,也就是600u/s。
GAS的新手经常会把BaseValue
和Attribute
的最大值搞混,把两者当作同一个东西。这种认知并不正确。Attributes
的最大值也会发生改变,它会和技能或者UI相关联,应该作为一个单独的Attributes
来处理。对于硬编码的最大值和最小值,有一种方式可以通过FAttributeMetaData
的DataTable
来定义(其中有关于最大值和最小值设置的内容),但是Epic对于这个结构体注释道:work in progress,也就是该功能目前还没有稳定下来,可能还会进行修改。详细内容请参阅AttributeSet.h
。为了防止混淆,我建议是将那些和技能或者UI关联的最大值作为一个单独的Attributes
来对待——硬编码的最大值和最小值仅用于限定AttributeSet
中的Attributes
的上下限的限定。Attributes
的上下限的限制的讨论后面还会继续进行,具体是在GameplayEffects
为属性施加影响时,比如PreAttributeChange()中对CurrentValue
可以发生的变化的限制,又比如PostGameplayEffectExecute()中对BaseValue
可以发生的变化的限制。
即刻生效Instant
的GameplayEffects
会对BaseValue
产生永久性的影响,而持续一段时间Duration
的和无限持续Infinite
的GameplayEffects
改变的是CurrentValue
。周期性Periodic
的GameplayEffects
和instant
类型的GameplayEffects
是类似的,都改变的是BaseValue
。
4.3.3 元属性 - Meta Attributes
一些Attributes
会作为和其他Attributes
作交互的临时值的占位数据,这一类的属性被称为是元属性Meta Attributes
。例如,我们通常会去将伤害值定义为Meta Attribute
。我们使用伤害值的Meta Attribute
作为占位数据,而不是使用GameplayEffect
直接改变我们的生命值的Attribute
。这样,伤害值就可以通过 GameplayEffectExecutionCalculation
中的buff和debuff等进行修改,也可以在AttributeSet
中作进一步处理,例如让伤害值减去当前的护甲的Attribute
,然后再让生命值的Attribute
减去前面的结果。伤害值的Meta Attribute
在多个GameplayEffects
之间并不是恒定的,可以被任意一个覆盖重写。Meta Attributes
通常不会被复制。
像我们经常会说:“我造成了多少的伤害”,“这个伤害值怎么处理”之类的,Meta Attributes
为此(伤害和治疗这类的属性)提供了一个良好的逻辑分离。这里的逻辑分离意思是我们的Gameplay Effects
和Execution Calculations
并不需要知道目标是如何处理这个伤害值的。继续我们关于伤害的话题,Gameplay Effect
决定了伤害值的多少,然后AttributeSet
去具体处理这个值。并不是所有的角色都有着相同的Attributes
,特别是当你拓展AttributeSets
的子类时。基类AttributeSet
可能仅有生命值一个Attribute
,其子类可能添加了一个护盾的Attribute
。那自然的,基类和子类在处理这个伤害值的时候就不同了。
即便Meta Attributes
是一个良好的设计模式,但是这并不意味着非得用它不可。如果你仅有一个Execution Calculation
用来处理所有的伤害值,且所有角色共享着同一个Attribute Set
,那么你就可以直接在Execution Calculation
中来作伤害值、生命值和护盾值的计算和修改。这样做的代价自然就是牺牲掉一定的灵活性,这中间的权衡全在于你。
4.3.4 响应属性的变化 - Responding to Attribute Changes
要监听某个Attribute
的变化从而更新UI或者其他游玩部分,可以使用UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute)
。这个方法返回一个委托,你可以自由绑定相应的回调,当对应的Attribute
发生变化时就会自动执行这个回调。这个委托提供了一个FOnAttributeChangeData
参数,有NewValue
,OldValue
以及FGameplayEffectModCallbackData
。注意:FGameplayEffectModCallbackData
只能够在服务器进行设置。
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetHealthAttribute()).AddUObject(this, &AGDPlayerState::HealthChanged);
virtual void HealthChanged(const FOnAttributeChangeData& Data);
示例项目将Attribute
值变化的委托绑定到GDPlayerState
中的一个方法来更新HUD并响应玩家的死亡(生命值归零)的情况。
在示例项目中还包含一个使用异步任务ASyncTask
将所有这些封装起来的自定义蓝图节点。它被用在名为UI_HUD
的UMG Widget
用来更新生命值,魔法值以及体力值。这个AsyncTask
会一直存在直到手动调用了EndTask()
,我们一般会在UMG Widget
的Destruct
事件中去调用。参阅AsyncTaskAttributeChanged.h/cpp
获取更多内容。
4.3.5 衍生属性 - Derived Attributes
要令某个Attribute
的值是从其他某个或者某些Attributes
的值衍生过来,需要使用Infinite
类型的GameplayEffect
,以及一个或多个Attribute Based
或者MMC
的Modifiers
。Derived Attribute
将会自动根据其依赖的Attribute
的更新而进行更新。
Derived Attribute
上的所有Modifiers
的最终公式与Modifier Aggregators
的公式是同一个。如果你需要依照一定的顺序进行计算,需要在MMC
内完成所有的操作。
((CurrentValue + Additive) * Multiplicitive) / Division
注意: 如果在PIE中运行多个客户端时,你需要在编辑器偏好界面中禁用Run Under One Process
,否则处第一个客户端以外的其他客户端将不会更新Derived Attributes
。
这里我们举个例子,我们有一个Infinite
类型的GameplayEffect
,其会根据TestAttrB
和TestAttrC
的值来推导TestAttrA
的值。公式具体为TestAttrA = (TestAttrA + TestAttrB) * ( 2 * TestAttrC)
。无论何时,当TestAttrB
和TestAttrC
中的任意一个属性更新时,那么TestAttrA
将会自动根据上面的公式进行计算。