在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源对状态的修改就要特别注意了。
实现
- GameplayTag是struct,只保存TagName的TagHash(int),作为Dictionary的Key是非常高效的。UE则是引擎FName直接实现的,也是hash字符串。
- GameplayTagManager管理所有GameplayTagNode以及TagName,可以运行时获取GameplayTag以及TagName。解析TagAsset文本后,用Dictionary保存在内存里,用树形结构管理层级。树形结构可以方便的回溯,很容易实现层级化引用。
- GameplayTagNode是class,被GameplayTagManager所管理,里面只存有GameplayTag和编辑器下才会有的注释信息,以及父节点和子节点引用。
- GameplayTagContainer,是GameplayTag容器,List实现,可序列化,提供多项匹配。内部会额外存储parent节点,以加速匹配。
- GameplayTagCountContainer,可以有效处理GameplayTag堆叠,并抛出对应的事件。内部维护两个Dictionary<GameplayTag,int>,分别处理
显式GameplayTag
,以及GameplayTag
,举个例子,添加1个A.1
,则显式GameplayTag
只有A.1
,GameplayTag
则有A
和A.1
,标签A
也会被隐式添加。 - 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 |
编辑器支持
- GameplayTag可以直接编辑Json文本,也可以在Editor面板里直接编辑。
- Inspector面板可以直接选择对应的GameplayTag,Tag注释也会显示成Tooltip,并且Tag丢失时,会标红。
- 代码定义tag,生成配置
参考
UE4 GameplayTag