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
- 要另外設計需要的參數
| |
GameplayCue BP內填入對應的GameplayCueTag
如果專案內會大量使用GameplayCue, 可以用DataTable來管理GameplayCueTags
| |
GameplayCue執行的進入點
Input是從Server傳輸過來的參數
MyTarget就是AbilitySystemComp的Owner
Parameters則是一堆參數
為了優化網路傳輸, 用不到的參數可以不要傳給client
Rep的時機點也會根據不同duration policy有不同的執行結果
結論
可以進一步優化, 避免不需要的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)
- 無堆疊, 每一次的效果視為獨立
- 由來源合計
- 由目標合計
- Stack Limit Count : 堆疊數量, 我們專案比較常見的做法是把堆疊術設成1, 來避免一些效果太強的buff在堆疊時造成的不公平
- Stack Duration Refresh Policy : duration要不要重算
- Stack Period Reset Policy : period要不要重算
- Stack Expiration Policy : Stack內所有effect的時間是一起計算的, duration結束時提供3種處理方式
- 整個stack移除, 效果就全部結束
- 扣一後再跑一次duration, 效果會弱化
- 自己寫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
- 經過code trace的結果, 這個欄位應該不是給ImmunityQuery使用的
- 但是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架構, 來減少開發成本
写的不错,收藏了
回覆刪除