【UE5 C++课程系列笔记】12——Gameplay标签的基本使用
目录
概念
主要作用
1 组织与分类游戏元素
1.1 驱动游戏逻辑
1.2 便于查询与筛选
2 助力网络同步与优化
定义Gameplay标签
1 在项目设置中添加标签
2 从数据表资产导入标签
3 使用C++定义标签
3.1 UE_DECLARE_GAMEPLAY_TAG_EXTERN
3.2 UE_DEFINE_GAMEPLAY_TAG
3.3 UE_DEFINE_GAMEPLAY_TAG_COMMENT
3.4 UE_DEFINE_GAMEPLAY_TAG_STATIC
*3.5 完整步骤
使用Gameplay标签
1 将标签应用于对象
2 使用条件函数对标签求值
3 Gameplay标记查询
4 在C++中使用Gameplay标签
5 通过IGameplayTagAssetInterface访问Gameplay标签
概念
Gameplay标签(分层标签系统) 是用户定义的字符串,充当概念性的分层标签。你可以将Gameplay标签应用于项目中的对象,并对其求值以驱动你的Gameplay实现,类似于检查布尔值或标记。Gameplay标签本质上是一种有层级结构的文本标识,通常以 “.” 作为层级分隔符,形如 “Category.Subcategory.Item”,这样的结构方便进行分类管理以及灵活的查询和匹配操作。
主要作用
1 组织与分类游戏元素
游戏中存在大量不同种类的对象、状态、行为等,比如角色的各种技能(攻击技能、防御技能、辅助技能等)、不同类型的道具(武器、防具、消耗品等)、场景中的各种交互元素(可破坏物、可触发机关等)。通过为它们赋予相应的 Gameplay 标签,可以清晰地将这些元素按照不同的逻辑范畴进行归类。例如,可以给所有治疗技能贴上 “Skill.Healing” 标签,给近战武器贴上 “Item.Weapon.Melee” 标签,方便开发人员从整体上把握游戏内容的构成。
1.1 驱动游戏逻辑
条件判断依据:在编写游戏逻辑时,常常需要根据不同的条件来决定是否执行某些操作。Gameplay 标签就可以作为很好的判断条件。例如,在一个角色扮演游戏中,技能释放逻辑可能会检查角色是否具有 “Status.Buff.Strength”(力量增益状态)标签,若有,则某个特定的强力攻击技能可以被释放;或者检查目标是否带有 “Status.Debuff.Vulnerable”(易伤状态)标签,来决定伤害计算的系数等。
触发事件响应:可以基于 Gameplay 标签的添加、移除等变化来触发相应的事件。比如当角色进入 “State.Invisible”(隐身状态)这个标签所代表的状态时,游戏中的敌人 AI 可以监听该标签变化事件,进而调整搜索策略或者进入警戒状态等,实现不同游戏系统之间的协同和交互。
1.2 便于查询与筛选
精确查找:开发人员可以根据具体的标签精确地找到特定的游戏元素。比如在一个复杂的道具系统中,要找到所有带有 “Item.Consumable.HealthPotion”(消耗品 - 生命药水)标签的道具,以便进行批量处理,如更新其属性、调整其在商店中的售价等。
模糊筛选:凭借标签的层级结构,还能进行模糊筛选。假设想获取所有属于 “Item.Weapon”(武器类物品)的元素,无论它是近战、远程还是其他细分类型的武器,都可以通过查找以该父标签开头的所有标签对应的元素来实现,这种方式极大地增强了对游戏内容管理和操作的灵活性。
2 助力网络同步与优化
在网络游戏中,为了减少不必要的数据传输量、节省网络带宽以及降低服务器和客户端的运算负载,Gameplay 标签发挥着重要作用。可以规定只有带有特定关键 Gameplay 标签的游戏数据才需要在网络间进行同步。例如,对于场景中那些带有 “Object.NetworkSync”(网络同步对象)标签的物体,才会将其位置、状态等关键信息发送给客户端,而其他无关紧要的物体信息则无需同步,以此提高网络传输的效率。
定义Gameplay标签
1 在项目设置中添加标签
在“项目设置-》GameplayTags”中点击“新增Gameplay标签源”
这里命名为“Test.ini”
点击“管理Gameplay标签”
在打开的“Gameplay标签管理器”界面中点击加号来新增Gameplay标签
编辑标签的命名、注释如下,设置源为刚创建的“Test.ini”
编辑好后点击“添加新标签”
添加好后可以在项目目录中找到“Test.ini”
打开后内容如下
在“Gameplay标签管理器”中可以对Gameplay标签进行添加子标签、重命名、删除等操作。
2 从数据表资产导入标签
添加一个数据表格
行结构选择“GameplayTagTableRow”
这里命名为“DT_TableTag”
打开“DT_TableTag”,这里添加3行数据
对这3行数据的标签和注释进行编辑
在“项目设置-》GameplayTags”中,点击添加一个Gameplay标签列表元素
这里设置为刚创建的“DT_TableTag”
此时再次打开“Gameplay标签管理器”
可以看到“Gameplay标签管理器”界面中出现了在“DT_TableTag”中编辑的标签
3 使用C++定义标签
可以使用宏“UE_DECLARE_GAMEPLAY_TAG_EXTERN”、“UE_DEFINE_GAMEPLAY_TAG”、“UE_DEFINE_GAMEPLAY_TAG_COMMENT”、“UE_DEFINE_GAMEPLAY_TAG_STATIC”来定义Gameplay标签。
3.1 UE_DECLARE_GAMEPLAY_TAG_EXTERN
宏“UE_DECLARE_GAMEPLAY_TAG_EXTERN”用于在头文件中声明一个外部的 Gameplay 标签变量,通常在你想要在多个源文件中使用同一个 Gameplay 标签定义时使用。它只是声明了这个标签的存在,具体的定义会在其他源文件中通过对应的 UE_DEFINE_GAMEPLAY_TAG
等宏来完成,用法如下:
// 例如,在某个头文件(比如 MyTags.h)中声明一个外部的 Gameplay 标签
UE_DECLARE_GAMEPLAY_TAG_EXTERN(TagName) //TagName 是要声明的 Gameplay 标签的名称
3.2 UE_DEFINE_GAMEPLAY_TAG
用于定义一个 Gameplay 标签,将其注册到虚幻引擎的 Gameplay 标签系统中,使其能够在游戏中被识别和使用。它通常与 UE_DECLARE_GAMEPLAY_TAG_EXTERN
配合使用,先在头文件中声明,再在源文件中定义,不过也可以单独使用来直接定义一个标签,用法如下:
// 在对应的源文件(比如 MyTags.cpp)中定义之前声明的 Gameplay 标签
UE_DEFINE_GAMEPLAY_TAG(TagName, "My.Gameplay.Tag")
3.3 UE_DEFINE_GAMEPLAY_TAG_COMMENT
该宏除了可定义 Gameplay 标签外,还允许为该标签添加注释说明。这个注释可以帮助其他开发人员更好地理解这个标签所代表的含义、用途等信息,对于代码的可读性和维护性很有帮助,尤其是在项目中有大量 Gameplay 标签存在的情况下,用法如下:
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TagName, "My.Gameplay.Tag", "This is a comment explaining the tag's purpose.")
3.4 UE_DEFINE_GAMEPLAY_TAG_STATIC
该宏用于静态地定义一个 Gameplay 标签,它与 UE_DEFINE_GAMEPLAY_TAG
的主要区别在于其定义的标签具有静态存储特性,适用于一些特定的场景,比如当你希望这个标签只在某个特定的编译单元内有效,或者不想让它参与到一些动态的 Gameplay 标签管理过程中(例如避免被某些全局的标签查找、修改机制影响等),用法如下:
UE_DEFINE_GAMEPLAY_TAG_STATIC(TagName, "My.Gameplay.Tag")
*3.5 完整步骤
首先必须将 GameplayTags
模块添加到项目的 Build.cs
文件,才能在C++中访问Gameplay标签功能,如下所示:
新建一个C++类,这里命名为“TagType”,用于定义所有的Gamelay标签
再新建一个Actor类,这里命名为“TagActor”,用于使用Gamelay标签
在“TagType.h”中使用 NativeGameplayTags.h
继续声明Gameplay标签变量
在“TagType.cpp”中定义Gameplay 标签
在“TagActor.h”中导入“TagType.h”,注意要写在“#include "TagActor.generated.h"”之前
然后我们在“TagActor.cpp”中使用UE_DEFINE_GAMEPLAY_TAG_STATIC宏 定义一个 Gameplay 标签,如下:
编译后,在“Gameplay标签管理器”中可以看到添加的Gameplay标签如下:
使用Gameplay标签
1 将标签应用于对象
2 使用条件函数对标签求值
在“Gameplay标签管理器”中先添加新标签
接下来去使用Gameplay标签,这里通过关卡蓝图来测试使用。打开关卡蓝图,添加一个变量,这里命名为“TestTagContainer”
变量类型为“Gameplay标签容器”
首先通过“Has Tag”节点来判断“TestTagContainer”是否含有“Test.Two”标签,勾选“Exact Match”表示精确匹配。
判断 “TestTagContainer”是否同时含有“Test.One”和“Test.Three”标签
两个条件必需同时为false结果才是true
“Test.One”和“Test.Three”标签是否存在任意一个。只有当不存在“Test.Two”标签,不同时存在“Test.One”和“Test.Three”标签,并且“Test.One”和“Test.Three”标签至少存在一个的情况下,最终结果才为true。
设置 “TestTagContainer”默认值
运行测试,打印结果为true
3 Gameplay标记查询
Gameplay 标记查询主要通过FGameplayTagQuery
来实现,FGameplayTagQuery
是一种逻辑查询,用于针对FGameplayTagContainer
进行查询操作,查询成功则称 “匹配”。查询是逻辑表达式,可测试标签容器的交集属性或子表达式的匹配状态,具有递归性和表达性。
接下来使用“Gameplay标记查询”的方式实现:只有当不存在“Test.Two”标签,不同时存在“Test.One”和“Test.Three”标签,并且“Test.One”和“Test.Three”标签至少存在一个的情况下,最终结果才为true的逻辑。
首先添加“Does Container Match Tag Query” 节点来判断标签容器是否与标签查询匹配,若匹配则返回true。
编辑标签查询条件
根表达式设置为所有表达式匹配
添加第1个表达式为“任意标签匹配”,设置只要标签“Test.One”和“Test.Three”有一个匹配,则第1个表达式返回true
添加第2个表达式为“无表达式匹配”,设置不能同时存在“Test.One”和“Test.Three”标签,也不能存在“Test.Two”
测试一下,当“TestTagContainer”只包含标签“Test.One”或“Test.Three”时,运行结果为true,除此外,运行结果为false。
4 在C++中使用Gameplay标签
在3.5小节中我们已经使用C++定义了3个Gameplay标签,现在我们继续使用它。
首先在“TagActor.h”中定义一个Gameplay标签容器,这里命名为“MyTagContainer”
在“TagActor.cpp”中,让“MyTagContainer”在BeginPlay后添加一个标签
编译后,创建一个派生自“TagActor”的蓝图类“BP_TagActor”
将“BP_TagActor”拖入场景中,在运行前可以看到“MyTagContainer”不包含任何标签
当运行后可以看到“MyTagContainer”成功添加了标签“Mode.Idle”
接下来继续使用“MyTagContainer”做匹配查询,在如下代码中,我们判断“MyTagContainer”是否同时包含标签“Mode_Idle”和“Mode_Coding”,若包含则输出日志。
5 通过IGameplayTagAssetInterface访问Gameplay标签
IGameplayTagAssetInterface
接口主要用于使实现该接口的类能够方便地与 Gameplay
标签进行交互,提供了统一的方式来管理和获取与这些资产相关联的 Gameplay
标签信息。通过实现这个接口,不同类型的游戏资产可以融入到基于 Gameplay
标签构建的游戏逻辑体系中,例如方便地进行标签查询、根据标签来驱动相关行为等。
接下来开始使用IGameplayTagAssetInterface
接口。先在“TagActor.h”中引入#include "GameplayTagAssetInterface.h",然后让TagActor继承IGameplayTagAssetInterface
重写IGameplayTagAssetInterface
的GetOwnedGameplayTags方法
实现 GetOwnedGameplayTags方法如下,该方法将“MyTagContainer”中的所有 Gameplay 标签添加到“TagContainer”中
打开蓝图“BP_TagActor”,在事件开始运行后,通过“Get Owned Gameplay Tags”节点来获取资产上拥有的Gameplay标签,获取Gameplay标签后判断TagContainer是否同时包含“Mode.Coding”和“Mode.Idle”标签,延迟0.2s是因为蓝图的BeginPlay通常比其C++父类的BeginPlay要早执行。
可以看到打印结果为true
我们还可以通过“Get Debug String from Gameplay Tag Container”节点来打印资产上所有拥有的Gameplay标签的名称。