4.5 Gameplay Effects
4.5.1 定义Gameplay Effects
GameplayEffects
(简称GE
)是Ability
改变Attributes
和GameplayTags
的载体。它们可以立刻改变Attribute
,如伤害或治疗,或施加长期的状态增益/减益,如移动速度提升或眩晕。UGameplayEffect
类是一个**Data-Only
(仅数据)**类,用于定义单个游戏效果。GameplayEffects
中不应添加额外的逻辑。通常,设计师会创建UGameplayEffect
的许多蓝图子类。
GameplayEffects
通过Modifiers
和Executions
(GameplayEffectExecutionCalculation
)改变Attributes
。
GameplayEffects
有三种持续时间类型:Instant
、Duration
和Infinite
。
此外,GameplayEffects
可以添加/执行GameplayCues
。Instant
类型的GameplayEffect
会在GameplayCue
的GameplayTags
上调用Execute
,而Duration
或Infinite
类型的GameplayEffect
会在GameplayCue
的GameplayTags
上调用Add
和Remove
。
持续时间类型 | GameplayCue事件 | 何时使用 |
---|---|---|
Instant (即时) | Execute | 用于对Attribute 的BaseValue 进行即时永久更改。GameplayTags 不会被应用,甚至不会持续一帧。 |
Duration (持续) | Add & Remove | 用于对Attribute 的CurrentValue 进行临时更改,并应用GameplayTags ,这些标签将在GameplayEffect 过期或被手动移除时被移除。持续时间在UGameplayEffect 类/蓝图中指定。 |
Infinite (无限) | Add & Remove | 用于对Attribute 的CurrentValue 进行临时更改,并应用GameplayTags ,这些标签将在GameplayEffect 被移除时被移除。这些标签不会自行过期,必须由能力或ASC 手动移除。 |
Duration
和Infinite
类型的GameplayEffects
可以选择应用Periodic Effects
,这些效果会根据其定义的Period
每隔X
秒应用其Modifiers
和Executions
。在更改Attribute
的BaseValue
和执行GameplayCues
时,Periodic Effects
被视为Instant
类型的GameplayEffects
。这些效果对于持续伤害(DOT)类型的效果非常有用。注意:Periodic Effects
无法被预测。
Duration
和Infinite
类型的GameplayEffects
可以在应用后暂时关闭和打开,如果它们的Ongoing Tag Requirements
未满足/满足(Gameplay Effect Tags)。关闭GameplayEffect
会移除其Modifiers
和应用的GameplayTags
的效果,但不会移除GameplayEffect
。重新打开GameplayEffect
会重新应用其Modifiers
和GameplayTags
。
如果需要手动重新计算Duration
或Infinite
类型GameplayEffect
的Modifiers
(例如,您有一个MMC
使用的数据不来自Attributes
),可以使用UAbilitySystemComponent::ActiveGameplayEffects.SetActiveGameplayEffectLevel(FActiveGameplayEffectHandle ActiveHandle, int32 NewLevel)
,并使用UAbilitySystemComponent::ActiveGameplayEffects.GetActiveGameplayEffect(ActiveHandle).Spec.GetLevel()
获取相同的级别。基于支持Attributes
的Modifiers
会在这些支持Attributes
更新时自动更新。SetActiveGameplayEffectLevel()
更新Modifiers
的关键功能是:
MarkItemDirty(Effect);
Effect.Spec.CalculateModifierMagnitudes();
// 私有函数,否则我们可以在不需要将级别设置为当前级别的情况下调用这三个函数
UpdateAllAggregatorModMagnitudes(Effect);
GameplayEffects
通常不会被实例化。当一个能力或ASC
想要应用一个GameplayEffect
时,它会从GameplayEffect
的ClassDefaultObject
创建一个GameplayEffectSpec
。成功应用的GameplayEffectSpecs
会被添加到一个名为FActiveGameplayEffect
的新结构中,这是ASC
在一个特殊的容器结构ActiveGameplayEffects
中跟踪的内容。
4.5.2 应用Gameplay Effects
GameplayEffects
可以以多种方式应用,从GameplayAbilities
和ASC
上的函数,通常采用ApplyGameplayEffectTo
的形式。不同的函数本质上只是方便函数,最终会调用UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf()
到Target
。
要在外部应用GameplayEffects
,例如从投射物,需要获取Target's
ASC
并使用其函数之一来ApplyGameplayEffectToSelf
。
可以通过绑定到其委托来监听任何Duration
或Infinite
类型的GameplayEffects
何时应用于ASC
:
AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(this, &APACharacterBase::OnActiveGameplayEffectAddedCallback);
回调函数:
virtual void OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);
服务器将始终调用此函数,无论同步模式如何。自主代理将仅为此同步GameplayEffects
在Full
和Mixed
同步模式中调用此函数。模拟代理将仅在此同步Full
同步模式中调用此函数。
4.5.3 移除Gameplay Effects
GameplayEffects
可以以多种方式移除,从GameplayAbilities
和ASC
上的函数,通常采用RemoveActiveGameplayEffect
的形式。不同的函数本质上只是方便函数,最终会调用FActiveGameplayEffectsContainer::RemoveActiveEffects()
到Target
。
要移除GameplayEffects
外部GameplayAbility
,需要获取Target's
ASC
并使用其函数之一来RemoveActiveGameplayEffect
。
可以通过绑定到其委托来监听任何Duration
或Infinite
类型的GameplayEffects
何时从ASC
中移除:
AbilitySystemComponent->OnAnyGameplayEffectRemovedDelegate().AddUObject(this, &APACharacterBase::OnRemoveGameplayEffectCallback);
回调函数:
virtual void OnRemoveGameplayEffectCallback(const FActiveGameplayEffect& EffectRemoved);
服务器将始终调用此函数,无论同步模式如何。自主代理将仅为此同步GameplayEffects
在Full
和Mixed
同步模式中调用此函数。模拟代理将仅在此同步Full
同步模式中调用此函数。
4.5.4 Gameplay Effect Modifiers
Modifiers
改变一个Attribute
,并且是唯一可以预测改变一个Attribute
的方法。一个GameplayEffect
可以有零个或多个Modifiers
。每个Modifier
只负责更改一个Attribute
,通过指定的操作。
操作 | 描述 |
---|---|
Add | 将结果添加到Modifier 指定的Attribute 。使用负值进行减法。 |
Multiply | 将结果乘以Modifier 指定的Attribute 。 |
Divide | 将结果除以Modifier 指定的Attribute 。 |
Override | 将Modifier 指定的Attribute 覆盖为结果。 |
Attribute
的CurrentValue
是所有Modifiers
添加到其BaseValue
的结果。聚合公式如下:
((InlineBaseValue + Additive) * Multiplicitive) / Division
任何Override
Modifiers
将覆盖最终值,最后应用的Modifier
优先。
**注意:**对于百分比变化,请确保使用Multiply
操作,以便在添加之后发生。
注意:预测对百分比变化有困难。
有四种类型的Modifiers
:ScalableFloat
(可缩放浮点)、Attribute-Based
(基于属性的)、Custom Calculation Class
(自定义计算类)和SetByCaller
(设置为调用者)。它们都生成一些浮点值,然后用于根据其操作更改指定的Attribute
。
Modifier 类型 | 描述 |
---|---|
ScalableFloat | FScalableFloats 是一个结构,可以指向具有行和级别作为列的数据表。可缩放浮点将自动读取指定表行的值,该表行在能力当前级别(或不同级别,如果覆盖)。此值可以进一步通过系数进行操作。如果未指定数据表/行,则将其视为1,因此系数可以用于在所有级别中硬编码单个值。 ![]() |
Attribute Based | Attribute Based Modifiers 获取Source (谁创建了GameplayEffectSpec )或Target (谁接收了GameplayEffectSpec )的CurrentValue 或BaseValue ,并进一步修改它与系数和前系数添加。Snapshotting 意味着在创建GameplayEffectSpec 时捕获支持Attribute ,而无需快照意味着在应用GameplayEffectSpec 时捕获Attribute 。 |
Custom Calculation Class | Custom Calculation Class 提供了最灵活的Modifiers 。此Modifier 采用ModifierMagnitudeCalculation 类,并可以进一步修改结果浮点值与系数和前系数添加。 |
Set By Caller | SetByCaller Modifiers 是值,这些值是在运行时通过能力或谁制作GameplayEffectSpec 在GameplayEffectSpec 之外设置的。例如,如果您想根据玩家按住按钮的时间来设置伤害,可以使用SetByCaller 。SetByCallers 是TMap<FGameplayTag, float> ,它们生活在GameplayEffectSpec 上。Modifier 只是告诉Aggregator 查找与提供的GameplayTag 关联的SetByCaller 值。SetByCallers 用于Modifiers 的GameplayTag 版本,FName 版本在此被禁用。如果Modifier 设置为SetByCaller ,但GameplayEffectSpec 上不存在具有正确GameplayTag 的SetByCaller ,游戏将抛出运行时错误并返回值0。这可能会在Divide 操作中引起问题。请参阅SetByCallers 了解更多信息。 |
4.5.4.1 Multiply和Divide Modifiers
默认情况下,所有Multiply
和Divide Modifiers
在将它们应用于Attribute
的BaseValue
之前会一起添加。
float FAggregatorModChannel::EvaluateWithBase(float InlineBaseValue, const FAggregatorEvaluateParameters& Parameters) const
{
...
float Additive = SumMods(Mods[EGameplayModOp::Additive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Additive), Parameters);
float Multiplicitive = SumMods(Mods[EGameplayModOp::Multiplicitive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Multiplicitive), Parameters);
float Division = SumMods(Mods[EGameplayModOp::Division], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Division), Parameters);
...
return ((InlineBaseValue + Additive) * Multiplicitive) / Division;
...
}
float FAggregatorModChannel::SumMods(const TArray<FAggregatorMod>& InMods, float Bias, const FAggregatorEvaluateParameters& Parameters)
{
float Sum = Bias;
for (const FAggregatorMod& Mod : InMods)
{
if (Mod.Qualifies())
{
Sum += (Mod.EvaluatedMagnitude - Bias);
}
}
return Sum;
}
摘自 GameplayEffectAggregator.cpp
Multiply
和Divide
Modifiers
在公式中都有一个Bias
值为1
(Addition
有一个Bias
为0
)。所以它看起来像这样:
1 + (Mod1.Magnitude - 1) + (Mod2.Magnitude - 1) + ...
这个公式导致了一些意外的结果。首先,这个公式在添加或除以数字之前将所有修饰符添加在一起。大多数人会期望它将它们相乘或除以它们。例如,如果您有两个Multiply
修饰符为1.5
,大多数人会期望BaseValue
乘以1.5 x 1.5 = 2.25
。相反,这个公式将1.5
添加到BaseValue
中,使其增加2
(50%增加+另一个50%增加=100%增加)。这是从GameplayPrediction.h
中的示例,其中10%
速度增益在500
基础速度上为550
。添加另一个10%
速度增益,它将为600
。
其次,这个公式有一些未记录的规则,这些规则是根据Paragon设计的。
Multiply
和Divide
乘法加法公式规则:
(No more than one value < 1) AND (Any number of values [1, 2))
OR (One value >= 2)
公式中的Bias
基本上从Sum
值中减去数字范围[1, 2)
中的整数数字。循环开始时的Bias
值减去从Sum
值中减去,因此任何值本身都可以工作,并且任何小于1
的值都可以与数字范围[1, 2)
中的数字一起工作。
一些例子与Multiply
:
Multipliers: 0.5
1 + (0.5 - 1) = 0.5
,正确
Multipliers: 0.5, 0.5
1 + (0.5 - 1) + (0.5 - 1) = 0
,不正确,预期1
?多个小于1
的值对于添加乘数没有意义。Paragon被设计为仅使用最大负值用于Multiply
Modifiers
,因此最多只会有一值小于1
乘以BaseValue
。
Multipliers: 1.1, 0.5
1 + (0.5 - 1) + (1.1 - 1) = 0.6
,正确
Multipliers: 5, 5
1 + (5 - 1) + (5 - 1) = 9
,不正确,预期10
。总是Modifiers
的数量减去1的和。
许多游戏将希望他们的Multiply
和Divide
Modifiers
在将它们应用于BaseValue
之前将它们相乘或除以它们。要实现这一点,您需要更改引擎代码以覆盖FAggregatorModChannel::EvaluateWithBase()
。
float FAggregatorModChannel::EvaluateWithBase(float InlineBaseValue, const FAggregatorEvaluateParameters& Parameters) const
{
...
float Multiplicitive = MultiplyMods(Mods[EGameplayModOp::Multiplicitive], Parameters);
float Division = MultiplyMods(Mods[EGameplayModOp::Division], Parameters);
...
return ((InlineBaseValue + Additive) * Multiplicitive) / Division;
}
float FAggregatorModChannel::MultiplyMods(const TArray<FAggregatorMod>& InMods, const FAggregatorEvaluateParameters& Parameters)
{
float Multiplier = 1.0f;
for (const FAggregatorMod& Mod : InMods)
{
if (Mod.Qualifies())
{
Multiplier *= Mod.EvaluatedMagnitude;
}
}
return Multiplier;
}
4.5.4.2 Modifiers上的Gameplay Tags
每个Modifier都可以设置SourceTag
和TargetTag
。它们的工作方式与Application Tag requirements
相同。因此,这些标签仅在效果被应用时考虑。例如,当具有周期性、无限效果时,它们仅在效果第一次应用时被考虑,但不在每次周期性执行时被考虑。
Attribute Based
的 Modifiers
还可以设置SourceTagFilter
和TargetTagFilter
。在确定属性的大小(这是基于属性的修改器的源)时,这些过滤器用于排除某些Modifier
,以该属性。源或目标没有所有过滤器的标签的Modifiers
会被排除在外。
这意味着:Source ASC
和目标Target ASC
的标签被GameplayEffects
捕获。Source ASC
标签在创建GameplayEffectSpec
时捕获,Target ASC
标签在效果执行时捕获。当确定是否应用效果的修改器时,如果设置了过滤器,则捕获的标签与过滤器进行比较。
4.5.5 堆叠Gameplay Effects
默认情况下,GameplayEffects
会应用新的GameplayEffectSpec
实例,这些实例不知道或不关心之前存在的GameplayEffectSpec
实例。GameplayEffects
可以设置为堆叠,其中不是添加新的GameplayEffectSpec
实例,而是更改当前存在的GameplayEffectSpec's
堆叠计数。堆叠仅适用于Duration
和Infinite
类型的GameplayEffects
。
有两种堆叠类型:Aggregate by Source
(按源聚合)和Aggregate by Target
(按目标聚合)。
堆叠类型 | 描述 |
---|---|
Aggregate by Source (按源聚合) | 每个源ASC 在Target 上都有一个单独的堆叠实例。每个源可以应用X数量的堆叠。 |
Aggregate by Target (按目标聚合) | Target 上只有一个堆叠实例,无论来源如何。每个源可以应用堆叠,直到达到共享堆叠限制。 |
堆叠也有策略用于过期、持续刷新和周期重置。它们在GameplayEffect
蓝图中有一个有用的悬停工具提示。
示例项目包括一个自定义蓝图节点,该节点监听GameplayEffect
堆叠更改。HUD UMG小部件使用它来更新玩家被动装甲堆叠的数量。此AsyncTask
将永远存在,直到手动调用EndTask()
,我们在UMG小部件的Destruct
事件中这样做。请参阅AsyncTaskEffectStackChanged.h/cpp
。
4.5.6 授予Abilities
GameplayEffects
可以授予新的GameplayAbilities
到ASCs
。只有Duration
和Infinite
类型的GameplayEffects
可以授予能力。
一个常见用例是当您希望强制另一个玩家执行某些操作时,例如将他们从击退或拉出。您将应用GameplayEffect
到他们,该GameplayEffect
授予他们自动激活能力(请参阅被动Abilities,了解如何自动激活能力),该能力执行所需的操作。
设计师可以选择授予GameplayEffect
的能力、授予它们的级别、在何处绑定输入它们以及授予能力的移除策略。
移除策略 | 描述 |
---|---|
立即取消Ability | 当授予能力的GameplayEffect 被移除时,立即取消并移除该能力。 |
在结束时移除Ability | 允许能力完成,然后从目标中移除能力。 |
Do Nothing (什么都不做) | 目标不受授予GameplayEffect 移除的影响。目标将永久拥有能力,直到手动移除。 |
4.5.7 Gameplay Effect Tags
GameplayEffects
携带多个GameplayTagContainers
。设计师将编辑每个类别和结果的Added
和Removed
GameplayTagContainers
,结果将显示在编译时的Combined
GameplayTagContainer
上。Added
标签是此GameplayEffect
添加的新标签,其父类没有先前添加。Removed
标签是父类具有但此子类没有的标签。
类别 | 描述 |
---|---|
Gameplay Effect Asset Tags | 标签是GameplayEffect 具有的标签。它们本身没有任何功能,仅用于描述GameplayEffect 。 |
Granted Tags | 标签生活在GameplayEffect 上,但也提供给应用GameplayEffect 的ASC 。它们在移除GameplayEffect 时从ASC 中移除。此功能仅适用于Duration 和Infinite 类型的GameplayEffects 。 |
Ongoing Tag Requirements | 一旦应用,这些标签确定GameplayEffect 是否打开。如果GameplayEffect 由于未能通过持续标签要求而关闭,但随后满足要求,GameplayEffect 将再次打开并重新应用其修改器。此功能仅适用于Duration 和Infinite 类型的GameplayEffects 。 |
Application Tag Requirements | 目标上的标签,用于确定是否可以将GameplayEffect 应用于目标。如果这些要求未满足,则不会应用GameplayEffect 。 |
Remove Gameplay Effects with Tags | 目标上的GameplayEffects 具有这些标签中的任何一个,这些标签在Asset Tags 或Granted Tags 中,将从目标中移除目标,当此GameplayEffect 成功应用时。 |
4.5.8 Immunity(免疫)
GameplayEffects
可以授予免疫,有效地阻止其他GameplayEffects
的申请,基于GameplayTags
。虽然免疫可以通过其他方式有效地实现,例如Application Tag Requirements
,使用此系统提供了委托,当GameplayEffects
由于免疫而被阻止时。
GrantedApplicationImmunityTags
检查源ASC
(包括从源能力AbilityTags
中提取的标签,如果有的话)是否具有任何指定的标签。这是防止从某些角色或来源的所有GameplayEffects
的一种方法,基于他们的标签。
Granted Application Immunity Query
检查传入的GameplayEffectSpec
,如果它匹配任何查询以阻止或允许其应用。
查询有有用的悬停工具提示在GameplayEffect
蓝图中。
4.5.9 Gameplay Effect Spec
GameplayEffectSpec
(简称GESpec
)可以被认为是GameplayEffects
的实例化。它们持有对它们表示的GameplayEffect
类的引用、创建时所在的级别以及谁创建了它们。这些可以在运行时自由创建和修改,而不像GameplayEffects
应该由设计师在运行时之前创建。当应用GameplayEffect
时,从GameplayEffect
创建GameplayEffectSpec
,这实际上是应用到目标的。
GameplayEffectSpecs
是从GameplayEffects
使用UAbilitySystemComponent::MakeOutgoingSpec()
创建的,该UAbilitySystemComponent::MakeOutgoingSpec()
是BlueprintCallable
。GameplayEffectSpecs
不需要立即应用。通常,将GameplayEffectSpec
传递给从能力创建的投射物,该投射物可以在稍后应用于目标。当GameplayEffectSpecs
成功应用时,它们返回一个新的结构,称为FActiveGameplayEffect
。
重要的GameplayEffectSpec
内容:
- 此
GameplayEffect
是从哪个GameplayEffect
创建的。 - 此
GameplayEffectSpec
的级别。通常与创建GameplayEffectSpec
的能力的级别相同,但可以不同。 GameplayEffectSpec
的持续时间。默认情况下为GameplayEffect
的持续时间,但可以不同。GameplayEffectSpec
的周期,用于周期效果。默认情况下为GameplayEffect
的周期,但可以不同。- 此
GameplayEffectSpec
的当前堆叠计数。堆叠限制在GameplayEffect
上。 GameplayEffectContextHandle
告诉我们谁创建了这个GameplayEffectSpec
。- 由于快照,捕获了
Attributes
。 DynamicGrantedTags
,GameplayEffectSpec
授予目标,除了GameplayEffect
授予的GameplayTags
。DynamicAssetTags
,GameplayEffectSpec
具有的AssetTags
,除了GameplayEffect
具有的AssetTags
。SetByCaller
TMaps
。
4.5.9.1 SetByCallers(调用者设置)
SetByCallers
允许GameplayEffectSpec
携带与GameplayTag
或FName
关联的浮点值。它们存储在各自的TMaps
中:TMap<FGameplayTag, float>
和TMap<FName, float>
在GameplayEffectSpec
上。这些可以用于GameplayEffect
上的Modifiers
或作为通用手段,将浮点数从一个地方传递到另一个地方。通常,将数值数据生成在能力内部传递到GameplayEffectExecutionCalculations
或ModifierMagnitudeCalculations
通过SetByCallers
。
SetByCaller 用法 | 笔记 |
---|---|
Modifiers | 必须提前在GameplayEffect 类中定义。只能使用GameplayTag 版本。如果一个在GameplayEffect 类中定义,但GameplayEffectSpec 上没有相应的标签和浮点值对,游戏将在应用GameplayEffectSpec 时抛出运行时错误并返回0。这是Divide 操作的问题。请参阅Modifiers 。 |
其他用法 | 不需要提前在任何地方定义。读取GameplayEffectSpec 上不存在SetByCaller 可以返回开发人员定义的默认值,并带有可选警告。 |
要在蓝图中分配SetByCaller
值,请使用您需要的版本(GameplayTag
或FName
)的蓝图节点:
要在蓝图中读取SetByCaller
值,您将需要在您的蓝图库中制作自定义节点。
要在C++中分配SetByCaller
值,请使用您需要的版本(GameplayTag
或FName
)的函数:
void FGameplayEffectSpec::SetSetByCallerMagnitude(FName DataName, float Magnitude);
void FGameplayEffectSpec::SetSetByCallerMagnitude(FGameplayTag DataTag, float Magnitude);
要在C++中读取SetByCaller
值,请使用您需要的版本(GameplayTag
或FName
)的函数:
float GetSetByCallerMagnitude(FName DataName, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const;
float GetSetByCallerMagnitude(FGameplayTag DataTag, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const;
我建议使用GameplayTag
版本而不是FName
版本。这可以防止蓝图中的拼写错误。
4.5.10 Gameplay Effect Context
GameplayEffectContext
结构体持有关于GameplayEffectSpec's
发起者和TargetData
的信息。这也是一个很好的结构,用于继承以传递任意数据,如ModifierMagnitudeCalculations
/ GameplayEffectExecutionCalculations
、AttributeSets
和GameplayCues
。
要继承GameplayEffectContext
:
- 继承
FGameplayEffectContext
- 重写
FGameplayEffectContext::GetScriptStruct()
- 重写
FGameplayEffectContext::Duplicate()
- 如果您的数据需要同步,重写
FGameplayEffectContext::NetSerialize()
- 对子结构体实现
TStructOpsTypeTraits
,如父结构FGameplayEffectContext
具有的那样 - 在您的
AbilitySystemGlobals
类中重写AllocGameplayEffectContext()
,并返回新的子结构体对象
GASShooter使用继承的GameplayEffectContext
来添加TargetData
,这可以在GameplayCues
中访问,特别是对于霰弹枪,因为它可以击中多个敌人。
4.5.11 Modifier Magnitude Calculation(修饰符强度计算)
ModifierMagnitudeCalculations
(简称ModMagCalc
或MMC
)是强大的类,用于GameplayEffects
中的Modifiers
。它们的功能类似于GameplayEffectExecutionCalculations
,但最重要的是它们可以被预测。它们的唯一目的是从CalculateBaseMagnitude_Implementation()
返回浮点值。您可以在蓝图中子类化和覆盖此函数。
MMCs
可以用于任何持续时间的GameplayEffects
- Instant
、Duration
、Infinite
或Periodic
。
MMCs'
强度在于它们捕获任何数量的Attributes
的能力,这些Attributes
在Source
或Target
上,完全访问GameplayEffectSpec
以读取GameplayTags
和SetByCallers
。Attributes
可以快照或不快照。快照Attributes
在创建GameplayEffectSpec
时捕获,非快照Attributes
在应用时捕获并自动更新,当Attribute
更改时,对于Infinite
和Duration
类型的GameplayEffects
。捕获Attributes
会重新计算它们的CurrentValue
,从ASC
上的现有修改器中。此重新计算将不运行PreAttributeChange()
在AbilitySet
中,因此任何clamping必须在此再次完成。
Snapshot (快照) | Source 或Target | 在GameplayEffectSpec 中捕获 | 当Attribute 变为Infinite GE 或Duration GE 自动更新 |
---|---|---|---|
是 | Source | 创建 | 否 |
是 | Target | 应用 | 否 |
不 | Source | 应用 | 是 |
不 | Target | 应用 | 是 |
从MMC
获得的浮点值可以进一步在GameplayEffect's
Modifier
中修改,通过系数和前系数添加。
一个示例MMC
捕获目标的mana Attribute
,并将其从中毒效果中减少,该效果取决于目标有多少mana,以及目标可能具有的标签:
UPAMMC_PoisonMana::UPAMMC_PoisonMana()
{
//标头 FGameplayEffectAttributeCaptureDefinition ManaDef 中定义的ManaDef;
ManaDef.AttributeToCapture = UPAAttributeSetBase::GetManaAttribute();
ManaDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;
ManaDef.bSnapshot = false;
//MaxManaDef 在标头 FGameplayEffectAttributeCaptureDefinition MaxManaDef 中定义;
MaxManaDef.AttributeToCapture = UPAAttributeSetBase::GetMaxManaAttribute();
MaxManaDef.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;
MaxManaDef.bSnapshot = false;
RelevantAttributesToCapture.Add(ManaDef);
RelevantAttributesToCapture.Add(MaxManaDef);
}
float UPAMMC_PoisonMana::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec & Spec) const
{
// 收集源和目标的标签,因为这会影响应该使用哪些增益
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
float Mana = 0.f;
GetCapturedAttributeMagnitude(ManaDef, Spec, EvaluationParameters, Mana);
Mana = FMath::Max<float>(Mana, 0.0f);
float MaxMana = 0.f;
GetCapturedAttributeMagnitude(MaxManaDef, Spec, EvaluationParameters, MaxMana);
MaxMana = FMath::Max<float>(MaxMana, 1.0f); // Avoid divide by zero
float Reduction = -20.0f;
if (Mana / MaxMana > 0.5f)
{
// Double the effect if the target has more than half their mana
Reduction *= 2;
}
if (TargetTags->HasTagExact(FGameplayTag::RequestGameplayTag(FName("Status.WeakToPoisonMana"))))
{
// Double the effect if the target is weak to PoisonMana
Reduction *= 2;
}
return Reduction;
}
如果您不在RelevantAttributesToCapture
中添加FGameplayEffectAttributeCaptureDefinition
到RelevantAttributesToCapture
,然后尝试捕获Attributes
,您将收到缺少Spec的错误,如果您不需要捕获Attributes
,则不需要添加任何内容。
4.5.12 Gameplay Effect Execution Calculation(游戏玩法效果执行计算)
GameplayEffectExecutionCalculations
(简称ExecutionCalculation
、Execution
(您将在插件的源代码中经常看到此术语)或ExecCalc
)是GameplayEffects
对ASC
进行更改的最强大方式。与ModifierMagnitudeCalculations
类似,这些可以捕获Attributes
并可选地捕获它们。与MMCs
不同,这些可以更改多个Attribute
并基本上执行任何其他程序想要执行的操作。此功能的强大和灵活性的代价是它们无法被预测并且必须用C++实现。
ExecutionCalculations
只能用于Instant
和Periodic
类型的GameplayEffects
。任何带有’Execute’的单词通常指的是这两种类型。
快照捕获Attribute
,当GameplayEffectSpec
被创建时,而不是在应用时捕获它们。捕获Attributes
会重新计算它们的CurrentValue
,从ASC
上的现有修改器中。此重新计算将不运行PreAttributeChange()
在AbilitySet
中,因此任何clamping必须在此再次完成。
快照 | 源或目标 | 在GameplayEffectSpec 中捕获 |
---|---|---|
是 | 源 | 创建 |
是 | 目标 | 应用 |
不 | 源 | 应用 |
不 | 目标 | 应用 |
要设置Attribute
捕获,我们遵循Epic的ActionRPG示例项目设置的模式,定义一个结构来捕获和定义我们如何捕获Attributes
,并在结构构造函数中创建一个副本。您将有一个结构,如下所示,用于每个ExecCalc
。**注意:**每个结构需要一个唯一的名称,因为它们共享相同的命名空间。使用相同的名称会导致捕获Attributes
时出现错误行为(主要是捕获错误的Attributes
)。
对于Local Predicted
、Server Only
和Server Initiated
类型的GameplayAbilities
,ExecCalc
仅在服务器上调用。
计算伤害接收基于复杂公式从许多属性在Source
和Target
上读取伤害的示例是最常见的示例。包括示例项目有一个简单的ExecCalc
,用于计算伤害,从GameplayEffectSpec's
SetByCaller
读取伤害值,然后根据从Target
捕获的armor Attribute
来减少该值。请参阅GDDamageExecCalculation.cpp/.h
。
4.5.12.1 发送数据到Execution Calculations
除了捕获Attributes
,还有几种方法可以将数据发送到ExecutionCalculation
.
4.5.12.1.1 SetByCaller(调用者设置)
任何在GameplayEffectSpec
上设置的SetByCallers
都可以直接在ExecutionCalculation
中读取。
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
float Damage = FMath::Max<float>(Spec.GetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.Damage")), false, -1.0f), 0.0f);
4.5.12.1.2 Backing数据Attribute计算Modifier(Backing Data Attribute Calculation Modifier)
如果您想硬编码值到GameplayEffect
中,您可以使用CalculationModifier
,该CalculationModifier
使用一个捕获的Attributes
作为后备数据。
在此截图示例中,我们将50添加到捕获的Damage Attribute
。您也可以将其设置为Override
,以仅接受硬编码值。
ExecutionCalculation
在捕获Attribute
时读取此值。
float Damage = 0.0f;
// Capture optional damage value set on the damage GE as a CalculationModifier under the ExecutionCalculation
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageDef, EvaluationParameters, Damage);
4.5.12.1.3 Backing Data Temporary Variable Calculation Modifier(Backing数据临时变量计算Modifier)
如果您想硬编码值到GameplayEffect
中,您可以使用CalculationModifier
,该CalculationModifier
使用Temporary Variable
或Transient Aggregator
作为它被调用的C++中的Temporary Variable
。
在此截图示例中,我们使用Data.Damage
GameplayTag
将50添加到Temporary Variable
。
将后备Temporary Variables
添加到ExecutionCalculation's
构造函数:
ValidTransientAggregatorIdentifiers.AddTag(FGameplayTag::RequestGameplayTag("Data.Damage"));
ExecutionCalculation
使用特殊捕获函数类似Attribute
捕获函数读取此值。
float Damage = 0.0f;
ExecutionParams.AttemptCalculateTransientAggregatorMagnitude(FGameplayTag::RequestGameplayTag("Data.Damage"), EvaluationParameters, Damage);
4.5.12.1.4 Gameplay Effect Context
您可以通过自定义GameplayEffectContext
在GameplayEffectSpec
上将数据发送到ExecutionCalculation
。
在ExecutionCalculation
中,您可以从FGameplayEffectCustomExecutionParameters
访问EffectContext
。
const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
FGSGameplayEffectContext* ContextHandle = static_cast<FGSGameplayEffectContext*>(Spec.GetContext().Get());
如果您需要更改GameplayEffectSpec
或EffectContext
上的某些内容:
FGameplayEffectSpec* MutableSpec = ExecutionParams.GetOwningSpecForPreExecuteMod();
FGSGameplayEffectContext* ContextHandle = static_cast<FGSGameplayEffectContext*>(MutableSpec->GetContext().Get());
使用谨慎,如果修改GameplayEffectSpec
在ExecutionCalculation
中。请参阅GetOwningSpecForPreExecuteMod()
的注释。
/** Non const access. Be careful with this, especially when modifying a spec after attribute capture. */
FGameplayEffectSpec* GetOwningSpecForPreExecuteMod() const;
4.5.13 Custom Application Requirement(自定义应用需求)
CustomApplicationRequirement
(简称CAR
)类为设计师提供了高级控制,以确定是否可以应用GameplayEffect
,而不是简单地检查GameplayEffect
上的GameplayTag
。这些可以在蓝图中通过覆盖CanApplyGameplayEffect()
来实现,在C++中通过覆盖CanApplyGameplayEffect_Implementation()
来实现。
使用CARs
的示例:
Target
需要具有一定数量的Attribute
Target
需要具有一定数量的GameplayEffect
堆叠
CARs
还可以执行更多高级操作,例如检查目标上是否存在此GameplayEffect
的实例,并更改现有实例的持续时间(如果CanApplyGameplayEffect()
返回false)。
4.5.14 Cost Gameplay Effect(消耗型 GameplayEffect)
GameplayAbilities
具有一个可选的GameplayEffect
,专门设计用于作为能力成本。成本是ASC
需要拥有的Attribute
数量,以便能够激活GameplayAbility
。如果GA
无法负担Cost GE
,则无法激活。此Cost GE
应为Instant
类型的GameplayEffect
,并具有一个或多个Modifiers
,这些Modifiers
会从Attributes
中减去。默认情况下,Cost GEs
旨在被预测,并且建议保持这种能力,不要使用ExecutionCalculations
。MMCs
是完全可接受的,并且鼓励用于复杂的成本计算。
在开始时,您将拥有一个唯一的Cost GE
,每个GA
具有成本。更高级的技术是重用一个Cost GE
来为多个GAs
服务,只需修改从Cost GE
创建的GameplayEffectSpec
,并使用GA
特定的数据(成本值在GA
上定义)。这只适用于Instanced
能力。
两种技术用于重用Cost GE
:
- **使用
MMC
。**这是最简单的方法。创建一个MMC
,该MMC
从GameplayAbility
实例中读取成本值,您可以从GameplayEffectSpec
获取该值。
float UPGMMC_HeroAbilityCost::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec & Spec) const
{
const UPGGameplayAbility* Ability = Cast<UPGGameplayAbility>(Spec.GetContext().GetAbilityInstance_NotReplicated());
if (!Ability)
{
return 0.0f;
}
return Ability->Cost.GetValueAtLevel(Ability->GetAbilityLevel());
}
在此示例中,成本值是一个FScalableFloat
,我将其添加到GameplayAbility
子类中。
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cost")
FScalableFloat Cost;
- **覆盖
UGameplayAbility::GetCostGameplayEffect()
。**覆盖此函数并在运行时创建GameplayEffect
,该GameplayEffect
读取成本值在GameplayAbility
上。
4.5.15 Cooldown Gameplay Effect
GameplayAbilities
具有一个可选的GameplayEffect
,专门设计用于作为能力冷却。冷却确定激活能力后可以再次激活的时间。如果GA
仍在冷却中,则无法激活。此Cooldown GE
应为Duration
类型的GameplayEffect
,并且没有Modifiers
,并且具有唯一的GameplayTag
,每个GameplayAbility
或每个能力槽(如果您的游戏具有可互换的能力分配到槽中,这些槽共享冷却)在GameplayEffect's
GrantedTags
中(“Cooldown Tag
”)。GA
实际上检查Cooldown Tag
的存在,而不是Cooldown GE
的存在。默认情况下,Cooldown GEs
旨在被预测,并且建议保持这种能力,不要使用ExecutionCalculations
。MMCs
是完全可接受的,并且鼓励用于复杂的冷却计算。
在开始时,您将拥有一个唯一的Cooldown GE
,每个GA
具有冷却。更高级的技术是重用一个Cooldown GE
来为多个GAs
服务,只需修改从Cooldown GE
创建的GameplayEffectSpec
,并使用GA
特定的数据(持续时间和Cooldown Tag
在GA
上定义)。这只适用于Instanced
能力。
复用Cooldown GE
的两种方法:
- **使用
SetByCaller
。**这是最简单的方法。将Cooldown GE
的持续时间设置为SetByCaller
,并使用GameplayTag
在GameplayAbility
子类中定义一个浮点数/FScalableFloat
,一个FGameplayTagContainer
用于唯一的Cooldown Tag
,以及一个临时FGameplayTagContainer
,我们将使用作为返回指针的联合体,我们的Cooldown Tag
和Cooldown GE's
标签。
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cooldown")
FScalableFloat CooldownDuration;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cooldown")
FGameplayTagContainer CooldownTags;
// Temp container that we will return the pointer to in GetCooldownTags().
// This will be a union of our CooldownTags and the Cooldown GE's cooldown tags.
UPROPERTY(Transient)
FGameplayTagContainer TempCooldownTags;
然后重写UGameplayAbility::GetCooldownTags()
以返回我们的Cooldown Tags
和任何现有Cooldown GE
标签的联合体。
const FGameplayTagContainer * UPGGameplayAbility::GetCooldownTags() const
{
FGameplayTagContainer* MutableTags = const_cast<FGameplayTagContainer*>(&TempCooldownTags);
MutableTags->Reset(); // MutableTags writes to the TempCooldownTags on the CDO so clear it in case the ability cooldown tags change (moved to a different slot)
const FGameplayTagContainer* ParentTags = Super::GetCooldownTags();
if (ParentTags)
{
MutableTags->AppendTags(*ParentTags);
}
MutableTags->AppendTags(CooldownTags);
return MutableTags;
}
最后,重写UGameplayAbility::ApplyCooldown()
以注入我们的Cooldown Tags
到冷却GameplayEffectSpec
。
void UPGGameplayAbility::ApplyCooldown(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo * ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const
{
UGameplayEffect* CooldownGE = GetCooldownGameplayEffect();
if (CooldownGE)
{
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(CooldownGE->GetClass(), GetAbilityLevel());
SpecHandle.Data.Get()->DynamicGrantedTags.AppendTags(CooldownTags);
ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, SpecHandle);
}
}
在这张图中,冷却的持续时间 Modifier
被设置为 SetByCaller
,并带有一个 Data Tag
,名为 Data.Cooldown
。Data.Cooldown
在上面的代码中会是 OurSetByCallerTag
。
2.使用 MMC
。 这与上面的设置相同,唯一不同的是在 Cooldown GE
和 ApplyCooldown
中设置 SetByCaller
作为持续时间。相反,将持续时间设置为一个 Custom Calculation Class
,并指向我们将创建的新 MMC
。
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cooldown")
FScalableFloat CooldownDuration;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Cooldown")
FGameplayTagContainer CooldownTags;
// 临时容器,我们将在 GetCooldownTags() 中返回它的指针。
// 这将是我们 CooldownTags 和 Cooldown GE 的冷却标签的联合。
UPROPERTY(Transient)
FGameplayTagContainer TempCooldownTags;
然后重写 UGameplayAbility::GetCooldownTags()
,以返回我们 Cooldown Tags
和任何现有的 Cooldown GE
标签的联合。
const FGameplayTagContainer * UPGGameplayAbility::GetCooldownTags() const
{
FGameplayTagContainer* MutableTags = const_cast<FGameplayTagContainer*>(&TempCooldownTags);
MutableTags->Reset(); // MutableTags 写入 TempCooldownTags 的 CDO,因此在能力冷却标签发生变化时(例如移到不同的槽位)清空它
const FGameplayTagContainer* ParentTags = Super::GetCooldownTags();
if (ParentTags)
{
MutableTags->AppendTags(*ParentTags);
}
MutableTags->AppendTags(CooldownTags);
return MutableTags;
}
最后,重写 UGameplayAbility::ApplyCooldown()
,将我们的 Cooldown Tags
注入到冷却 GameplayEffectSpec
中。
void UPGGameplayAbility::ApplyCooldown(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo * ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) const
{
UGameplayEffect* CooldownGE = GetCooldownGameplayEffect();
if (CooldownGE)
{
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(CooldownGE->GetClass(), GetAbilityLevel());
SpecHandle.Data.Get()->DynamicGrantedTags.AppendTags(CooldownTags);
ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, SpecHandle);
}
}
float UPGMMC_HeroAbilityCooldown::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec & Spec) const
{
const UPGGameplayAbility* Ability = Cast<UPGGameplayAbility>(Spec.GetContext().GetAbilityInstance_NotReplicated());
if (!Ability)
{
return 0.0f;
}
return Ability->CooldownDuration.GetValueAtLevel(Ability->GetAbilityLevel());
}
4.5.15.1 获取 Cooldown GameplayEffect 的剩余时间
bool APGPlayerState::GetCooldownRemainingForTag(FGameplayTagContainer CooldownTags, float & TimeRemaining, float & CooldownDuration)
{
if (AbilitySystemComponent && CooldownTags.Num() > 0)
{
TimeRemaining = 0.f;
CooldownDuration = 0.f;
FGameplayEffectQuery const Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(CooldownTags);
TArray< TPair<float, float> > DurationAndTimeRemaining = AbilitySystemComponent->GetActiveEffectsTimeRemainingAndDuration(Query);
if (DurationAndTimeRemaining.Num() > 0)
{
int32 BestIdx = 0;
float LongestTime = DurationAndTimeRemaining[0].Key;
for (int32 Idx = 1; Idx < DurationAndTimeRemaining.Num(); ++Idx)
{
if (DurationAndTimeRemaining[Idx].Key > LongestTime)
{
LongestTime = DurationAndTimeRemaining[Idx].Key;
BestIdx = Idx;
}
}
TimeRemaining = DurationAndTimeRemaining[BestIdx].Key;
CooldownDuration = DurationAndTimeRemaining[BestIdx].Value;
return true;
}
}
return false;
}
**注意:**查询冷却的剩余时间在客户端上需要它们可以接收同步GameplayEffects
。这取决于它们的ASC
的 同步模式。
4.5.15.2 Listening for Cooldown Begin and End(监听冷却开始和结束)
要监听冷却开始,您可以响应Cooldown GE
被添加时,也可以响应Cooldown Tag
被添加时,我建议监听Cooldown GE
被添加,因为您也可以访问应用于GameplayEffectSpec
的GameplayEffectSpec
。从这里,您可以确定它是本地预测的Cooldown GE
还是服务器纠正的Cooldown GE
。
要监听冷却结束,您可以响应Cooldown GE
被移除时,也可以响应Cooldown Tag
被移除时。我建议监听Cooldown Tag
被移除,因为当服务器纠正的Cooldown GE
进来时,它会移除我们本地预测的Cooldown GE
,导致OnAnyGameplayEffectRemovedDelegate()
被触发,即使我们仍然在冷却中。Cooldown Tag
不会在本地预测的Cooldown GE
被移除和应用服务器纠正的Cooldown GE
时更改。
**注意:**监听GameplayEffect
添加或移除在客户端上需要它们可以接收同步GameplayEffects
。这取决于它们的ASC's
同步模式。
示例项目包括一个自定义蓝图节点,该节点监听冷却开始和结束。HUD UMG小部件使用它来更新Meteor冷却剩余时间。此AsyncTask
将永远存在,直到手动调用EndTask()
,我们在UMG小部件的Destruct
事件中这样做。请参阅AsyncTaskCooldownChanged.h/cpp
。
4.5.15.3 Predicting Cooldowns(预测冷却时间)
冷却无法真正被预测。我们可以开始UI冷却计时器,当本地预测的Cooldown GE
被应用时,但GameplayAbility's
实际冷却时间与服务器冷却剩余时间相关联。根据玩家的延迟,本地预测的冷却可能会在服务器上过期,但GameplayAbility
仍然在冷却中,这将防止GameplayAbility's
立即重新激活,直到服务器冷却过期。
示例项目通过在本地预测的Cooldown GE
开始时灰化Meteor能力UI图标,然后在服务器纠正的Cooldown GE
进来时开始冷却计时器来处理这种情况。
游戏后果是,具有高延迟的玩家在短冷却能力上的射击速度低于具有低延迟的玩家,这使他们在游戏中处于劣势。Fortnite通过他们的武器具有自定义的书籍来避免这种情况,这些武器不使用冷却GameplayEffects
。
允许真正的预测冷却(玩家可以在本地冷却过期后激活GameplayAbility
,但服务器仍在冷却中)是Epic希望在未来迭代中实现的东西。
4.5.16 更改已激活的 Gameplay Effect 的持续时间
要更改Cooldown GE
或任何Duration
类型的GameplayEffect
的剩余时间,我们需要更改GameplayEffectSpec's
Duration
,更新其StartServerWorldTime
,更新其CachedStartServerWorldTime
,更新其StartWorldTime
,并重新运行检查以查看持续时间。在服务器上执行此操作并标记FActiveGameplayEffect
脏,以将更改同步到客户端。**注意:**这涉及const_cast
,可能不是Epic希望更改持续时间的方式,但它似乎效果很好。
bool UPAAbilitySystemComponent::SetGameplayEffectDurationHandle(FActiveGameplayEffectHandle Handle, float NewDuration)
{
if (!Handle.IsValid())
{
return false;
}
const FActiveGameplayEffect* ActiveGameplayEffect = GetActiveGameplayEffect(Handle);
if (!ActiveGameplayEffect)
{
return false;
}
FActiveGameplayEffect* AGE = const_cast<FActiveGameplayEffect*>(ActiveGameplayEffect);
if (NewDuration > 0)
{
AGE->Spec.Duration = NewDuration;
}
else
{
AGE->Spec.Duration = 0.01f;
}
AGE->StartServerWorldTime = ActiveGameplayEffects.GetServerWorldTime();
AGE->CachedStartServerWorldTime = AGE->StartServerWorldTime;
AGE->StartWorldTime = ActiveGameplayEffects.GetWorldTime();
ActiveGameplayEffects.MarkItemDirty(*AGE);
ActiveGameplayEffects.CheckDuration(Handle);
AGE->EventSet.OnTimeChanged.Broadcast(AGE->Handle, AGE->StartWorldTime, AGE->GetDuration());
OnGameplayEffectDurationChange(*AGE);
return true;
}
4.5.17 在运行时创建动态 GameplayEffects
在运行时创建动态GameplayEffects
是一个高级主题。您不应该经常这样做。
只有Instant
类型的GameplayEffects
可以在运行时从零创建,因为当它们同步时,它们会寻找GameplayEffect
类定义,这些定义不存在。要实现此功能,您应该改为制作一个GameplayEffect
类原型,然后根据需要定制运行时GameplayEffectSpec
实例。
运行时创建的Instant
类型的GameplayEffects
也可以从本地预测中的本地预测GameplayAbility
调用。但是,尚不清楚动态创建是否会有副作用。
示例
示例项目创建一个用于将黄金和经验点返回给角色杀死者的GameplayEffect
。
// 创建一个动态的即时 Gameplay Effect 以给予奖励
UGameplayEffect* GEBounty = NewObject<UGameplayEffect>(GetTransientPackage(), FName(TEXT("Bounty")));
GEBounty->DurationPolicy = EGameplayEffectDurationType::Instant;
int32 Idx = GEBounty->Modifiers.Num();
GEBounty->Modifiers.SetNum(Idx + 2);
FGameplayModifierInfo& InfoXP = GEBounty->Modifiers[Idx];
InfoXP.ModifierMagnitude = FScalableFloat(GetXPBounty());
InfoXP.ModifierOp = EGameplayModOp::Additive;
InfoXP.Attribute = UGDAttributeSetBase::GetXPAttribute();
FGameplayModifierInfo& InfoGold = GEBounty->Modifiers[Idx + 1];
InfoGold.ModifierMagnitude = FScalableFloat(GetGoldBounty());
InfoGold.ModifierOp = EGameplayModOp::Additive;
InfoGold.Attribute = UGDAttributeSetBase::GetGoldAttribute();
Source->ApplyGameplayEffectToSelf(GEBounty, 1.0f, Source->MakeEffectContext());
第二个示例显示了运行时在本地预测GameplayAbility
中创建的GameplayEffect
。使用时要小心(请参阅代码中的注释!)!
UGameplayAbilityRuntimeGE::UGameplayAbilityRuntimeGE()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
}
void UGameplayAbilityRuntimeGE::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
}
// 在运行时创建 GE
UGameplayEffect* GameplayEffect = NewObject<UGameplayEffect>(GetTransientPackage(), TEXT("RuntimeInstantGE"));
GameplayEffect->DurationPolicy = EGameplayEffectDurationType::Instant; // Only instant works with runtime GE.
// 添加一个简单的可缩放浮动修改器,将 MyAttribute 修改为 42。
// 在实际应用中,可使用 TriggerEventData 中传递的信息。
const int32 Idx = GameplayEffect->Modifiers.Num();
GameplayEffect->Modifiers.SetNum(Idx + 1);
FGameplayModifierInfo& ModifierInfo = GameplayEffect->Modifiers[Idx];
ModifierInfo.Attribute.SetUProperty(UMyAttributeSet::GetMyModifiedAttribute());
ModifierInfo.ModifierMagnitude = FScalableFloat(42.f);
ModifierInfo.ModifierOp = EGameplayModOp::Override;
// 应用 GE
// 在这里创建 GESpec 以避免 ASC 创建基于 GE 类默认对象的 GESpec。
// 由于这里是动态 GE,这将创建一个带有基本 GameplayEffect 类的 GESpec,因此我们
// 将丢失修改器。请注意:目前尚不清楚这种 "hack" 是否会产生副作用!
// Spec 防止 GE 对象被垃圾回收,因为 GE 是 GESpec 上的 UPROPERTY。
FGameplayEffectSpec* GESpec = new FGameplayEffectSpec(GameplayEffect, {}, 0.f); // "new", since lifetime is managed by a shared ptr within the handle
ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, FGameplayEffectSpecHandle(GESpec));
}
EndAbility(Handle, ActorInfo, ActivationInfo, false, false);
}
4.5.18 Gameplay Effect Containers
Epic的Action RPG示例项目实现了一个称为FGameplayEffectContainer
的结构。这些不是vanilla GAS的一部分,但它们非常有用,用于包含GameplayEffects
和TargetData
。它自动化了一些工作,例如从GameplayEffects
创建GameplayEffectSpecs
并设置默认值在GameplayEffectContext
中。在GameplayAbility
中制作GameplayEffectContainer
并将其传递给生成的投射物非常容易和直接。我选择不在包含示例项目中实现GameplayEffectContainers
,以显示如何在没有它们的情况下工作,但强烈建议查看它们并考虑将它们添加到您的项目中。
要访问GESpecs
,请中断FGameplayEffectContainer
并访问GESpec
引用,方法是按索引访问数组中的GESpec
。这需要您提前知道GESpec
的索引。
GameplayEffectContainers
还包含一个可选的效率高的Targeting
容器方法。