GAS 文档阅读摘抄笔记

GAS 文档阅读摘抄笔记

Ability System Component

  • 所有期望使用GameplayAbility, 包含Attribute, 或者接受GameplayEffect的Actor都必须附加ASC.
  • ASC附加的Actor被引用作为该ASC的OwnerActor, 该ASC的物理代表Actor被称为AvatarActor. 比如MOBA游戏中玩家控制的英雄, 其中OwnerActor是PlayerState, AvatarActor是英雄的Character类.
  • 绝大多数Actor的ASC都附加在其自身, 如果你的Actor会重生并且重生时需要持久化Attribute或GameplayEffect(比如MOBA中的英雄), 那么ASC理想的位置就是PlayerState.
  • 如果ASC位于PlayerState, 那么你需要提高PlayerState的NetUpdateFrequency, 其默认是一个很低的值, 因此在客户端上发生Attribute和GameplayTag改变时会造成延迟或卡顿. 确保启用Adaptive Network Update Frequency(net.UseAdaptiveNetUpdateFrequency=1),之后对Actor的NetUpdateFrequency和MinNetUpdateFrequency的设置就可以自适应的改变复制频率。
  • 在ASC的子类里函数里使用ABILITYLIST_SCOPE_LOCK()来防止在它的作用域里移除列表里的Ability.
      //ABILITYLIST_SCOPE_LOCK会在栈里创建1个变量,构造函数里给ASC的AbilityScopeLockCount加1,表示增删锁的数量,析构函数里会减一表示释放锁。
      //所有的锁被完全释放后,会应用缓存的PendingAdds和PendingRemove
      ABILITYLIST_SCOPE_LOCK();
      for (FGameplayAbilitySpec& Spec : ActivatableAbilities.Items){...}
    

同步模式

ASC定义了三种不同的同步模式用于同步GameplayEffect, GameplayTag和GameplayCue: Full, Mixed和Minimal. Attribute由其AttributeSet同步.

同步模式 如何使用 描述
Full 单人 所有GameplayEffect都同步到客户端.
Mixed 多人, 玩家控制的Actor GameplayEffect只同步到其所属客户端, 只有GameplayTag和GameplayCue同步到所有客户端.
Minimal 多人, AI控制的Actor GameplayEffect从不同步到任何客户端, 只有GameplayTag和GameplayCue同步到所有客户端.
  • Mixed同步模式需要OwnerActor的Owner是Controller. PlayerState的Owner默认是Controller但是Character不是. 如果OwnerActor不是PlayerState时使用Mixed同步模式, 那么需要在OwnerActor中调用SetOwner()设置自己的Owner为Controller.

设置和初始化

  • ASC一般在OwnerActor的构建函数中创建并且需要明确标记为Replicated. 这必须在C++中完成.
  • OwnerActor和AvatarActor的ASC在服务端和客户端上均需初始化, 你应该在Pawn的Controller设置之后初始化(Possess之后), 单人游戏只需参考服务端的做法.
  • 对于玩家控制的Character且ASC位于Pawn, 我一般在服务端Pawn的PossessedBy()函数中初始化, 在客户端PlayerController的AcknowledgePossession()函数中获取Pawn的ASC来初始化.
  • 于玩家控制的Character且ASC位于PlayerState, 我一般在服务端Pawn的PossessedBy()函数中初始化, 在客户端PlayerController的OnRep_PlayerState()函数中初始化, 因为这里确保了PlayerState存在于客户端上。PlayerState的ASC初始化时,传入的OwnerActor和AvatarActor分别是this和Character,Character上的ASC初始化时,传入的2个参数都是this.

Gameplay Tags

  • FGameplayTag是由GameplayTagManager注册的形似Parent.Child.Grandchild...的层级类标签, 这些标签对于分类和描述对象的状态非常有用
  • 当给某个对象设置标签时, 如果它有ASC的话, 我们一般添加标签到ASC以与其交互.
  • 多个GameplayTag可被保存于一个FGameplayTagContainer中, 相比TArray,GameplayTagContainer做了一些很有效率的优化。
  • 应该在项目设置里开启GamePlayTag的快速复制功能。此外GameplayTag编辑器可以选择填写普遍需要同步的GameplayTag以对其深度优化.
  • 如果GameplayTag由GameplayEffect添加, 那么其就是可同步的. ASC允许你添加不可同步的LooseGameplayTag且必须手动管理.
  • 对于标签的访问和比较,应该使用UGameplayTagManager提供的方法,相比直接使用字符串比较,GameplayTagManager实际上使用关系节点(父, 子等等)保存GameplayTag以获得更快的处理速度.
  • UPROPERTY宏Meta = (Categories = "GameplayCue")用于在蓝图中过滤标签而只显示父标签为GameplayCue的GameplayTag
  • 如果你想过滤函数中的GameplayTag参数, 使用UFUNCTION宏Meta = (GameplayTagFilter = "GameplayCue")
  • 响应Tag的添加和删除: AbilitySystemComponent->RegisterGameplayTagEvent(FGameplayTag::RequestGameplayTag(FName("State.Debuff.Stun")), EGameplayTagEventType::NewOrRemoved).AddUObject(this, &AGDPlayerState::StunTagChanged);

Attribute

  • 如果某项数值是属于某个Actor且游戏相关的, 你就应该考虑使用Attribute
  • Attribute一般应该只能由GameplayEffect修改, 这样ASC才能预测(Predict)其改变
  • Attribute也可以由AttributeSet定义并存于其中. AttributeSet用于同步那些标记为replication的Attribute.
  • Attribute的类型FGameplayAttributeData由BaseValue和CurrentValue组成,CurrentValue用于记录当前值,BaseValue用于备份和恢复上一个值。
  • 在PreAttributeChange中处理对CurrentValue的修改以及取值范围问题, 在PostGameplayEffectExecute中处理GE对BaseValue的修改.
  • InstantGE可以永久性修改BaseValue,Duration&InfiniteGE可以修改CurrentValue,PeriodicGE被视为InstantGE并且可以修改BaseValue.
  • Meta Attribute是临时的、可被任意GE修改、不可同步的,将于任意Attribute交互的临时属性。 比如伤害值作为Meta Attribute占位符, 而不是使用GE直接修改生命值Attribute, 使用这种方法, 伤害值就可以在GameplayEffectExecutionCalculation中由buff和debuff修改, 并且可以在AttributeSet中进一步操作。
  • UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate可以为一个属性返回一个委托,这个委托会在属性值发生改变时广播。
  • 如果一个Attribute的值基于其他的Attribute,当其他属性变化,自己也自动变化,可以使用一个基于n个Attribute或MMC的InfiniteGE来让它自动推导.

    TestAttrA = (TestAttrA + TestAttrB) * ( 2 * TestAttrC)

AttributeSet

  • AttributeSet用于定义, 保存以及管理对Attribute的修改。开发者应该继承UAttributeSet. 在OwnerActor的构造函数中创建AttributeSet会自动注册到其ASC. 这必须在C++中完成.
  • 你可以使用多个AttributeSet来表示按需添加到Actor的Attribute分组, 例如, 你可以有一个生命相关的AttributeSet, 一个魔法相关的AttributeSet等等。
  • Attribute在内部被引用为AttributeSetClassName.AttributeName, 当你继承AttributeSet时, 所有父类的Attribute仍将保留父类名作为前缀.
  • ASC中不应该有多个同一类的AttributeSet,否则ASC会随机选择一个操作。
  • AttributeSet拥有一个Attribute, 并不意味着必须要使用它, 未使用的Attribute只占用很少的内存
  • AttributeSet可以在运行时从ASC上添加和移除,但是移除是有客户端奔溃的风险的。
  • 对于可共享的物品如武器携带的弹药、盔甲耐久等,有3种方案可控权衡。
    原文介绍
  • Attribute只能使用C++在AttributeSet头文件中定义。建议使用ATTRIBUTE_ACCESSORS(ClassName, PropertyName)宏定义(在示例项目里)。
  • AttributeSet的.cpp文件应该用预测系统(Prediction System)使用的GAMEPLAYATTRIBUTE_REPNOTIFY宏填充OnRep函数
  • 为了配合预测系统,让客户端的OnRep在属性相同的情况下(因为有预测)依旧回调,需要在GetLifetimeReplicatedProps做一下处理。
      void UGDAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
      {
        Super::GetLifetimeReplicatedProps(OutLifetimeProps);
            //REPTNOTIFY_Always用于设置OnRep函数在客户端值已经与服务端同步的值相同的情况下触发(因为有预测),
        DOREPLIFETIME_CONDITION_NOTIFY(UGDAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
      }
    
  • Attribute的BaseValue和CurrentValue的初始化建议使用InstantGE,当然也可以使用AttributeSet->InitHealth(100.0f);,InitXX方法由ATTRIBUTE_ACCESSORS定义。
  • PreAttributeChange用于在CurrentValue改变前修改查询Modifier时的返回值,这意味着像GameplayEffectExecutionCalculations和ModifierMagnitudeCalculations这种使用所有Modifier重新计算CurrentValue的函数需要再次执行值的限制操作。
  • PreAttributeChange不应该作为游戏逻辑的事件回调,而应该只做限制操作。游戏逻辑的事件应该使用UAbilitySystemComponent::GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute)返回的代理。
  • PostGameplayEffectExecute仅在InstantGE对Attribute的BaseValue修改之后触发, 当GameplayEffect对其修改时, 这就是一个处理更多Attribute操作的有效位置.
  • 当PostGameplayEffectExecute()被调用时, 对Attribute的修改已经发生, 但是还没有被同步回客户端, 客户端只会接收到限制后的值.
  • OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator)会在Aggregator为集合中的某个Attribute创建时触发。它允许FAggregatorEvaluateMetaData的自定义设置。
  • AggregatorEvaluateMetaData用于Aggregator基于所有应用的Modifier评估Attribute的CurrentValue。默认情况下, AggregatorEvaluateMetaData只由Aggregator用于确定哪些Modifier是满足条件的。
  • MostNegativeMod_AllPositiveMods为例, 其允许所有正(Positive)Modifier但是限制负(Negative)Modifier(除了最负的那一个),即只允许将最负移动速度减速效果应用到玩家, 不满足条件的Modifier仍存于ASC中, 只是不被总合进最终的CurrentValue, 一旦条件改变, 它们之后就可能满足条件, 就像如果最负Modifier过期后, 下一个最负Modifier(如果存在的话)就是满足条件的.
      void UGSAttributeSetBase::OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const
    {
      Super::OnAttributeAggregatorCreated(Attribute, NewAggregator);
      if (!NewAggregator) return;
    
      if (Attribute == GetMoveSpeedAttribute())
      {
            //设置AggregatorEvaluateMetaData
        NewAggregator->EvaluationMetaData = &FAggregatorEvaluateMetaDataLibrary::MostNegativeMod_AllPositiveMods;
      }
    }
    

Gameplay Effect

  • GE是GameplayAbility修改自己或他人Attribute和GameplayTag的容器。只是单纯的配置数据,使用时一般会创建很多的GE子类蓝图。
  • GE通过Modifier和Execution(GameplayEffectExecutionCalculation)修改Attribute.
  • GE有三种持续类型: 即刻(Instant), 持续(Duration)和无限(Infinite).
  • GE可以添加、移除、执行GameplayeCue.
类型 GameplayCue事件 何时使用
Instant Execute 对Attribute中BaseValue立即进行的永久性修改. 其不会应用GameplayTag, 哪怕是一帧.
Duration Add & Remove 对Attribute中CurrentValue的临时修改和当GameplayEffect过期或手动移除时, 应用将要被移除的GameplayTag. 持续时间是在UGameplayEffect类/蓝图中明确的.
Infinite Add & Remove 对Attribute中CurrentValue的临时修改和当GameplayEffect移除时, 应用将要被移除的GameplayTag. 该类型自身永不过期且必须由某个Ability或ASC手动移除.
  • PeriodGE修改Attribute的BaseValue和执行GameplayCue时就被视为InstantGE, 这种类型的Effect对于像随时间推移的持续伤害(damage over time, DOT)很有用. 注意: 周期性的Effect不能被预测.
  • GE一般不实例化, 而是从GameplayEffect的ClassDefaultObject创建一个GameplayEffectSpec,然后应用后被添加到ASC下名为ActiveGameplayEffects的特殊结构体容器.
  • GE可以被GameplayAbility和ASC中的多个函数应用, 其通常是ApplyGameplayEffectTo的形式,可以通过绑定AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf委托监听Duration&InfiniteGE。服务器上总会被回调,Autonomous Proxy只会在Full和Mixed同步模式下对于同步的GameplayEffect调用该函数, Simulated Proxy只会在Full同步模式下调用该函数。
  • GE可以被GameplayAbility和ASC中的多个函数移除, 其通常是RemoveActiveGameplayEffect的形式,课用过绑定AbilitySystemComponent->OnAnyGameplayEffectRemovedDelegate()的委托监听Duration&InfiniteGE。服务器上总会被回调,Autonomous Proxy只会在Full和Mixed同步模式下对于同步的GameplayEffect调用该函数, Simulated Proxy只会在Full同步模式下调用该函数。

Modifier

  • Modifier可以修改Attribute并且是唯一可以预测性修改Attribute的方法. 一个GE可以有0个或多个Modifier, 每个Modifier通过某个指定的操作只能修改一个Attribute.
  • Modifier可以指定4种操作:AddMultiplyDivideOverride。最后CurrentValue按照定义的总和公式:((InlineBaseValue + Additive) * Multiplicitive) / Division计算,Override会优先覆盖最后应用的Modifier得出的最终值。
  • 有四种类型的Modifier: ScalableFloat, AttributeBased, CustomCalculationClass, 和 SetByCaller, 它们全都生成一些浮点数, 用于之后基于各自的操作修改指定Modifier的Attribute.

Period GE的Period配置

  • Period: 周期性GE的周期(单位秒),表示多少秒应用一次
  • Execute Periodic Effect On Application: true/false, 表示在GE应用的瞬间就开始执行,或者等1个Period时间。
  • Periodic Inhibition Policy: 在该GE失效一段时间后(可能由于GameTag被免疫),如何重新计算周期。
    策略 描述
    Reset Period 把免疫结束的那一刻作为新的周期开始点,等待下个周期应用效果
    Execute and Reset Period 把免疫结束的那一刻作为新的周期开始点,并立即应用效果
    Never Reset 按原来规定的时间周期继续执行

Stacking 叠加

  • Stacking Type:多个GE应用到同一个Target的ASC上时,如何计算GE效果。
    类型 描述
    None GE不叠加,各自执行自己的计算
    Aggregated By Target GE的目标有1个stackingGE实例
    Aggregated By Source GE的每个来源都有1个stackingGE实例

以下的配置都是针对每个StackingGE实例的配置

  • Stack Limit Count: GE叠加数量的上限,GE开启叠加后只有1个GESpec实例,但是会具有数量(层数)。
  • Stack Duration Refresh Policy:叠加后,时长怎么处理:
    策略 描述
    RefreshOnSuccessfulApplication 按照最新的GE应用时间计算结束时间
    NeverRefresh 仍然按照第一个GE计算的结束时间
  • Stack Period Reset Policy: 叠加后,周期怎么处理:
    策略 描述
    ResetOnSuccessfulApplication 使用新叠加的GE重置周期
    NeverReset 任然按照第一个GE的周期
  • Stack Expiration Policy: 叠加后,结束时怎么处理:
    策略 描述
    ClearEntireStack 清除所有叠加数量
    RemoveSingleStackAndRefreshDuration 层数-1,刷新Duration继续直到层数变为0
    RefreshDuration 刷新Duration继续,相当于叠加的GE一直存在,主要用来在FActiveGameplayEffectsContainer::OnStackCountChange里手动处理层数和刷新时长

Gameplay Effect Tag

  • Gameplay Effect Asset Tags用于说明GE,并不是GE生效后给目标添加的标签。
  • GE是否能应用到目标,由Application Tag Requirements填写的标签是否满足决定。GE应用后可能是开启的,也可能是关闭的,这依赖于Ongoing Tag Requirements是否满足,应用期间如果依赖的标签不满足GE就会关闭,之后如果满足了,就会重新打开并重新应用它的Modifer.
  • GE应用或移除时给ASC添加或删除的标签由Granted Tags决定。
  • GE应用后,将会从该GE的Gameplay Effect Asset TagsGranted Tags中移除Remove Gameplay Effects with Tags中填写的标签。
  • GE的Immunity(免疫)基于GameplayTag实现,能有效组织其他GE的应用。尽管Application Tag Requirement也能从另一个角度达到此效果,但是使用Immunity可以提供UAbilitySystemComponent::OnImmunityBlockGameplayEffectDelegate委托。
  • 免疫下的GrantedApplicationImmunityTags会检查源ASC(包括源Ability的AbilityTag, 如果有的话)是否包含特定的标签, 这是一种基于确定Character或源的标签对其所有GE提供免疫的方法.
  • 免疫下的Granted Application Immunity Query, 会检查传入的GameplayEffectSpec是否与其查询条件相匹配, 从而阻止或允许其应用.

GameplayEffectSpec

  • GameplayEffectSpec基于GE在创建,并且可在应用前修改GE的等级、周期、时长等,还可以增加GE的动态Tag
  • GESpec有SetByCaller用于传递某个Ability内部生成的数值数据到GameplayEffectExecutionCalculations或ModifierMagnitudeCalculations.一般通过 FGameplayEffectSpec::SetSetByCallerMagnitude 、GetSetByCallerMagnitude使用,蓝图中提供了相应的方法。

GameplayEffectContext

GameplayEffectContext结构体存有关于GameplayEffectSpec创建者(Instigator)和TargetData的信息, 这也是一个很好的可继承结构体以在ModifierMagnitudeCalculation/GameplayEffectExecutionCalculation, AttributeSet和GameplayCue之间传递任意数据.

  • 可以继承UAbilitySystemGlobals然后重写FGameplayEffectContext* AllocGameplayEffectContext()返回你继承FGameplayEffectContext自定义的类实例。

Modifier Magnitude Calculation

MMC是在GameplayEffect中作为Modifier使用的强有力的类, 它的功能类似GameplayEffectExecutionCalculation但是要逊色一些, 最重要的是它是可预测的. 它唯一要做的就是自CalculateBaseMagnitude_Implementation()返回浮点值, 你可以在C++和蓝图中继承并重写该函数.

  • MMC里可以设置被捕获的attribute是否snapshot(快照),是否快照会影响要捕获的attribute的捕获时机(Attribute在GE创建或应用时捕获),同时选择snapshot的后该属性被Duration/Infinite的GE修改时,不会自动更新(这就是快照的含义)。
Snapshot 源(Source)或目标(Target) 在GameplayEffectSpec中捕获 Attribute被无限(Infinite)或持续(Duration)GameplayEffect修改时自动更新
Source 创建
Target 应用
Source 应用
Target 应用
  • 捕获Attribute会自ASC现有的Modifier重新计算它们的CurrentValue, 该重新计算不会执行AbilitySet中的PreAttributeChange(), 因此所有的限制操作(Clamp)必须在这里重新处理.

Execution Calculation

GameplayEffectExecutionCalculation(Execution)是GE对ASC进行修改最强有力的方式,与MMC一样,它也可以捕获Attribute并选择性地为其创建Snapshot, 和MMC不同的是, 它可以修改多个Attribute并且基本上可以处理程序员想要做的任何事. 这种强有力和灵活性的负面就是它是不可预测的且必须在C++中实现.

  • ExecutionCalculation只能由Instant和Periodic GE使用
快照 Source或Target 在GameplayEffectSpec中捕获
Source 创建
Target 应用
Source 应用
Target 应用
  • ExecCalc最普遍的应用场景是计算一个来自很多源(Source)和目标(Target)中Attribute伤害值的复杂公式.
  • 除了捕获Attribute,还有别的发送数据到ExecCalc的方法,比如使用:SetByCaller、Backing Data Attribute Calculation Modifier、 Backing Data Temporary Variable Calculation Modifier、GameplayEffectContext。

CustomApplicationRequirement

CustomApplicationRequirement(CAR)类为设计师提供对于GameplayEffect是否可以应用高阶控制, 而不是对GameplayEffect进行简单的GameplayTag检查.

Cost GE

  • 开始的时候, 你通常会为每个有花费的GA都设置一个独一无二的Cost GE, 一个更高阶的技巧是对多个GA复用一个Cost GE, 只需修改自GA的Cost GE创建的GameplayEffectSpec中指定的数据(花费值是在GA上定义的), 这只作用于实例化的Ability.
  • 复用CostGE有2种方法:1. 定义并使用MMC,在MMC里即时获取Ability的花费数值并计算。2. 重写UGameplayAbility::GetCostGameplayEffect(). 重写函数并在运行时即时的创建一个用来读取GameplayAbility中花费值的GameplayEffect.

Cooldown GE

  • CoolDown基于Ability是否包含CoolDown的GameplayTag来实现, 所以Cooldown GE应该是一个不带有Modifier的Duration GE。
  • 与Cost GE一样,CoolDown GE也可以复用
  • 复用Cost GE有2中方法: 1. 使用SetByCaller设置GE的Duration Magnitude. 2. 使用MMC设置GE的Duration Magnitude。
  • 使用AbilitySystemComponent::GetActiveEffectsTimeRemainingAndDuration(const FGameplayEffectQuery&)可以查询匹配传入的GameplayTag的GE剩余时间和总时长。注意在客户端上查询剩余冷却时间要求其可以接收同步的GE, 这依赖于它们ASC的同步模式.
  • ASC上可以监听GE的添加/移除,以及Tag的添加/移除。要监听冷却的开始结束,建议监听GE的添加来确认冷却的开始,监听Tag的移除来确认冷却的结束注意在客户端上监听某个GameplayEffect添加或移除要求其可以接收同步的GameplayEffect, 这依赖于它们ASC的同步模式.
  • 目前冷却时间不是真正可预测的.
  • CoolDown和其他DurationGE的持续时间可以被修改(修改示例见原文档)。

GameplayEffect Containers

  • Epic的Action RPG样例项目实现了一个名为FGameplayEffectContainer的结构体, 它不属于原生GAS, 但是对于包含GameplayEffect和TargetData极其好用

Gameplay Abilities

GameplayAbility(GA)是Actor在游戏中可以触发的一切行为和技能. 多个GameplayAbility可以在同一时刻激活, 例如奔跑和射击.

  • GA运行在所属(Owning)客户端还是服务端取决于网络执行策略(Net Execution Policy)而不是Simulated Proxy.
  • 不要使用Replication Policy. 这个名字会误导你并且你并不需要它. GameplayAbilitySpec默认会从服务端向所属(Owning)客户端同步, 上文提到过, GameplayAbility不会运行在Simulated Proxy上, 其使用AbilityTask和GameplayCue来replicate或者RPC视觉变化到Simulated Proxy.
  • Server Respects Remote Ability Cancellation(服务器承认客户端GA结束(取消或完成)),该选项一般禁用,这表示客户端的GA由于玩家取消或者自然完成时, 就会强制它的服务端版本结束而不管其是否完成
  • 建议禁用Replicate Input Directly,而是使用创建在已有输入相关的AbilityTask中的Generic Replicated Event(如果你的输入绑定在ASC)

绑定输入到ASC

  • 为了绑定输入到ASC, 你必须首先创建一个枚举来将输入事件名称转换为byte, 枚举名必须准确匹配****项目设置中用于输入事件的名称, DisplayName就无所谓了.
  • 除了分配的输入事件可以激活GA, ASC也接受设置外的ConfirmCancel输入, 这些特殊输入被AbilityTask用来确定或取消像Target Actor这样的对象.
  • 如果你的ASC位于Character, 那么就在SetupPlayerInputComponent()中包含用于绑定到ASC的函数.
  • 如果你的ASC位于PlayerState, SetupPlayerInputComponent()中有一个潜在的竞争情况就是PlayerState还没有同步到客户端, 因此, 建议同时在SetupPlayerInputComponent()和OnRep_PlayerState()中绑定输入,利用bool值可以防止多次绑定
  • 如果你不想你的GA在按键按下时自动激活, 但是仍想将它们绑定到输入以与AbilityTask一起使用, 你可以在UGameplayAbility子类中添加一个新的布尔变量, bActivateOnInput, 其默认值为true并重写UAbilitySystemComponent::AbilityLocalInputPressed

授予Ability

向ASC授予GA会将其添加到ASC的ActivatableAbilities列表, 从而允许其在满足GameplayTag需求时激活该GA.

  • 我们在服务端授予GA, 之后其会自动同步GameplayAbilitySpec到所属(Owning)客户端, 其他客户端/Simulated proxy不会接受到GameplayAbilitySpec.

激活Ability

如果某个GA被分配给了一个输入事件, 那么当输入按键按下并且它的GameplayTag需求满足时, 它将会自动激活, 这可能并非总是激活GA的期望方式。
ASC提供了另外4种方法来定义如何激活GA.

UFUNCTION(BlueprintCallable, Category = "Abilities")
bool TryActivateAbilitiesByTag(const FGameplayTagContainer& GameplayTagContainer, bool bAllowRemoteActivation = true);

UFUNCTION(BlueprintCallable, Category = "Abilities")
bool TryActivateAbilityByClass(TSubclassOf<UGameplayAbility> InAbilityToActivate, bool bAllowRemoteActivation = true);

bool TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation = true);

bool TriggerAbilityFromGameplayEvent(FGameplayAbilitySpecHandle AbilityToTrigger, FGameplayAbilityActorInfo* ActorInfo, FGameplayTag Tag, const FGameplayEventData* Payload, UAbilitySystemComponent& Component);

FGameplayAbilitySpecHandle GiveAbilityAndActivateOnce(const FGameplayAbilitySpec& AbilitySpec);
  • 当从蓝图中的Event激活GA时, 你必须使用ActivateAbilityFromEvent节点, 并且标准的ActivateAbility节点不能出现在图表中, 如果ActivateAbility节点存在, 它就会一直被调用不调用ActivateAbilityFromEvent节点.
  • 不要忘记应该在GameplayAbility终止时调用EndAbility(), 除非你的GameplayAbility是像被动技能那样一直运行的GameplayAbility.
  • 客户端调用TryActivateAbility后一直到ActivateAbility最终激活的过程中,如果是Local Predicted GA,过程中会调用CallServerTryActivateAbility,服务端收到CallServerTryActivateAbility后,和客户端一样开始从ServerTryActivateAbility开始执行,当调用到CanActivateAbility()后会根据返回ture/false,通知客户端是否激活成功(调用ClientActivateAbilitySucceed()/ClientActivateAbilityFailed()),客户端如果接收到服务端激活失败的消息,就会立即终止客户端的GA并撤销所有预测的修改.
  • 被动GA应该在OnAvatarSet激活,同时被动GA一般有一个Server Only的网络执行策略。

取消Ability

  • GA内部可以用CancelAbility()取消GA,他会调用EndAbility,然后标记WasCancelled = true;
  • 外部GAS提供了下列一些取消GA函数:
    /** Cancels the specified ability CDO. */
    void CancelAbility(UGameplayAbility* Ability);	
    
    /** Cancels the ability indicated by passed in spec handle. If handle is not found among reactivated     abilities nothing happens. */
    void CancelAbilityHandle(const FGameplayAbilitySpecHandle& AbilityHandle);
    
    /** Cancel all abilities with the specified tags. Will not cancel the Ignore instance */
    void CancelAbilities(const FGameplayTagContainer* WithTags=nullptr, const FGameplayTagContainer*   WithoutTags=nullptr, UGameplayAbility* Ignore=nullptr);
    
    /** Cancels all abilities regardless of tags. Will not cancel the ignore instance */
    void CancelAllAbilities(UGameplayAbility* Ignore=nullptr);
    
    /** Cancels all abilities and kills any remaining instanced abilities */
    virtual void DestroyActiveState();
    
  • CancelAllAbilities无法取消非实例化的GA,需要依靠CancelAbility

获取激活的Active

  • 你必须搜索ASC的ActivatableAbility列表(ASC拥有的已授予GameplayAbility)并找到一个与你正在寻找的资源或授予的GameplayTag相匹配的AbilitySpec.或者使用ASC提供的直接查询接口。
  • FGameplayAbilitySpec有一个方法IsActive()可以获取GA当前是否激活。

实例化策略

GameplayAbility的实例化策略决定了当GA激活时是否和如何实例化.

  • 分为:Instanced Per ActorInstanced Per ExecutionNon-InstancedInstanced Per Actor最常用,Non-Instanced性能最好,但是一般只会用在小兵的基础攻击等频繁使用的简单Ability上面。

网络执行策略

GA的网络执行策略(Net Execution Policy)决定了谁该以什么顺序激活该GA.单人游戏要使用Server Only

网络执行策略(Net Execution Policy) 描述
Local Only GameplayAbility只运行在所属(Owning)客户端. 这对那些只需做本地视觉变化的Ability很有用. 单人游戏应该使用Server Only.
Local Predicted Local Predicted GameplayAbility首先在所属(Owning)客户端激活, 之后在服务端激活. 服务端版本会纠正客户端预测的所有不正确的地方. 参见Prediction.
Server Only GameplayAbility只运行在服务端. 被动GameplayAbility一般是Server Only. 单人游戏应该使用该项.
Server Initiated Server Initiated GameplayAbility首先在服务端激活, 之后在所属(Owning)客户端激活. 我个人使用的不多(如果有的话).

Ability标签

GameplayAbility自带有内建逻辑的GameplayTagContainer. 这些GameplayTag都不进行同步.这是针对该GA的配置。

Game Ablity Spec

  • 当GameplayAbility在服务端授予时, 服务端会同步GameplayAbilitySpec到所属(Owning)客户端, 因此可以激活它.
  • 激活GameplayAbilitySpec会根据它的实例化策略(Instancing Policy)创建一个GameplayAbility实例(Non-Instanced GameplayAbility除外).

传递数据到Ability

  • 有4种方式可以传递数据到Ability(参照文档), 其中大部分未同步或保证及时同步。使用TargetData是一种在客户端和服务端之间传递任意数据的好方法.

Ability的Cost和Cooldown

GameplayAbility默认带有对可选Cost和Cooldown的功能.

  • Cost是指ASC激活GameplayAbility所必需的Attribute量,如果满足条件,则会应用Cost GE。CoolDown功能由Duration类型的Cooldown GE实现。
  • GA在Activate()之前,会进行UGameplayAbility::CheckCost()UGameplayAbility::CheckCooldown()的判断。
  • GA在Activate()之后,可以掉用UGameplayAbility::CommitAbility()提交Cost和Cooldown,他会调用CommitAbility()CommitCooldown()
  • 有前摇的GA在CommitAbility()之前,有可能被其他GA提前Commit了,此时CommitAbility()可能失败,然后结束GA。
  • 如果prediction key在提交时有效的话, 提交Cost和Cooldown是可以客户端预测的.

升级GA

升级GA有2种做法,区别是是否取消激活的GA

升级方法 描述
升级时取消授予和重新授予 自ASC取消授予(移除)GameplayAbility, 并在下一等级于服务端上重新授予. 如果此时GameplayAbility是激活的, 那么就会终止它.
增加GameplayAbilitySpec的等级 在服务端上找到GameplayAbilitySpec, 增加它的等级, 并将其标记为Dirty以同步到所属(Owning)客户端. 该方法不会在GameplayAbility激活时将其终止.

AbilitySet

  • GameplayAbilitySet是很方便的UDataAsset类, 其用于保存输入绑定和初始GameplayAbility列表, 该GameplayAbility列表用于拥有授予GameplayAbility逻辑的Character. 子类也可以包含额外的逻辑和属性

Ability批处理

GA的生命周期里,至少涉及3个客户端->服务端的RPC调用,CallServerTryActivateAbility(); ServerSetReplicatedTargetData()(可选); ServerEndAbility()

  • 这些RPC如果在同一帧同一个原子组里调用,就可以批处理为1个RPC调用。
  • ASC中批处理默认是禁止的,重写ShouldDoServerAbilityRPCBatch()打开。
  • 如果要批处理激活的GA,在激活GA前必须创建FScopedServerAbilityRPCBatcher变量,在它的作用域内,所有激活的AC才会尝试批处理。这个变量会拦截域内的RPC然后打包进一个批处理结构体内,在出域的时候,会把这个RPC给服务端。
  • UAbilitySystemComponent::ServerAbilityRPCBatch_Internal会接收发来的批处理结构体,是一个打断点查看批处理是否正确运行的好函数。

网络安全策略

  • GA可以设置它的NetSecurityPolicy网络安全策略(EditDefautlsOnly)用来说明客户端触发GA的执行和退出是否被服务器忽略。
    网络安全策略 描述
    ClientOrServer 没有安全需求. 客户端或服务端可以自由地触发该Ability的执行和终止.
    ServerOnlyExecution 客户端对该Ability请求的执行会被服务端忽略, 但客户端仍可以请求服务端取消或结束该Ability.
    ServerOnlyTermination 客户端对该Ability请求的取消或结束会被服务端忽略, 但客户端仍可以请求执行该Ability.
    ServerOnly 服务端控制该Ability的执行和终止, 客户端的任何请求都会被忽略.

Ability Task

GameplayAbility只能在1帧里执行,这本身不够灵活。所以我们用AbilityTask实现随时间触发或者经过一段时间后触发的委托操作。GAS提供了大量的UAbilityTask供使用。

  • 同时运行的AbilityTask数量最多不超过1000个。

自定义AbilityTask

  • UAbilityTask主要包括:
    1. 供蓝图调用的静态创建函数,里边创建并返回类的实例。
    2. 暴露给蓝图的使用各项委托(需要注意所有委托只能是同一种委托类型,不论是否有参数)。
    3. 进行主要工作的Activate()函数,会绑定委托。
    4. 其他
  • Ability不会运行在SimulatedProxy上,但是可以通过AbilityTask运行。为此需要设置bSimulatedTask=true,重写virtual void InitSimulatedTask
  • 设置bTickingTask = true后可以在TickTask()里拿到帧间隔做平滑处理。

使用AbilityTask

  • cpp里可使用静态创建函数创建,绑定代理回调后,调用Task->ReadyForActivation()激活。
  • 蓝图里使用AbilityTask后,K2Node_LatentGameplayTaskCall自动调用Task->ReadyForActivation().

Root Motion Source Ability

  • GAS自带的AbilityTask可以使用挂载在CharacterMovementComponent中的Root Motion Source随时间推移而移动Character, 像击退, 复杂跳跃, 吸引和猛冲.(4.25版本修复了已存的bug)

Gameplay Cue

定义

  • GameplayCue(GC)执行非游戏逻辑相关的功能, 像音效, 粒子效果, 镜头抖动等等. GameplayCue一般是可同步(除非在客户端明确执行, 添加和移除), 同时也是可预测的.
  • 我们可以在ASC中通过发送一个强制带有"GameplayCue"父名的相应GameplayTag,以及GameplayCueManager的事件类型(Execute, Add或Remove)来触发GameplayCue.
  • GameplayCueNotify对象和其他实现IGameplayCueInterface的Actor可以基于GameplayCueTag来订阅这些事件.
  • 有2个GameplayCueNotify的子类供使用:
    1. Instant和Periodic GE只有Execute事件,使用GameplayCueNotify_Static类订阅,适用于一次性效果(如打击效果)。
    2. Duration和Infinite GE有AddRemove事件,使用GameplayCueNotify_Actor类订阅,适用于循环的声音和粒子特效
  • GameplayCueNotify技术上可以响应任何事件, 但是这是我们一般使用它的方式.
  • 当使用GameplayCueNotify_Actor时, 要勾选Auto Destroy on Remove, 否则接下来对GameplayCueTag的添加(Add)调用将无法运行.
  • 当使用除了Full以外的同步模式时,AddRemove的GameplayCue事件会在监听服务器玩家上触发两次,1次是应用GE,一次是Server Multicast到客户端。而WhileActive事件仍会只触发一次。
  • 有些GC可以通过客户端触发来进行优化, 而不是通过GE同步。

触发

  • GE成功应用后,会自动触发GE里配置的GameplayCues(通过填入的GameplayCueTag寻找对应GC)。
  • GC编辑触发的GameplayTag的时候,还能设置IsOverride,标识该标签的父标签触发的GC是否递归向上触发。true表示不递归触发。
  • 手动触发:GA提供了接口添加和移除GC,这些接口暴露给了蓝图。ASC也在C++里提供了接口,你可以同在在子类暴露给蓝图。

客户端GameplayCue

GameplayAbilityASC中暴露的用于触发GameplayCue的函数默认是可同步的. 每个GameplayCue事件都是一个多播(Multicast)RPC. 这会导致大量RPC. GAS也强制在每次网络更新中最多能有两个相同的GameplayCueRPC. 我们可以通过使用client GameplayCue来避免这个问题.

  • 本地GC的触发一般通过UGameplayCueManager::HandleGameplayCue(...)触发GC的WhileActive、Remove...等事件。
  • GC遵循客户端添加客户端移除、服务器添加服务器移除的原则。

GameplayCueParameters

如果手动触发GC,你就必须填写传递GameplayCueParameters给GC。如果由GE触发,这个变量就会自动填充。

  • GameplayCueParameters包含大量数据可供传递给GC。

Gameplay Cue Manager

  • 游戏开始时GampelayCueManager会扫描游戏的全部目录以寻找GameplayCueNotify并加载进内存。可以修改DefaultGame.ini来修改默认的扫描路径。
      [/Script/GameplayAbilities.AbilitySystemGlobals]
      +GameplayCueNotifyPaths="/Game/GASShooter/Characters" # 项目路径Content/GASShooter/Characters
      +GameplayCueNotifyPaths="/Game/GASShooter/Weapons" # 项目路径Content/GASShooter/Weapons
    
  • 地图开始时默认会加载游戏里所有的GC和他们依赖的资源,这可能会导致无效的资源内存占用,增加潜在的游戏无响应几率,导致第一次GC触发时出现延迟。如果要解决这个问题,需要自己继承UGameplayCueManager重写ShouldAsyncLoadRuntimeObjectLibraries方法,并在配置里指定GCManager类。
//GSGameplayCueManager.h
virtual bool ShouldAsyncLoadRuntimeObjectLibraries() const override
{
	return false; //default is true
}
//DefaultGame.ini
[/Script/GameplayAbilities.AbilitySystemGlobals]
GlobalGameplayCueManagerClass="/Script/GASShooter.GSGameplayCueManager" // 项目路径Source/GasShooter/xxx/xxx/xxx/GSGameplayCueManager.h

阻止GameplayCue响应

  • 为了阻止GE上配置的GC执行,我们可以在GameplayEffectExecutionCalculations中调用OutExecutionOutput.MarkGameplayCuesHandledManually(), 之后手动发送我们的GameplayCue事件到Target或Source的ASC中.
  • 如果想要某个指定的ASC中的GameplayCue永不触发,可以设置AbilitySystemComponent->bSuppressGameplayCues = true;

GameplayCue的批处理

和GameplayAbility一样,每次GameplayCue触发都是一次不可靠的多播(NetMulticast)RPC. 在同一时刻触发多个GameplayCue的情况下, 有一些优化方法来将它们压缩成一个RPC或者通过发送更少的数据来节省带宽。

  • 阻塞UGameplayCueManager::FlushPendingCues(),将排队的GC合并进自定义的结构体,手动RPC结构体到客户端。在客户端接收并解释结构体,然后执行对应的ClientGC。
  • 通过设置AbilitySystem.AlwaysConvertGESpecToGCParams = true来将GameplayEffectSpec转换为FGameplayCueParameter结构体并且RPC它,而不是整个FGameplayEffectSpecForRPC, 这会节省带宽但是只有较少的信息。

Ability System Globals

AbilitySystemGlobals类保存有关GAS的全局信息. 大多数变量可以在DefaultGame.ini中设置. 一般你不需要和该类互动, 但是应该知道它的存在. 如果你需要继承像GameplayCueManagerGameplayEffectContext这样的对象, 就必须通过AbilitySystemGlobals来做.

  • 想要继承AbilitySystemGlobals, 需要在DefaultGame.ini中设置类名:
    [/Script/GameplayAbilities.AbilitySystemGlobals]
    AbilitySystemGlobalsClassName="/Script/ParagonAssets.PAAbilitySystemGlobals"
    
  • 继承AbilitySystemGlobals后,你需要调用一次UAbilitySystemGlobals::InitGlobalData()来使用TargetData, 否则你会遇到关于ScriptStructCache的错误, 并且客户端会从服务端断开连接。将其放到UEngineSubsystem::Initialize()是个好位置。

预测 Prediction

GAS的预测是在服务器许可前客户端激活GA和应用GE,服务器拒绝后客户端回滚以匹配服务器。

  • 有些是可以“预测”的,有些不能预测,比如:GE的移除,GE的周期效果。
  • Epic的理念是预测“不受伤害”的事情。

Prediction Key

GAS的预测体系建立在Prediction Key上面,它是客户端激活GA时生成的Integer标识符。

客户端 数据同步 服务端
激活、P-Key
CallServerTryActivateAbility P-Key --> receive P-Key
add P-Key to all applying GE(c) add P-Key to all applying GE(s)
P-Key out scope
receive GE(s) using P-key <-- GE(s) sync GE(s)
receive P-key(stale) <-- P-Key sync back P-Key
rm GE(c) using P-Key(stale),keep GE(s)
all GE(c) not using P-Key(stale) is wrong

Scoped Prediction Window

FScopedPredictionWindow是一个“窗口”,在构造时窗口打开,同时生成新的预测Key,在析构时窗口关闭。在此期间,FPredictionKey UAbilitySystemComponent::ScopedPredictionKey是有效Key,可以用来预测。

预测性的生成Actor

在客户端预测性地生成Actor是一项高级技术. GAS对此没有提供开箱即用的功能(SpawnActor AbilityTask只在服务端生成Actor)。

  • 如果要生成的Actor只是装饰、不影响游戏逻辑。可以限制服务器同步到所属客户端,所属(Owning)客户端会拥有其本地生成的版本, 而服务端和其他客户端会拥有服务端同步的版本.
bool APAReplicatedActorExceptOwner::IsNetRelevantFor(const AActor * RealViewer, const AActor * ViewTarget, const FVector & SrcLocation) const
{
	return !IsOwnedBy(ViewTarget);
}
  • 如果生成的Actor影响了游戏逻辑, 像投掷物就需要预测伤害值, 那么你需要本文档范围之外的高级知识, 在Epic Games的Github上查看UnrealTournament是如何生成投掷物的, 它有一个只在所属(Owning)客户端生成且与服务端同步的投掷物.

Targeting

Target Data

FGameplayAbilityTargetData是用于通过网络传输定位数据的通用结构体,一般包括AAcotr/UObject的引用、FHitResult、和其他通用的Location/Direciton/OriginData。

  • 本质上你可以继承它以增添想要的任何数据, 其可以简单理解为在客户端服务端GameplayAbility传递数据
  • TargetData一般由Target Actor或者手动创建, 供AbilityTask使用, 或者GE通过EffectContext使用. 因为其位于EffectContext中, 所以Execution, MMC, GameplayCue和AttributeSet的后端函数可以访问该TargetData.
  • 我们一般不直接使用FGameplayAbilityTargetData,而是FGameplayAbilityTargetDataHandle.

Target Actor

AGameplayAbilityTargetActor用于展示正在定位的目标。比如AGameplayAbilityTargetActor_GroundTrace用地面贴花表示陨石技能的伤害区域效果。

  • TargetActor默认是不可同步的. 当然这是可以修改的。
  • 在TargetActor和WaitTargetData AbilityTask中存在大量委托, TargetActor响应输入来产生广播TargetData的准备, 确认或者取消委托, WaitTargetData监听TargetActor的TargetData的准备、确认和取消委托, 并将该信息转发回GameplayAbility和服务端

Target Data Filter

同时使用Make GameplayTargetDataFilter和Make Filter Handle节点, 你可以过滤玩家的Pawn或者只选择某个特定类.

Gameplay Effect Container Targeting

它提供了一个可选的产生TargetData的高效方法. 当EffectContainer在客户端和服务端上应用时, 定位会立即进行。不支持用户输入, 无需确认即可立即进行,** 不能取消, 并且不能从客户端向服务端发送数据(在两者上产生数据), 它对即时射线检测和碰撞**很有用。

posted @ 2022-10-24 23:53  GameSprite  阅读(1607)  评论(0编辑  收藏  举报