关于 ue-gameplay-tags-messaging
面向 UE 5.8 开发的 Gameplay Tags 与解耦式游戏消息技能,指导开发者完成标签注册、匹配、查询、编辑器筛选和消息设计。
主要功能
- 01原生与配置文件标签定义
- 02标签容器匹配与层级语义
- 03构建和评估标签查询
- 04编辑器标签筛选配置
- 05解耦式游戏消息设计
适用场景
- 01在 C++ 模块中注册原生标签
- 02按标签层级检查游戏状态
- 03为设计人员配置多标签条件
- 04在系统间传递消息而不建立硬引用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-gameplay-tags-messaging安装后在 Claude Code 中输入/ue-gameplay-tags-messaging确认已加载。
附带文件references/gameplay-messaging.mdreferences/gameplay-tag-reference.md
详细介绍
UE Gameplay Tags 和消息传递
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;旧形式列在“已弃用 — 请勿使用”下。
Gameplay Tags 是内置运行时模块,而非插件:在 Build.cs 的 PublicDependencyModuleNames 中添加 "GameplayTags",并包含 GameplayTagContainer.h / NativeGameplayTags.h / GameplayTagsManager.h。本技能涵盖标签定义(原生标签和 ini)、FGameplayTag、FGameplayTagContainer、FGameplayTagQuery、UGameplayTagsManager、编辑器筛选和解耦式游戏消息传递,包括 AsyncMessageSystem 插件(在 5.8 中为实验性功能)。
上下文
如果 .agents/ue-project-context.md 存在,请阅读(模块名称、约定、已启用的插件、GAS/网络设置)。如果该文件不存在,请继续。
根据请求和代码库确定所属领域。只有当两种合理的解读会产生不同代码时,才提问。
| 请求涉及…… | 转到 |
|---|---|
| 在 C++ 中声明标签、原生标签宏、模块设置 | 原生标签定义 |
在 .ini 中声明标签、数据表、重命名标签 | Ini 和数据表标签源 |
| 测试标签、容器成员关系、父级与精确匹配 | 匹配语义 |
| 多标签条件、AND/OR/NOT、设计师编写的规则 | 标签查询 |
| Actor 暴露的标签、基于标签构建的门控和状态 | 标签驱动设计 |
| 通过网络复制标签、标签开销、热点路径 | 复制与性能 |
| 在系统间广播事件,而不建立硬引用 | 解耦式消息传递 |
标签选择器、Categories 筛选、每个团队的标签文件 | 编辑器体验与筛选 |
标签来源
请求标签前,必须先注册该标签。四种来源会汇入 UGameplayTagsManager 持有的全局字典:
| 来源 | 位置 | 用途 |
|---|---|---|
| 原生 | 模块 .cpp 中的 UE_DEFINE_GAMEPLAY_TAG* | 供 C++ 按名称比较的标签 |
| 默认 ini | Config/DefaultGameplayTags.ini、+GameplayTagList= | 由设计师编写的标签 |
| 额外 ini | Config/Tags/*.ini 和插件配置目录 | 按团队或插件划分的标签列表 |
| 数据表 | 在 GameplayTagTableList 中列出的 FGameplayTagTableRow 类型 UDataTable | 从电子表格批量导入 |
EGameplayTagSourceType(Native、DefaultTagList、TagList、RestrictedTagList、DataTable、Invalid)是在编辑器中用于命名这些来源的枚举。标签具有层级结构:注册 MyGame.State.Stunned 时,会隐式注册 MyGame.State 和 MyGame。
原生标签定义
在公共头文件中声明,并在同一模块的一个且仅一个 .cpp 文件中定义。这些宏会通过 static_assert 确保它们在 .cpp 文件中使用。
// MyGameplayTags.h
#pragma once
#include "NativeGameplayTags.h"
MYGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_MyGame_State_Stunned);
MYGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_MyGame_Damage_Fire);
MYGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_MyGame_Message_ScoreChanged);
// MyGameplayTags.cpp
#include "MyGameplayTags.h"
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_MyGame_State_Stunned, "MyGame.State.Stunned", "Character cannot act");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_MyGame_Damage_Fire, "MyGame.Damage.Fire", "Fire damage channel");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_MyGame_Message_ScoreChanged, "MyGame.Message.ScoreChanged", "Score changed broadcast");
| 宏 | 参数 | 效果 |
|---|---|---|
UE_DECLARE_GAMEPLAY_TAG_EXTERN | (TagName) | extern FNativeGameplayTag TagName;——放在头文件中,并在前面加上你的 _API 宏 |
UE_DEFINE_GAMEPLAY_TAG | (TagName, Tag) | 定义变量,开发者注释为空 |
UE_DEFINE_GAMEPLAY_TAG_COMMENT | (TagName, Tag, Comment) | 同上,并添加在编辑器中显示的开发者注释 |
UE_DEFINE_GAMEPLAY_TAG_STATIC | (TagName, Tag) | static——仅在该 .cpp 文件内可见,头文件中无需对应的声明 |
Tag 和 Comment 是不带前缀的字符串字面量:宏会自行用 TEXT() 包装 Comment。在其中写 TEXT("…") 会导致编译失败。
FNativeGameplayTag 在静态构造时注册,并在模块卸载时取消注册,因此插件中的标签会随插件一同干净地消失。它可隐式转换为 FGameplayTag,也提供 GetTag():
const FGameplayTag Stunned = TAG_MyGame_State_Stunned.GetTag();
if (OwnedTags.HasTag(TAG_MyGame_State_Stunned))
{
return;
}
单一定义规则: 每个二进制文件中,每个标签变量必须且只能有一个 UE_DEFINE_GAMEPLAY_TAG(或 _COMMENT / _STATIC)定义。两个模块定义相同的标签字符串没有问题(管理器会对注册进行引用计数),但同一变量的两次定义会导致链接错误,而在头文件中定义则会导致每个包含该文件的编译单元都出错。
若要注册名称仅在运行时才确定的标签,请直接调用管理器:
const FGameplayTag Runtime = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName(TEXT("MyGame.Generated.Slot01")), TEXT("Generated at startup"));
启动后调用 AddNativeGameplayTag 会使快速复制失效(参见复制与性能)。请通过 CallOrRegister_OnAddNativeTagsDelegate 注册,并在 CallOrRegister_OnDoneAddingNativeTagsDelegate 中运行使用标签的代码:
// MyGameModule.cpp — FDelegateHandle NativeTagsHandle; is a member of FMyGameModule.
#include "MyGameModule.h"
#include "GameplayTagsManager.h"
void FMyGameModule::StartupModule()
{
NativeTagsHandle = UGameplayTagsManager::CallOrRegister_OnDoneAddingNativeTagsDelegate(
FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FMyGameModule::OnTagsReady));
}
void FMyGameModule::ShutdownModule()
{
UGameplayTagsManager::UnregisterNativeTagDelegate(NativeTagsHandle);
}
如果注册已完成,CallOrRegister_OnDoneAddingNativeTagsDelegate 会立即运行委托,而 CallOrRegister_OnAddNativeTagsDelegate 会在当前模块加载结束时运行委托(GameplayTagsManager.h:419-427),因此晚些时候调用它们也不存在顺序隐患。
Ini 和数据表标签源
UGameplayTagsSettings 是派生自 UGameplayTagsList 的 UCLASS(config=GameplayTags, defaultconfig),因此“项目设置 → Gameplay Tags”会写入 Config/DefaultGameplayTags.ini:
[/Script/GameplayTags.GameplayTagsSettings]
ImportTagsFromConfig=True
FastReplication=False
NumBitsForContainerSize=6
NetIndexFirstBitSegment=16
+GameplayTagList=(Tag="MyGame.State.Stunned",DevComment="Character cannot act")
+GameplayTagList=(Tag="MyGame.Damage.Fire",DevComment="Fire damage channel")
+GameplayTagTableList=/Game/Data/DT_MyGameTags.DT_MyGameTags
+GameplayTagRedirects=(OldTagName="MyGame.State.Stun",NewTagName="MyGame.State.Stunned")
| 键 | 类型 | 含义 |
|---|---|---|
ImportTagsFromConfig | bool | Ini 定义标签的总开关;可通过 UGameplayTagsManager::ShouldImportTagsFromINI() 读取 |
GameplayTagList | TArray<FGameplayTagTableRow> | Tag(FName)和 DevComment(FString) |
GameplayTagTableList | TArray<FSoftObjectPath> | FGameplayTagTableRow 数据表,由 LoadGameplayTagTables 加载 |
GameplayTagRedirects | TArray<FGameplayTagRedirect> | OldTagName → NewTagName;继承自 UGameplayTagsList |
CategoryRemapping | TArray<FGameplayTagCategoryRemap> | 将引擎的 Categories 筛选器重新映射到项目类别 |
RestrictedConfigFiles | TArray<FRestrictedConfigInfo> | 仅允许指定所有者编辑的顶层标签 |
InvalidTagCharacters | FString | 标签名称中禁止使用的字符,换行符等除外 |
其他 Ini 源位于 Config/Tags/(会自动添加),也可以位于传给 UGameplayTagsManager::Get().AddTagIniSearchPath(RootDir) 的任意目录中;RemoveTagIniSearchPath 可将这些目录移除。Game Feature 插件的配置目录会通过此方式注册,因此只有在插件处于活动状态时,其中的标签才会出现。
重命名标签必须添加重定向。 FGameplayTagRedirect 的结构为 { OldTagName, NewTagName };序列化时使用的是 FGameplayTagRedirectors::Get().RedirectTag(OldName, OutTag)(引擎内部:此类未导出)。若没有重定向,所有引用旧名称的资源都会静默地加载为无效标签。
完整的 Ini 和数据表详情见 gameplay-tag-reference.md。
匹配语义
FGameplayTag 是一个 FName。容器会保留显式添加的标签,以及一个临时展开的父级列表,因此,考虑父级的检查是数组查找,而不是字符串解析。
| 调用 | 接收方 | 语义 | {"A.1"} 与 A 的比较 |
|---|---|---|---|
MatchesTag(TagToCheck) | FGameplayTag | 此标签或任一父级等于 TagToCheck | A.1.MatchesTag(A) → true;A.MatchesTag(A.1) → false |
MatchesTagExact(TagToCheck) | FGameplayTag | 名称相等 | A.1.MatchesTagExact(A) → false |
MatchesAny(ContainerToCheck) | FGameplayTag | 与容器中任意标签进行父级展开匹配 | A.1.MatchesAny({A,B}) → true |
MatchesAnyExact(ContainerToCheck) | FGameplayTag | 容器显式包含此标签 | A.1.MatchesAnyExact({A,B}) → false |
HasTag(TagToCheck) | FGameplayTagContainer | 显式列表或父级列表包含该标签 | {A.1}.HasTag(A) → true |
HasTagExact(TagToCheck) | FGameplayTagContainer | 仅检查显式列表 | {A.1}.HasTagExact(A) → false |
HasAny / HasAnyExact | FGameplayTagContainer | 至少有一个;参数为空时为 false | {A.1}.HasAny({A,B}) → true |
HasAll / HasAllExact | FGameplayTagContainer | 全部满足;参数为空时为 true | {A.1,B.1}.HasAll({A,B}) → true |
Filter(OtherContainer) | FGameplayTagContainer | 返回一个新容器,其中包含与 OtherContainer 中任意标签匹配的标签,并展开父级 | {A.1,C}.Filter({A}) → {A.1} |
FilterExact(OtherContainer) | FGameplayTagContainer | 真正的交集,名称必须完全相同 | {A.1,C}.FilterExact({A}) → {} |
对空参数的处理不对称是有意为之:“空集合中的任意项”结果为 false,“空集合中的所有项”结果为 true。以 RequiredTags.IsEmpty() || Owned.HasAll(RequiredTags) 编写的门控条件是多余的。
FGameplayTagContainer MakeStateTags(const FGameplayTagContainer& ExtraTags)
{
FGameplayTagContainer Owned;
Owned.AddTag(TAG_MyGame_State_Stunned); // checks uniqueness and refills parents
Owned.AddTagFast(TAG_MyGame_Damage_Fire); // no uniqueness check — only when the tag is known to be new
Owned.AppendTags(ExtraTags);
Owned.RemoveTag(TAG_MyGame_State_Stunned);
if (!Owned.IsEmpty())
{
UE_LOG(LogMyGame, Log, TEXT("%d tags, first %s, last %s: %s"),
Owned.Num(), *Owned.First().ToString(), *Owned.Last().ToString(), *Owned.ToStringSimple());
}
return Owned;
}
父级导航:FGameplayTag::RequestDirectParent() 对 x.y 返回 x;FGameplayTag::GetGameplayTagParents() 返回一个包含该标签及其所有父级的容器;FGameplayTagContainer::GetGameplayTagParents() 对整个容器执行相同操作。ParseParentTags(TArray<FGameplayTag>&) 可在不访问管理器的情况下完成此操作。
AddTagFast 会跳过 Contains 检查,但仍会填充父级;从已确认唯一的数据构建容器时使用它,切勿用它替代对用户输入调用的 AddTag。
标签查询
FGameplayTagQuery 是已编译的令牌流——求值开销低,构建开销高。只需构建一次并存储,即可多次求值。
// MyBurnRule.cpp
#include "GameplayTagContainer.h"
#include "MyGameplayTags.h"
bool ShouldBurn(const FGameplayTagContainer& OwnedTags)
{
FGameplayTagContainer Elements;
Elements.AddTag(TAG_MyGame_Damage_Fire);
// (any element tag) AND NOT Stunned
FGameplayTagQuery ComplexQuery = FGameplayTagQuery::BuildQuery(
FGameplayTagQueryExpression()
.AllExprMatch()
.AddExpr(FGameplayTagQueryExpression().AnyTagsMatch().AddTags(Elements))
.AddExpr(FGameplayTagQueryExpression().NoTagsMatch().AddTag(TAG_MyGame_State_Stunned)));
return ComplexQuery.Matches(OwnedTags);
}
快捷形式无需表达式树即可涵盖常见情况:
FGameplayTagContainer Blocking;
Blocking.AddTag(TAG_MyGame_State_Stunned);
const FGameplayTagQuery BlockedQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(Blocking);
FGameplayTagQuery 上的快捷方法:MakeQuery_MatchAnyTags、MakeQuery_MatchAllTags、MakeQuery_MatchNoTags、MakeQuery_ExactMatchAnyTags、MakeQuery_ExactMatchAllTags、MakeQuery_MatchTag。FGameplayTagQueryExpression 上的表达式动词:AnyTagsMatch、AllTagsMatch、NoTagsMatch、AnyTagsExactMatch、AllTagsExactMatch、AnyExprMatch、AllExprMatch、NoExprMatch,通过 AddTag、AddTags 和 AddExpr 提供数据。
可从任一侧求值:Query.Matches(Container) 或 Container.MatchesQuery(Query)。两者都会调用同一个求值器。
将查询公开为 UPROPERTY,设计人员无需额外操作即可使用查询编辑器:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Gating")
FGameplayTagQuery ActivationQuery;
ReplaceTagsFast(Tags) 和 ReplaceTagFast(Tag) 会替换缓存查询的标签字典,而无需重建令牌流;新容器的大小必须相同。
标签驱动设计
任何持有标签的对象都应通过 IGameplayTagAssetInterface 暴露这些标签,这样无关系统无需进行类型转换即可查询:
// MyTaggedActor.h
#pragma once
#include "GameFramework/Actor.h"
#include "GameplayTagAssetInterface.h"
#include "GameplayTagContainer.h"
#include "MyTaggedActor.generated.h"
UCLASS()
class MYGAME_API AMyTaggedActor : public AActor, public IGameplayTagAssetInterface
{
GENERATED_BODY()
public:
virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Tags", meta = (Categories = "MyGame.State"))
FGameplayTagContainer OwnedTags;
};
// MyTaggedActor.cpp
#include "MyTaggedActor.h"
void AMyTaggedActor::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
{
TagContainer.AppendTags(OwnedTags);
}
HasMatchingGameplayTag、HasAllMatchingGameplayTags 和 HasAnyMatchingGameplayTags 可直接从接口获得——它们会调用你的 GetOwnedGameplayTags。在 Blueprint 中,对应的函数是 UBlueprintGameplayTagLibrary::DoesTagAssetInterfaceHaveTag、HasAnyMatchingGameplayTags 和 GetOwnedGameplayTags。
计数标签: FGameplayTagContainer 是一个集合,因此两个来源授予 Stunned,而另一个来源移除它后,集合中就不再有该标签。如果多个来源可能授予同一个标签,请使用 FGameplayTagCountContainer(GameplayEffectTypes.h:1102,位于 GameplayAbilities 插件中;ASC 就用它来管理拥有的标签)。UpdateTagCount(Tag, +1/-1, EGameplayTagReplicationState::None) 会调整计数(单标签重载的第三个参数没有默认值;UpdateTagCount(Container, Delta) 接受两个参数);GetTagCount(Tag) 会计入子标签(添加 A.B 和 A.C 后,GetTagCount(A) == 2);GetExplicitTagCount 不会计入子标签;GetExplicitGameplayTags() 会返回供 GetOwnedGameplayTags 使用的当前集合;RegisterGameplayTagEvent(Tag, EGameplayTagEventType::NewOrRemoved | AnyCountChange) 会返回一个 FOnGameplayEffectTagCountChanged(void(const FGameplayTag, int32 NewCount)),可用于绑定。如果项目中使用 GAS,请使用 ASC 的临时标签,而不是自行持有一个。
经得起考验的设计规则:
- 标签命名的是状态和类别,而不是对象。
MyGame.State.Stunned和MyGame.Damage.Fire是合适的;MyGame.Actor.Goblin03则是披着标签外衣的类引用。 - 层级深度决定查询方式。 层级应保持足够浅,使
HasTag(MyGame.Damage)成为有意义的问题。 - 定义筛选条件,而不是分支。 暴露
FGameplayTagContainer或FGameplayTagQuery属性,让数据决定逻辑,而不是编写if (Tag == SpecificTag)链。 - 绝不要在游戏类型中将标签存储为
FName或FString。 存储FGameplayTag;它支持验证、重定向和复制。
复制与性能
FGameplayTag 是单个 FName——可以随意复制,也可以按值传递。开销来自注册和序列化。
| 关注点 | 规则 |
|---|---|
| 查找 | 每次调用 RequestGameplayTag 都会遍历映射。只解析一次并保存为原生标签或成员变量,绝不要在每次 Tick 或每次命中时解析 |
| 容器构建 | 每次调用 AddTag 都会重新填充父级数组;AddTagFast 会跳过唯一性检查;FGameplayTagContainer::CreateFromArray 会为整个数组一次性构建父级 |
| 网络格式 | FGameplayTag::NetSerialize 和 FGameplayTagContainer::NetSerialize 通过 TStructOpsTypeTraits 注册——复制的标签属性已使用它们 |
FastReplication=True | 按网络索引而非名称复制标签。客户端和服务器的字典必须完全一致,因此如果插件只在一端添加标签,就会出错 |
bDynamicReplication=True | 按连接分配索引;开销略高于 FastReplication,但允许字典不同。启用 FastReplication 时会忽略此设置 |
NetIndexFirstBitSegment | 首个索引段中的位数;列在 CommonlyReplicatedTags 中的标签会获得较低的索引,并适合放入单个段中 |
NumBitsForContainerSize | 复制容器元素数量所用的位数——应根据实际容器大小设置 |
UGameplayTagsManager::Get().DoneAddingNativeTags() 会刷新原生标签注册;此后添加更多标签对于快速复制而言并不安全,因为网络索引表已经构建并计算哈希(GetNetworkGameplayTagNodeIndexHash)。
有关承载这些容器的属性的网络开销,以及推送模型复制,请参阅 ue-networking-replication。
解耦消息传递
选择最轻量且适用的机制:
| 需求 | 使用 |
|---|---|
| 单个游戏中的少数已知监听器 | UGameInstanceSubsystem 或 UWorldSubsystem 上的动态多播委托 |
| 按标签寻址、分层、跨线程、带类型的载荷 | AsyncMessageSystem 插件(在 5.8 中为 Experimental) |
| 能力激活和 GAS 侧事件 | SendGameplayEventToActor / RegisterGameplayTagEvent,由 ue-gameplay-abilities 管理 |
子系统上的动态多播委托通常就够用了,而且不会带来额外开销:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FMyScoreChanged, AActor*, Scorer, int32, NewScore);
UPROPERTY(BlueprintAssignable, Category = "Score")
FMyScoreChanged OnScoreChanged;
只有在监听器必须按标签层级寻址或必须在游戏线程之外运行时,才使用 AsyncMessageSystem。在 .uproject 中启用该插件,并将 "AsyncMessageSystem" 添加到 PublicDependencyModuleNames;它已依赖 GameplayTags。具体做法、原样函数签名、绑定选项和端点见 gameplay-messaging.md。
// MyScorePayload.h
#pragma once
#include "StructUtils/InstancedStruct.h"
#include "MyScorePayload.generated.h"
USTRUCT()
struct FMyScorePayload
{
GENERATED_BODY()
UPROPERTY()
int32 NewScore = 0;
};
// MyScoreComponent.cpp — queue side
#include "AsyncMessageWorldSubsystem.h"
#include "AsyncMessageSystemBase.h"
#include "MyGameplayTags.h"
#include "MyScorePayload.h"
void UMyScoreComponent::BroadcastScore(int32 NewScore)
{
TSharedPtr<FAsyncMessageSystemBase> Sys = UAsyncMessageWorldSubsystem::GetSharedMessageSystem(GetWorld());
if (!Sys.IsValid())
{
return;
}
FMyScorePayload Payload;
Payload.NewScore = NewScore;
Sys->QueueMessageForBroadcast(FAsyncMessageId(TAG_MyGame_Message_ScoreChanged.GetTag()),
FInstancedStruct::Make<FMyScorePayload>(Payload));
}
引擎中不包含: UGameplayMessageSubsystem 及其 GameplayMessageRouter 是 Lyra 示例代码,复制到了 Lyra 项目中,并未随引擎一同发布。不要 #include "GameFramework/GameplayMessageSubsystem.h"——它无法解析。将 Lyra 文件移植到你自己的模块中,或使用 AsyncMessageSystem。
编辑器体验与筛选
meta = (Categories = "…") 可将标签选择器限制在一个子树中。元数据键为 Categories 和 GameplayTagFilter,它们在 ObjectMacros.h 的 namespace GameplayTagsManager 中声明:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Damage", meta = (Categories = "MyGame.Damage"))
FGameplayTag DamageType;
UFUNCTION(BlueprintCallable, Category = "Damage")
void ApplyElement(UPARAM(meta = (GameplayTagFilter = "MyGame.Damage")) FGameplayTag Element);
UGameplayTagsManager::GetCategoriesMetaFromField 会先读取 Categories,再回退到 GameplayTagFilter;GetCategoriesMetaFromFunction(Func, ParamName) 用于解析蓝图引脚的筛选条件。两者都接受以逗号分隔的根标签列表。
UGameplayTagsSettings 中的 FGameplayTagCategoryRemap(BaseCategory → RemapCategories)会将 Mover 或 GAS 等引擎侧筛选器的 Categories 值重定向到你自己的子树,从而让引擎属性显示你的标签。UGameplayTagsDeveloperSettings(config=EditorPerProjectUserSettings)保存添加新标签时在选择器中使用的每用户 FavoriteTagSource 和 DeveloperConfigName。当字典发生变化时,编辑器工具可通过 IGameplayTagsModule::OnGameplayTagTreeChanged 和 OnTagSettingsChanged 刷新。
已弃用 — 请勿使用
| 不要使用 | 在 5.8 中使用 | 来源 |
|---|---|---|
UGameplayTagsManager::OnLastChanceToAddNativeTags() | CallOrRegister_OnAddNativeTagsDelegate,或直接使用 AddNativeGameplayTag | GameplayTagsManager.h:415 中的 UE_DEPRECATED(5.8) |
CallOrRegister_OnDoneAddingNativeTags | CallOrRegister_OnDoneAddingNativeTagsDelegate | 此名称从未存在;GameplayTagsManager.h:429 |
UGameplayTagsManager::GetSingleTagContainer(Tag) | FindTagNode(Tag) 或 FGameplayTag::GetSingleTagContainer() | GameplayTagsManager.h:472 中的 UE_DEPRECATED(5.4) |
FGameplayTagContainer::AddParentsForTag | ParseParentTags 或 UGameplayTagsManager::ExtractParentTags | GameplayTagContainer.h:615 中的 UE_DEPRECATED(5.4) |
UGameplayTagsManager::ShouldClearInvalidTags() | 无 — 无效标签不会被清除 | GameplayTagsManager.h:594 中的 UE_DEPRECATED(5.5) |
ClearInvalidTags ini 键 | 将其移除 | GameplayTagsSettings.h:115 中的 UE_DEPRECATED(5.5) |
[/Script/Engine.Engine] 下的 +GameplayTagRedirects | [/Script/GameplayTags.GameplayTagsSettings] 下的同一键 | “deprecated location” 错误,GameplayTagRedirectors.cpp:26-56 |
EGameplayTagMatchType::Explicit / IncludeParentTags | 各调用中使用 Exact 后缀(HasTagExact、MatchesTagExact) | 5.8 中不存在此枚举 |
Container.HasTag(Tag, MatchTypeA, MatchTypeB) | HasTag(Tag) 或 HasTagExact(Tag) | 仅有单参数形式,GameplayTagContainer.h:299,316 |
Container.MatchesAll(Other) | HasAll(Other) / HasAllExact(Other) | 不存在此成员;GameplayTagContainer.h:379,402 |
Container.Matches(Query) | Container.MatchesQuery(Query) 或 Query.Matches(Container) | GameplayTagContainer.h:464,803 |
MakeQuery_MatchAnyTagsExact | MakeQuery_ExactMatchAnyTags | GameplayTagContainer.h:857 |
DEFINE_GAMEPLAY_TAG / DECLARE_GAMEPLAY_TAG_EXTERN(不带 UE_) | UE_DEFINE_GAMEPLAY_TAG / UE_DECLARE_GAMEPLAY_TAG_EXTERN | NativeGameplayTags.h:31,41 |
IGameplayTagsModule::Get().GetGameplayTagsManager() | UGameplayTagsManager::Get() | 模块仅公开两个变更委托,GameplayTagsModule.h:47-50 |
UGameplayMessageSubsystem(Lyra 示例,并非引擎功能) | 子系统委托或 AsyncMessageSystem | 在 Engine/Source 或 Engine/Plugins 下均不存在 |
常见错误
在管理器注册标签前请求标签: 在静态初始化或模块启动早期调用 RequestGameplayTag(FName("MyGame.X")),会确保并返回一个空标签。请使用原生标签变量,或将查找延迟到 CallOrRegister_OnDoneAddingNativeTagsDelegate 中。
同一个原生标签有两个定义: 在头文件中使用 UE_DEFINE_GAMEPLAY_TAG,或在两个 .cpp 文件中定义同一个变量,都会导致重复符号链接错误。在头文件中使用 UE_DECLARE_GAMEPLAY_TAG_EXTERN 声明一次,并在一个 .cpp 文件中定义一次。标签仅在文件内使用时,请使用 UE_DEFINE_GAMEPLAY_TAG_STATIC。
本意是精确匹配,却使用了 MatchesTag: 对于包含 MyGame.Damage.Fire.Napalm 的容器,Owned.HasTag(TAG_MyGame_Damage_Fire) 会返回 true;DamageType.MatchesTag(FireTag) 对任何火焰子类型都会返回 true。如果需要匹配叶节点且仅限叶节点,请使用 HasTagExact / MatchesTagExact。
在游戏逻辑代码中硬编码标签字符串: 在类中散布的 RequestGameplayTag(FName("MyGame.State.Stunned")),重命名后仍能通过编译,且没有重定向。请定义一个原生标签,并在所有地方引用它。
模块依赖: Engine.Build.cs 将 "GameplayTags" 列为公共依赖,因此依赖 Engine 的任何模块无需显式声明该依赖,也能编译和链接标签代码。仍请显式添加 "GameplayTags"(如果标签类型出现在公共头文件中,则设为公共依赖,否则设为私有依赖),这样即使以后移除 Engine,依赖也能保留,同时 IWYU 工具也能识别它。不依赖 Engine 的模块(很少见)则确实需要添加它。
重命名 ini 标签却不添加重定向: 每个引用旧名称的资源都会加载无效标签,而 WarnOnInvalidTags 最多只会记录日志。请在重命名的同一次提交中添加 +GameplayTagRedirects=(OldTagName="Old",NewTagName="New")。
在 FastReplication=True 时过晚调用 AddNativeGameplayTag: 网络索引表已构建完成,因此客户端和服务端会产生不一致。请在模块启动期间注册,或改用 bDynamicReplication。
每帧都构建 FGameplayTagQuery: BuildQuery 会生成令牌流。请在构造函数或 BeginPlay 中构建并保存,然后在热路径中调用 Matches。
误以为存在 Lyra 消息路由器: 在干净项目中,#include "GameFramework/GameplayMessageSubsystem.h" 无法解析——该子系统随 Lyra 示例提供,而非引擎自带。请使用子系统委托或 AsyncMessageSystem 插件。
相关技能
ue-gameplay-abilities— GAS 中的标签用法:技能激活门控、授予的标签、RegisterGameplayTagEvent和游戏事件ue-state-trees— 用于评估标签容器和查询的 State Tree 条件与任务ue-game-features— 插件作用域内的标签 ini 源,以及用于注册它们的激活生命周期ue-ai-navigation— Smart Object 和行为标签筛选ue-cpp-foundations—UPROPERTY/UFUNCTION修饰符、委托、承载消息传递的子系统ue-networking-replication— 复制标签属性、推送模型、RPC 开销ue-data-assets-tables— 用作标签源以及存储按标签键控数据的UDataTable资源