1176 字
6 分钟
GameplayTags——UE 的"类型安全的字符串"

一、GameplayTag 不是什么黑科技#

就是一个带层级结构的字符串,被引擎预注册、哈希化,比较时是整数比较(不是字符串比较):

GameplayTag: Ability.Sofia.RevealTwo
层级结构: Ability
└── Sofia
└── RevealTwo

引擎启动时,所有在 DefaultGameplayTags.ini 或 DataTable 中声明的 Tag 被加载到一个全局字典里,每个 Tag 获得一个唯一的整数 ID。运行时任何 TagA == TagB 的比较都是 O(1) 的整数比较。


二、为什么不用 Bool / Enum#

布尔值的死亡螺旋#

// 一个角色身上可能有这些状态
bool bIsStunned;
bool bIsDead;
bool bIsInvincible;
bool bIsInAir;
bool bIsUsingSkill;
bool bIsInteracting;
bool bIsInCutscene;
// ... 12 个 bool 后,没人知道 bIsStunned && !bIsDead && bIsInAir 是什么语义

布尔值的问题

  • 互斥关系靠 if 维护——哪天忘了加 if (!bIsDead),死了还能被眩晕
  • 组合查询困难——“角色是否有任何一个控制阻断的 debuff”需要 bIsStunned \|\| bIsFrozen \|\| bIsFeared \|\| ...

GameplayTag 替代#

// 不用 bool——用 Tag
ASC->AddLooseGameplayTag(Tag_Status_Stunned); // 眩晕
ASC->AddLooseGameplayTag(Tag_Status_Dead); // 死亡
// 查询:有任何一个 "控制阻断" 的 Tag 吗?
FGameplayTagContainer BlockingTags;
BlockingTags.AddTag(Tag_Status_Stunned);
BlockingTags.AddTag(Tag_Status_Frozen);
BlockingTags.AddTag(Tag_Status_Feared);
bool bBlocked = ASC->HasAnyMatchingGameplayTags(BlockingTags);
// 查询:有 Status.Dead 的所有子 Tag 吗?(包括 Status.Dead.Player, Status.Dead.NPC)
bool bDead = ASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag("Status.Dead"));

Tags 天然支持层级查询——HasTag(Status.Dead) 自动匹配 Status.Dead.PlayerStatus.Dead.NPC


三、Tag 的匹配模式#

请求 Tag: Status.Dead ← "有没有死亡的 Tag"
匹配: Status.Dead ✓ 精确匹配
Status.Dead.Player ✓ 子 Tag
Status.Alive ✗ 不同分支
Status ✗ 父 Tag(只向下匹配)

Tag 只在层级树上向下匹配,不向上。

TagQuery#

复杂的组合条件可以用 FGameplayTagQuery 表达:

// "角色没有死亡、且具有至少一个主动技能、且没有被沉默"
FGameplayTagQuery Query = FGameplayTagQuery::BuildQuery(
FGameplayTagQueryExpression()
.AllTagsMatch()
.AddTag(Tag_Status_Alive)
.AddAnyTag(Tag_Ability_Active)
.AddTag(FGameplayTagQueryExpression()
.NoTagsMatch()
.AddTag(Tag_Status_Silenced)
)
);
// 一个表达式搞定——不用 3 个 if

这个 Query 可以被序列化——存在 DataTable 里、存在 GE 配置里。策划可以在编辑器里配复杂的条件组合,不需要程序员写 if


四、Tags 作为系统间的通信协议#

Tags 最大的价值不是替代 Bool——是让不同的子系统通过 Tags 通信而互相不知道对方的存在

GAS 赋予 Tag → 动画系统监听到 Tag → 切动画
GAS 赋予 Tag → UI 监听到 Tag → 显示 Buff 图标
GAS 赋予 Tag → 输入系统监听到 Tag → 禁用移动输入
GAS 赋予 Tag → AI 监听到 Tag → 切换到被动行为

GAS 只知道”我给了这个角色一个 Status.Stunned 的 Tag”。收到 Tag 的系统各自决定自己的反应——动画系统说”播放眩晕动画”、输入系统说”禁用输入”、AI 说”停止当前行为”。各方对 GAS 无依赖,只依赖 Tags。


五、UE 中 Tags 的使用场景#

子系统用 Tags 做什么
GASAbility Tags、GE GrantedTags、ActivationBlockedTags、Attribute 查询
动画AnimBlueprint 中 HasTag 节点——“角色被眩晕了吗 → 切眩晕动画”
输入InputAction 绑定到 Tag——按 Q 键激活 Tag 为 Ability.Sofia.Reveal 的技能
AIBehavior Tree Decorator Blackboard Based Tag Query
UIGameplayTag 驱动的 UI 显示/隐藏——BuffIcon 在 Tag 存在时显示
物理碰撞通道的 Tag 过滤(Physical Material 上的 Tag)
DataTable行级别的 Tag 过滤——“这个道具只在 Tags 中包含 Season.Halloween 时加载”

Tags 已经渗透了 UE 几乎所有子系统——你现在写 UE 代码,不碰 Tags 几乎不可能。


六、Tags 的管理#

层级命名规范#

Status.Dead
Status.Stunned
Status.Rooted
Status.Silenced
Ability.Offensive.Fireball
Ability.Offensive.Lightning
Ability.Defensive.Shield
Ability.Passive.Swordsmanship
Event.Combat.Hit
Event.Combat.Kill
Event.Game.RoundStart
Event.Game.RoundEnd
Input.Skill.Q
Input.Skill.E
Input.Move.Jump

规范层级比技术实现更重要。 建议在项目初期就定好 Tag 命名规范——Category.SubCategory.SpecificName。Tag 多了后重构是很痛苦的(需要改所有引用的资产)。

DataTable 管理#

UE 推荐把所有 Tag 声明在 DataTable 里(DT_GameplayTags):

| Tag | Comment |
|------------------------------|------------------|
| Status.Stunned | 眩晕 |
| Status.Frozen | 冰冻 |
| Ability.Mage.Fireball | 法师火球技能 |
| GamePhase.Exploration | 探索阶段 |
| GamePhase.Combat | 战斗阶段 |

引擎启动时加载这个 DataTable → 注册所有 Tag 到 Global Dictionary。没有在 DataTable 里声明的 Tag 运行时也能用,但编辑器里不会有自动补全提示。


七、总结#

概念一句话
Tag 本质预注册的层级字符串 → 运行时整数 ID → O(1) 比较
层级匹配Status.Dead 自动匹配 Status.Dead.Player(向下查)
TagQuery复杂的 Tag 组合条件——可序列化,策划可配
系统间解耦GAS 写 Tag,动画/UI/输入/AI 各自监听——互不依赖
DataTable 管理统一声明 Tag 列表,编辑器有自动补全,后续重构可控

GameplayTag 是 UE Gameplay 架构的”结缔组织”——它把各个子系统缝合在一起,同时保持它们之间的松耦合。如果你在 UE 项目里写了超过 5 个 bool 来表达角色状态,停下来,用 Tags 代替。

GameplayTags——UE 的"类型安全的字符串"
https://www.m4doka.xyz/posts/ue/ue-5-gameplay-tags/
作者
m4doka
发布于
2026-08-10
许可协议
CC BY-NC-SA 4.0