在UE4中,层次化标签系统(GameplayTag)大量使用。树形结构,可替代一些枚举、bool变量,甚至能当成事件来传递。

设计如下标签,Buff.Strong.Stun、Buff.Weak.Fire、Buff.Weak.Ice。标签Buff.Weak.Fire、Buff.Weak.Ice,MatchesTag(“Buff.Weak”) 都会返回true。那么就可以很轻松的实现Buff驱散,而不用新增其他变量,来处理这些问题。所以标签设计就尤为重要,尽量不要在项目中后期修改已有标签。

很多buff都可能会给角色施加Stun效果,通常可以考虑用一个int表示,buff创建时增加1,buff销毁时,减少1,GameplayTagCountConainer也是这样解决的,并且自带了事件系统。如果所有状态使用标记位来实现(int/long存储),那么多个buff源对状态的修改就要特别注意了。

实现

  1. GameplayTag是struct,只保存TagName的TagHash(int),作为Dictionary的Key是非常高效的。UE则是引擎FName直接实现的,也是hash字符串。
  2. GameplayTagManager管理所有GameplayTagNode以及TagName,可以运行时获取GameplayTag以及TagName。解析TagAsset文本后,用Dictionary保存在内存里,用树形结构管理层级。树形结构可以方便的回溯,很容易实现层级化引用。
  3. GameplayTagNode是class,被GameplayTagManager所管理,里面只存有GameplayTag和编辑器下才会有的注释信息,以及父节点和子节点引用。
  4. GameplayTagContainer,是GameplayTag容器,List实现,可序列化,提供多项匹配。内部会额外存储parent节点,以加速匹配。
  5. GameplayTagCountContainer,可以有效处理GameplayTag堆叠,并抛出对应的事件。内部维护两个Dictionary<GameplayTag,int>,分别处理显式GameplayTag,以及GameplayTag,举个例子,添加1个A.1,则显式GameplayTag只有A.1GameplayTag则有AA.1,标签A也会被隐式添加。
  6. GameplayTagAsset,序列化文件json。反序列化细节,使用 [UnityEditor.Callbacks.DidReloadScripts] 特性,来每次脚本编译后,重新反序列化一次TagAsset到内存里。也可以手动点击编辑器里的Load按钮,来刷新。(后面可以考虑监听文件变化,FileSystemWatcher or OnPostprocessAllAssets)。

匹配规则,取自ue4文档

Source Function/Operation Input Parameter Return Value
A.1 MatchesTag A true
A.1 MatchesTagExact (or ==) A false
A.1 MatchesAny {A, C} true
A.1 MatchesAnyExact {A, C} false
A.1 MatchesAll {A} true
A.1 MatchesAllExact {A} false
{A.1, B.1} HasTag A true
{A.1, B.1} HasTagExact A false
{A.1, B.1} HasAny {A, C} true
{A.1, B.1} HasAnyExact {A, C} false
{A.1, B.1} HasAll {A, B} true
{A.1, B.1} HasAllExact {A, B} false

编辑器支持

  1. GameplayTag可以直接编辑Json文本,也可以在Editor面板里直接编辑。
  2. Inspector面板可以直接选择对应的GameplayTag,Tag注释也会显示成Tooltip,并且Tag丢失时,会标红。

  1. 代码定义tag,生成配置

参考

UE4 GameplayTag