4、GAS相关概念4.2 游戏标签(Gameplay Tags)

4.2 游戏标签(Gameplay Tags)

FGameplayTags 是一种层次化的名称,形式为 Parent.Child.Grandchild...,并注册在 GameplayTagManager 中。这些标签非常有用,可以用于分类和描述对象的状态。例如,如果一个角色被眩晕,我们可以在眩晕期间给它一个 State.Debuff.StunGameplayTag

你会发现自己会用 GameplayTags 来替代以前用布尔值或枚举处理的事情,并进行布尔逻辑以判断对象是否具有某些 GameplayTags

当给对象赋予标签时,我们通常将它们添加到对象的 ASC(如果有的话)中,以便GAS可以与它们交互。UAbilitySystemComponent 实现了 IGameplayTagAssetInterface,提供了访问其拥有的 GameplayTags 的功能。

多个 GameplayTags 可以存储在一个 FGameplayTagContainer 中。使用 GameplayTagContainer 优于使用 TArray<FGameplayTag>,因为 GameplayTagContainers 增加了一些效率上的魔法。虽然标签是标准的 FNames,但如果在项目设置中启用了 Fast Replication,它们可以被有效地打包在 FGameplayTagContainers 中进行复制。Fast Replication 要求服务器和客户端具有相同的 GameplayTags 列表。这通常不应该是个问题,所以你应该启用这个选项。GameplayTagContainers 还可以返回一个 TArray<FGameplayTag> 以供迭代。

存储在 FGameplayTagCountContainer 中的 GameplayTags 有一个 TagMap,用于存储该 GameplayTag 的实例数量。一个 FGameplayTagCountContainer 可能仍然有 GameplayTag,但其 TagMapCount 为零。如果一个 ASC 仍然有一个 GameplayTag,你可能会在调试时遇到这种情况。任何 HasTag()HasMatchingTag() 或类似的函数将检查 TagMapCount,如果 GameplayTag 不存在或其 TagMapCount 为零,则返回 false。

GameplayTags 必须提前在 DefaultGameplayTags.ini 中定义。虚幻引擎编辑器在项目设置中提供了一个界面,让开发者可以管理 GameplayTags,而无需手动编辑 DefaultGameplayTags.iniGameplayTag 编辑器可以创建、重命名、搜索引用和删除 GameplayTags

GameplayTag Editor in Project Settings

搜索 GameplayTag 引用将会在编辑器中显示熟悉的 Reference Viewer 图,显示所有引用该 GameplayTag 的资产。然而,这不会显示任何引用该 GameplayTag 的 C++ 类。

重命名 GameplayTags 会创建一个重定向,以便仍然引用原始 GameplayTag 的资产可以重定向到新的 GameplayTag。如果可能的话,我更喜欢创建一个新的 GameplayTag,手动更新所有引用到新的 GameplayTag,然后删除旧的 GameplayTag,以避免创建重定向。

除了 Fast ReplicationGameplayTag 编辑器还有一个选项,可以填充常用的复制 GameplayTags 以进一步优化它们。

如果 GameplayTags 是从 GameplayEffect 添加的,它们会被复制。ASC 允许你添加不被复制的 LooseGameplayTags,并且必须手动管理。示例项目使用 LooseGameplayTag 来表示 State.Dead,以便拥有的客户端可以立即响应其健康值降至零的情况。重生时手动将 TagMapCount 设置回零。只有在处理 LooseGameplayTags 时才手动调整 TagMapCount。最好使用 UAbilitySystemComponent::AddLooseGameplayTag()UAbilitySystemComponent::RemoveLooseGameplayTag() 函数,而不是手动调整 TagMapCount

在 C++ 中获取 GameplayTag 的引用:

FGameplayTag::RequestGameplayTag(FName("Your.GameplayTag.Name"))

对于高级的 GameplayTag 操作,如获取父或子 GameplayTags,请查看 GameplayTagManager 提供的功能。要访问 GameplayTagManager,请包含 GameplayTagManager.h 并使用 UGameplayTagManager::Get().FunctionName 调用它。GameplayTagManager 实际上将 GameplayTags 存储为关系节点(父、子等),以便比常量字符串操作和比较更快地处理。

GameplayTagsGameplayTagContainers 可以有可选的 UPROPERTY 指定符 Meta = (Categories = "GameplayCue"),在蓝图中过滤标签以仅显示具有父标签 GameplayCueGameplayTags。当你知道 GameplayTagGameplayTagContainer 变量应该仅用于 GameplayCues 时,这很有用。

另外,还有一个名为 FGameplayCueTag 的独立结构,封装了一个 FGameplayTag,并且在蓝图中自动过滤 GameplayTags,仅显示那些具有父标签 GameplayCue 的标签。

如果你想在函数中过滤 GameplayTag 参数,请使用 UFUNCTION 指定符 Meta = (GameplayTagFilter = "GameplayCue")。函数中的 GameplayTagContainer 参数不能被过滤。如果你想编辑引擎以允许这样做,请查看 SGameplayTagGraphPin::ParseDefaultValueData() 如何从 Engine\Plugins\Editor\GameplayTagsEditor\Source\GameplayTagsEditor\Private\SGameplayTagGraphPin.cpp 调用 FilterString = UGameplayTagsManager::Get().GetCategoriesMetaFromField(PinStructType); 并将 FilterString 传递给 SGameplayTagWidgetSGameplayTagGraphPin::GetListContent() 中。这些函数的 GameplayTagContainer 版本在 Engine\Plugins\Editor\GameplayTagsEditor\Source\GameplayTagsEditor\Private\SGameplayTagContainerGraphPin.cpp 中不检查元字段属性并传递过滤器。

示例项目广泛使用 GameplayTags

4.2.1 响应Gameplay Tags的变化

ASC 提供了一个委托,用于在 GameplayTags 被添加或移除时触发。它接受一个 EGameplayTagEventType,可以指定仅在 GameplayTag 被添加/移除时触发,或在 GameplayTagTagMapCount 发生任何变化时触发。

AbilitySystemComponent->RegisterGameplayTagEvent(FGameplayTag::RequestGameplayTag(FName("State.Debuff.Stun")), EGameplayTagEventType::NewOrRemoved).AddUObject(this, &AGDPlayerState::StunTagChanged);

回调函数有一个参数用于 GameplayTag 和新的 TagCount

virtual void StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount);

4.2.2 从插件下的 .ini 文件中加载Gameplay Tags

如果你创建了一个带有自己 .ini 文件的插件,其中包含 GameplayTags,你可以在插件的 StartupModule() 函数中加载该插件的 GameplayTag .ini 目录。

例如,这是虚幻引擎附带的 CommonConversation 插件的做法:

void FCommonConversationRuntimeModule::StartupModule()
{
	TSharedPtr<IPlugin> ThisPlugin = IPluginManager::Get().FindPlugin(TEXT("CommonConversation"));
	check(ThisPlugin.IsValid());
	
	UGameplayTagsManager::Get().AddTagIniSearchPath(ThisPlugin->GetBaseDir() / TEXT("Config") / TEXT("Tags"));
 
	//...
}

这将查找目录 Plugins\CommonConversation\Config\Tags,并在引擎启动时(如果插件已启用)将其中的任何 .ini 文件中的 GameplayTags 加载到你的项目中。