5. 常见实现的Abilities
和Effects
5.1 Stun(眩晕)
当我们实现眩晕效果时,通常需要:
- 取消角色当前所有激活的
GameplayAbilities
- 阻止新的
GameplayAbility
激活 - 阻止角色移动
示例项目中的 Meteor(陨石)
GameplayAbility
在击中目标时应用眩晕效果。
具体实现方式:
- 当眩晕效果被添加到目标时,调用
AbilitySystemComponent->CancelAbilities()
取消目标当前所有激活的GameplayAbilities
- 将眩晕效果的
GameplayTag
添加到GameplayAbility
的Activation Blocked Tags
中,以阻止新的GameplayAbility
激活 - 重写
CharacterMovementComponent
的GetMaxSpeed()
函数,当拥有者带有眩晕GameplayTag
时返回 0 来阻止移动
5.2 Sprint(冲刺)
示例项目展示了如何实现冲刺功能 - 按住左Shift键时跑得更快。
具体实现:
-
更快的移动速度由
CharacterMovementComponent
预测性处理,通过网络向服务器发送flag
。详见GDCharacterMovementComponent.h/cpp
-
GameplayAbility
负责响应左Shift
输入,告诉CharacterMovementComponent
开始和停止冲刺,并在按住左Shift
时预测性地(Predictively
)消耗耐力值。详见GA_Sprint_BP
5.3 Aim Down Sights(瞄准)
示例项目中的瞄准实现方式与冲刺类似,但是是降低移动速度而不是提高。
-
预测性降低移动速度的实现见
GDCharacterMovementComponent.h/cpp
-
输入处理的实现见
GA_AimDownSight_BP
,瞄准不消耗耐力值
5.4 Lifesteal(生命偷取)
生命偷取在伤害 ExecutionCalculation
中处理。GameplayEffect
会带有一个类似 Effect.CanLifesteal
的 GameplayTag
。ExecutionCalculation
检查 GameplayEffectSpec
是否有这个 Tag
,如果存在,就创建一个动态的 Instant GameplayEffect
,设置要恢复的生命值作为Modifier
(修改器),并将其应用到来源的 ASC
。
if (SpecAssetTags.HasTag(FGameplayTag::RequestGameplayTag(FName("Effect.Damage.CanLifesteal"))))
{
float Lifesteal = Damage * LifestealPercent;
UGameplayEffect* GELifesteal = NewObject<UGameplayEffect>(GetTransientPackage(), FName(TEXT("Lifesteal")));
GELifesteal->DurationPolicy = EGameplayEffectDurationType::Instant;
int32 Idx = GELifesteal->Modifiers.Num();
GELifesteal->Modifiers.SetNum(Idx + 1);
FGameplayModifierInfo& Info = GELifesteal->Modifiers[Idx];
Info.ModifierMagnitude = FScalableFloat(Lifesteal);
Info.ModifierOp = EGameplayModOp::Additive;
Info.Attribute = UPAAttributeSetBase::GetHealthAttribute();
SourceAbilitySystemComponent->ApplyGameplayEffectToSelf(GELifesteal, 1.0f, SourceAbilitySystemComponent->MakeEffectContext());
}
5.5 Generating a Random Number on Client and Server(在客户端和服务器上生成随机数)
有时在 GameplayAbility
中需要生成随机数
,比如用于子弹后坐力或扩散。客户端和服务器都需要生成相同的随机数
。为此,我们必须在 GameplayAbility
激活时设置相同的随机种子
。
有两种设置种子的方法:
-
使用激活预测键(
Activation Prediction Key
) -
通过事件负载在激活
GameplayAbility
时发送种子
随机种子设置方法 | 描述 |
---|---|
使用Activation Prediction Key | GameplayAbility 的Activation Prediction Key 是一个确保同步并且在客户端和服务端的Activation() 中都可用的int16 类型值。你可以在客户端和服务端中设置其作为随机数种子。该方法的缺点是每次游戏开始时Prediction Key 总是从0开始并且会在生成key 之后持续增加,这意味着每场游戏都有着及其相同的随机数序列,这可能满足或不满足你的随机数需要。 |
激活GameplayAbility 时通过事件负载(Event Payload )发送种子 | 通过事件激活GameplayAbility 并通过可同步的事件负载(Event Payload)从客户端发送随机生成的种子到服务端, 这允许更高的随机性, 但是客户端也容易被破解而每次只发送相同的种子。通过事件激活GameplayAbility 也会阻止其从用户绑定激活。 |
如果您的随机偏差较小,大多数玩家不会注意到每场比赛中的序列是相同的,因此使用激活预测键作为 random seed
应该适合您。如果您做的是更复杂的任务,并且需要防止黑客篡改,也许使用 Server Initiated
的 GameplayAbility
更为合适,在这种情况下,服务器可以生成预测键或生成随机种子并通过事件负载发送。
5.6 Critical Hits(暴击)
暴击判定在伤害 ExecutionCalculation
中处理。GameplayEffect
会带有一个类似 Effect.CanCrit
的 GameplayTag
。ExecutionCalculation
会检查 GameplayEffectSpec
是否带有这个 Tag
。如果存在,ExecutionCalculation
会根据暴击几率(从Source
捕获的Attribute
)生成随机数,如果触发成功则增加暴击伤害(同样是从Source
捕获的Attribute
)。
由于我们不预测伤害计算,所以不需要担心客户端和服务器之间的随机数同步问题,因为 ExecutionCalculation
只在服务器端运行。如果你尝试使用 MMC
来预测性地计算伤害,你就需要从 GameplayEffectSpec->GameplayEffectContext->GameplayAbilityInstance
获取随机种子的引用。
可以参考 GASShooter 中的爆头是如何实现的。原理类似,但不依赖随机数判定,而是检查 FHitResult
中的骨骼名称。
5.7 不叠加但保留最大效果值的 GameplayEffects (Non-Stacking Gameplay Effects but Only the Greatest Magnitude Actually Affects the Target)
在Paragon中,减速效果不叠加。每个减速实例正常应用并跟踪其生命周期,但只有最大减速效果实际影响Target
。GAS
通过 AggregatorEvaluateMetaData
提供了这种情况的解决方案。详见 AggregatorEvaluateMetaData()
。
5.8 游戏暂停时生成 Target Data
如果你需要在等待从玩家获取 TargetData
(通过 WaitTargetData AbilityTask
) 时暂停游戏,建议使用 slomo 0
而不是直接暂停。
5.9 One Button Interaction System(按钮交互系统)
GASShooter 实现了一个单键交互系统 - 玩家可以通过按住或点击’E’键与可交互物体互动,比如:
- 复活玩家
- 打开武器箱
- 打开或关闭滑动门