Attribute的定义

Attribute通过FGameplayAttributeData定义,其本质是一个浮点值,能够表达从角色经验、攻击力、受到的伤害、药水的价格等等游戏逻辑数值。

 

(UE_Note)基本所有与游戏性相关的数值都可以用Attribute定义,但并不是绝对,通常Atributes只能被GameEffect修改,如果一个数值无法通过GE修改,那么它就不适合定义成Attribute。

Attributes被定义在AttributeSet(属性集)中,AttributeSet也会处理Attributes的复制。

BaseValue与CurrentValue

Attribute由一个基础值BaseValue和一个当前值CurrentValue组成,基础值是一个恒定值,当前值是基础值加上GE修改之后的临时值。

例如,某角色的最大生命值MaxHp的基础值为1000,那么当角色创建时,MaxHp的当前值与基础值相同,为1000。如果角色获得了一个100生命上限加成的GE,那么MaxHp的当前值会变为1000+100=1100,而基础值仍为1000。当这个GE移除时,当前值会恢复为1000。

要注意,不要把一个属性的基础值当做”最大值“来使用,例如一个当前Hp和最大Hp应该是两个Attribute,而不能把最大Hp的CurrentValue当做当前Hp。

Instant(瞬时)GE会立刻永久性地改变BaseValue,而Duration(持续性)和Infinite(永久性)的GE将改变CurrentValue。Periodic(周期性)的GE会间隔地永久性改变BaseValue。

推导属性(DerivedAttributes)

实际的属性设计中,有些属性是基于其他属性推导而来的,例如攻击力=(0.7*力量+基础攻击力)*总攻击力加成。

可以创建一个永久性的GE,利用其MMC(CustomCalculationClass)的修改器进行计算,当上述公式中的推导公式中的属性发生变化时,推导属性的值将会自动更新。

元属性(MetaAttributes)

有一些属性会被作为某些逻辑的过程临时值使用。伤害值是一个很好例子。

我们把Damage属性定义为一个元属性,而不是使用GE直接修改Hp,这样我们就可以被各种持续性的GE去影响,只要让Hp减掉Damage即可。

元属性实现了伤害计算的逻辑分离,各种伤害加成效果甚至不需要关心他们如何影响Damage。

属性集(AttributeSet)

AttributeSet定义并管理一组Attribute,将AttributeSet注册到ASC上使之生效。

一个ASC可以拥有多个AttributeSet,这里推荐以组合化的方式设计AttributeSet。例如,在一个Moba游戏中,不同英雄使用的资源可能不同,有的使用法力,有的使用能量,还有的可能什么都不需要(比如野怪),这时,就可以为不同资源相关的属性(最大法力值、当前法力值、法力回复等)定义到一个AttributeSet中,然后根据实际情况为角色添加属性集,当然你也可以定义一个特别大的属性集,包括所有需要的属性。

限制属性的上/下限

AttributeSet中有个PreAttributeChange(const FGameplayAttribute& Attribute,float& NewValue)的方法,在属性的CurrentValue被改变前被调用,非常适合去做属性的限制。

该方法会传入即将改变的属性,并在修改后传出其新值。

(UE_Note)这里源码特别注释:

There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.

This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".

因为任何对属性的修改都会调用它,所以不要试图用它来做游戏逻辑,而应该乖乖地去处理属性。

伤害值的最终处理

AttributeSet另一个重要的方法是PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data),它仅会在instant类的GE修改属性的BaseValue时调用。

假如我们使用元属性实现Damage,那么每当造成伤害时,这个方法就可以用Hp减去Damage属性,如果有护盾的话,则可以先扣除护盾等等逻辑也在此处理。

其后续的伤害飘字等也可以在此处处理。

要注意,只有instant类(或是Period触发的Mod)的GE才能触发此方法,直接修改Attribute则无法触发。

自定义聚合器

有时候我们会有这样的需求:当一个角色身上有很多减速效果时,我们不希望它们同时生效,将角色的移动速度降到极慢,而是希望只有减速效果最强的生效。

而对于加速效果,我们希望所有的加速效果都能叠加,让角色跑得飞快(或是正好相反)。这时候,我们可以使用FAggregatorEvaluateMetaDataLibrary中提供的聚合器类型,在

OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator)对移动速度属性进行设置。

如:

if (Attribute == GetMoveSpeedAttribute()) { NewAggregator->EvaluationMetaData = &FAggregatorEvaluateMetaDataLibrary::MostNegativeMod_AllPositiveMods; }

MostNegativeMod_AllPositiveMods意味着,在该属性进行聚合时,会叠加所有增益效果,而只取最强的减益效果。