UE4 GameplayAbilitySystem - GameplayEffect & GameplayCue 如何設定參數


[前言] 

這篇文章主要是關於UE4 GameplayAbilitySystem中GameplayEffect的細項介紹
在專案內我們大量使用GameplayEffect的各種用法來達成不同的Gameplay
而GameplayEffect有一大堆的參數, 讓我們在使用上遇到不少問題
寫這篇文章就是希望其他開發者不用走一次我們走過的冤枉路


[簡介] 

先列出GameplayEffect可設定參數中的分類, 每個分類內在本文會有更詳細的介紹

GameplayEffect
  • Tags - Tag的新增與移除
  • Modifiers - 修改AttributeSet內的Value
  • Duration Policy - 不同的時間執行規則, 很多功能只有特定duration policy才會生效
  • Conditional Gameplay Effects - 額外apply的effect, 可以設定apply條件
  • Application - 這個效果能否套用的條件
  • Period - 週期執行
  • Display - 演出
  • Expiration - 結束後追加效果
  • Immunity - 這個效果套用時, 對其他效果免疫的設定
  • Stack - 同樣Effect的堆疊處理
  • Overflow - 堆疊溢出時追加效果
  • Grant Ability - "Give" Ability, 不會自動Activate
GameplayCue
  • GameplayCue Tag - GameplayEffect跟GameplayCue是透過GameplayTag關聯



[本文]

Tags
  • GameplayEffectAssetTag
    • 這個effect的tag, 在做一些remove, immunity之類的檢查欄位
  • GrantedTag
    • 當Effect apply後, 會同時賦予角色的tags
  • OngoingTag Requirements
    • 這個tag的玩法很像是遙控炸彈的概念, 先把炸彈的effect裝在某個對象上
      • 滿足OngoingTag的時候effect就會active( tag有變動的時候就會來checkOngoing
      • 不滿足的時候就會remove, 但是不會清掉, 只要再次滿足, 就又會active
    • 這個可能是節省網路傳輸的絕招
    • 根據實驗結果, 可以把會使用到多次的duration effect都改成infinite, 這樣ActiveGameplayEffects的傳輸就只會有一次!!
      • 然後透過tag的add/remove來達到effect的on/off
  • Application Tag Requirements
    • 能不能apply成功的tag檢查
  • Remove Gameplay Effects with Tags
    • 這個effect apply後可以同時remove其他effect


Modifiers
  • Attribute
    • 先決定針對哪個Attribute
    • 參考AttributeSet.h
      • 定義專案的角色會有哪些屬性
      • 可參考UE4官方的ActionRPG教學專案
  • Modifier Op
    • Enum共有五個
    • Add Multiply Divide三個都是數學基本運算
    • Override的使用通常會搭配Magnitude Calculation Type=Attribute Based
      • 也就是用一個attribute的數值去設定另一個attribute, 例如回滿血, hp = hpMax
    • Invalid沒有意義
  • Modifier Magnitude
    • 先說明一下, 所有"常數"都可以是直接填在Effect上的數字, 或是從表格讀值
    • Magnitude Calculation Type
      • Scalable Float
        • 常數
      • Attribute Based
        • 整個數值的公式是value = A*(X+B)+C
        • X是另一個Attribute的值
        • A B C都是常數
      • Custom Calculation Class
        • 這個需要自己寫class來做運算, 公式太複雜就可能要用上這個
      • Set by Caller
        • runtime的數值, 參考FGameplayEffectSpec::SetSetByCallerMagnitude

Duration Policy
  • 三種policy, 分別介紹三種的規則及應用範例
    • Instant
      • 單次效果
      • 只會在Server上執行, 沒有額外的網路傳輸
      • 例如子彈擊中傷害
    • Duration
      • 持續一段時間的效果, 解除效果時會復原attribute
      • Duration Mannitude : 持續時間, Policy選Duration才會顯示
      • 透過replicated property - ActiveGameplayEffect同步給所有client
        • 同步後可以得到效果還剩多久的資訊
      • 例如馬力歐賽車的吃蘑菇加速
    • Infinite
      • 不確定會持續多久的可以用這個, 可以配合remove的規則來做設計
      • 例如踩進Volume會得到持續效果, 離開volume就解除

Conditional Gameplay Effects
  • Executions
    • Calculation Class : 自訂執行與否的條件
  • Conditional Gameplay Effects
    • Requred Source Tags : 對象身上有這些tags, 就會執行額外的effect

Application
  • Chance to Apply to Target: 0.0-1.0 , 對應到百分比機率Application Requirement
    • bool CanApplyGameplayEffect(const UGameplayEffect* GameplayEffect, const FGameplayEffectSpec& Spec, UAbilitySystemComponent* ASC)
    • BP寫邏輯決定能否apply effect

Period
  • Period: 多久modify一次attribute
  • Execute Periodic Effect on Application
    • true: apply時就執行第一次效果
    • false: apply時不執行效果, 一個periodic time後才執行第一次效果

Display
  • 最重要就是GameplayCue設定
  • Require Modifier Success to Trigger Cues
    • 顧名思義就是有成功執行Modifier才會Trigger Cue
    • Modifier為0的情況下, 這個boolean就沒用了
    • 預設true: 可以避免gameplay不合理的cue傳輸
  • Suppress Stacking Cues
    • stack的第一個instance才會啟動Cue
    • 預設false: 改成true可以減少過多的cue傳輸, 堆疊時只啟動一個Cue
  • UIData
    • 空的UObject
    • 要另外設計需要的參數
  • GameplayCueTag來告訴client要call哪個GameplayCue Asset
  • Manitude Attribute如果有assign, GameplayCue裡就可以拿到
    (傷害數字顯示的效果)
  • 關於網路傳輸優化
    • 官方版GameplayCue是用Multicast unreliable RPC做溝通
    • 我們專案有實作一個Replicated property的版本, 減少傳輸的成本
GameplayCue BP內填入對應的GameplayCueTag

如果專案內會大量使用GameplayCue, 可以用DataTable來管理GameplayCueTags


GameplayCue執行的進入點



Input是從Server傳輸過來的參數

MyTarget就是AbilitySystemComp的Owner

Parameters則是一堆參數

為了優化網路傳輸, 用不到的參數可以不要傳給client


Rep的時機點也會根據不同duration policy有不同的執行結果

  • Instant
    • 一次OnExecute
  • Duration
    • 一次OnActive和一次WhileActive
      (這兩個幾乎同樣時間, 傳輸量表示....)
    • 結束時OnRemove
  • Duration with Period
    • Duration的calls
    • 每次attribute變化都會執行一次OnExecute <-傳輸量爆炸
結論
可以進一步優化, 避免不需要的GameplayCue傳輸

方法一
Effect內填四個bool, Server同步前先檢查EventType對應的是不是true

方法二
檢查GQ Class有沒有實作各EventType對應的function, 但是Server不一定有asset

方法三
建一個DataTable讓CueManager可以查詢
結構就是Key = GameplayCueTag, Value = bool *4
在EditorTime維護Table的內容, 每次對任一個GQ Asset進行編輯後, 就要更新這個Table的內容並上傳


UGameplayCueNotify_Static::HandleGameplayCue
{
...
switch (EventType)
{
    case EGameplayCueEvent::OnActive:
        OnActive(MyTarget, Parameters);
        break;
    case EGameplayCueEvent::WhileActive:
        WhileActive(MyTarget, Parameters);
        break;
    case EGameplayCueEvent::Executed:
        OnExecute(MyTarget, Parameters);
        break;
    case EGameplayCueEvent::Removed:
        OnRemove(MyTarget, Parameters);
        break;
};
}


Expiration
  • 只有duration跟infinite會有效
  • Premature Expiration Effect
    • 過早結束時會額外追加的效果
    • 甚麼叫過早? 外力介入
      • force remove / clear tags
      • 參考UAbilitySystemComponent::RemoveActiveGameplayEffect
  • Routine Expiration Effect
    • 正常結束時會額外追加的效果

Stack
  • 只有duration跟infinite會有效
  • Stacking Type(3 types)
    1. 無堆疊, 每一次的效果視為獨立
    2. 由來源合計
    3. 由目標合計
  • Stack Limit Count : 堆疊數量, 我們專案比較常見的做法是把堆疊術設成1, 來避免一些效果太強的buff在堆疊時造成的不公平
  • Stack Duration Refresh Policy : duration要不要重算
  • Stack Period Reset Policy : period要不要重算
  • Stack Expiration Policy : Stack內所有effect的時間是一起計算的, duration結束時提供3種處理方式
    1. 整個stack移除, 效果就全部結束
    2. 扣一後再跑一次duration, 效果會弱化
    3. 自己寫code處理, OnStackCountChange callback
Overflow (of Stack)
  • Overflow Effects : 堆疊滿了時會額外追加的效果
  • Deny Overflow Application : 造成堆疊超過的那次apply會fail
  • Clear Stack on Overflow : overflow後整個stack會清空

Immunity
  • 用途 : 這個effect在運作時可以阻擋其他effect的apply
UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf()
{
...
    // Are we currently immune to this? (ApplicationImmunity)
       const FActiveGameplayEffect* ImmunityGE=nullptr;
       if (ActiveGameplayEffects.HasApplicationImmunityToSpec(Spec, ImmunityGE))
       {
             OnImmunityBlockGameplayEffect(Spec, ImmunityGE);
             return FActiveGameplayEffectHandle();
       }
...
}

  • Granted Application Immunity Tags
    • Require Tags : 免疫tag effects
      • GE的tag是由GameplayEfectAssetTag欄位決定
    • Ignore Tags : 忽略免疫(台詞: 我免疫你的免疫)
      • 雖然滿足Require Tags, 但是如果這邊又有滿足其中一個ignore, 就忽略免疫
    • 這邊的tag運算規則是FGameplayTagRequirements::RequirementsMet
bool FGameplayTagRequirements::RequirementsMet(const FGameplayTagContainer&  Container) const
{
       bool HasRequired = Container.HasAll(RequireTags);
       bool HasIgnored = Container.HasAny(IgnoreTags);
       return HasRequired && !HasIgnored;
}
  • Granted Application Immunity Query : 進階的條件判斷, 把effect拆成更細的條件去做檢查

bool FGameplayEffectQuery::Matches
Query的關鍵就是Matches這個function

每個欄位都是會被檢查到的項目, 一堆的if
整個function可以想成是好幾關的面試過程
任一個項目沒有pass就return false, 全部都pass就return true
多個欄位同時填值時, 要注意優先度

面試順序與左圖中變數的順序一樣

    • Custom Match Delegate BP
      1. 經過code trace的結果, 這個欄位應該不是給ImmunityQuery使用的
      2. 但是Delegate的Input是ActiveGameplayEffect, BP unread, 所以沒改code的情況下, 這個delegate完全不能用

兩個可能的使用範例, 但是就像上面提到的第二點
BP中拿到ActiveGameplayEffect根本啥都看不到

沒辦法寫任何邏輯來決定Query是Match或not match

    • Owning Tag Query / Effect Tag Query / Source Tag Query : 三者就是比對effect內的不同tag container欄位, 比較需要知道的是Query如何設定


Query的設定基本上就是正規學過的樹狀結構

Expression = list of Tag Match Rules

Tag Match跟Express都有三種, 概念是一樣的
All Match : 所有都滿足才match
Any Match : 任一滿足就match
No Match : 不能有任一滿足才match

範例來了
只要滿足Expression 0或是Expression 1就match
ex0很簡單只有比對Damage的tag

ex1則是不能有heal跟overlap


結論
雖然是很彈性的架構, 但一般來說不會有這麼複雜的規則
在錯誤設定下, 可能產生因為矛盾而不可能match的expression

    • Modifying Attribute(Match Any)
      • GE內的Modifier只要任一個Attribute符合, 就免疫
      • 是以整個Effect為單位, 所以其他Attribute的Modifier也會一起受影響
    • Effect Soure
      • 檢查EffectContext的SoureObject
      • SourceObject的Type是UObject, 根據實作情形可以填不同的內容
        • 例如Pawn, Controller, PlayerState
        • 我們專案中是用Pawn
      • 而GameplayEffect中的SourceObject就是AbilitySpec的SourceObject接過來的值
if (Spec.GetEffectContext().GetSourceObject() != EffectSource)
{
        return false;
}
    • Effect Definition
      • 最簡單的填法, 直接填入免疫的GameplayEffect Class


Grant Ability
  • input id
    • 綁定input id來activate這個ability
  • removal policy
    • effect結束後要怎麼處理granted ability?
      • CancelAbilityImmediately
        • 媽媽: 馬上過來吃飯!
        • 馬上中止ability, remove ability
      • RemoveAbilityOnEnd
        • 媽媽: 存檔完過來吃飯
        • 如果ability沒有active, 就直接remove
        • 如果ability正在active, 則結束後會remove
      • DoNothing
        • 媽媽: 飯好了要吃自己拿
        • ability不會remove
[結尾]

雖然講了很多, 但很多用法我們專案也沒有實際使用
但可以看出UE4想要建立一套可在不同專案通用的Gameplay架構, 來減少開發成本

留言

張貼留言

這個網誌中的熱門文章

UE4 除錯技巧分享 (一)

UE4 GameplayAbilityTask介紹