mcpField
U
ue-gameplay-tags-messaging

ue-gameplay-tags-messaging

github.com/quodsoler开发419230 次安装4 周前更新
代码仓库

关于 ue-gameplay-tags-messaging

面向 UE 5.8 开发的 Gameplay Tags 与解耦式游戏消息技能,指导开发者完成标签注册、匹配、查询、编辑器筛选和消息设计。

主要功能

  1. 01原生与配置文件标签定义
  2. 02标签容器匹配与层级语义
  3. 03构建和评估标签查询
  4. 04编辑器标签筛选配置
  5. 05解耦式游戏消息设计

适用场景

  1. 01在 C++ 模块中注册原生标签
  2. 02按标签层级检查游戏状态
  3. 03为设计人员配置多标签条件
  4. 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++ 按名称比较的标签
默认 iniConfig/DefaultGameplayTags.ini、+GameplayTagList=由设计师编写的标签
额外 iniConfig/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")
键类型含义
ImportTagsFromConfigboolIni 定义标签的总开关;可通过 UGameplayTagsManager::ShouldImportTagsFromINI() 读取
GameplayTagListTArray<FGameplayTagTableRow>Tag(FName)和 DevComment(FString)
GameplayTagTableListTArray<FSoftObjectPath>FGameplayTagTableRow 数据表,由 LoadGameplayTagTables 加载
GameplayTagRedirectsTArray<FGameplayTagRedirect>OldTagName → NewTagName;继承自 UGameplayTagsList
CategoryRemappingTArray<FGameplayTagCategoryRemap>将引擎的 Categories 筛选器重新映射到项目类别
RestrictedConfigFilesTArray<FRestrictedConfigInfo>仅允许指定所有者编辑的顶层标签
InvalidTagCharactersFString标签名称中禁止使用的字符,换行符等除外

其他 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此标签或任一父级等于 TagToCheckA.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 / HasAnyExactFGameplayTagContainer至少有一个;参数为空时为 false{A.1}.HasAny({A,B}) → true
HasAll / HasAllExactFGameplayTagContainer全部满足;参数为空时为 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,或直接使用 AddNativeGameplayTagGameplayTagsManager.h:415 中的 UE_DEPRECATED(5.8)
CallOrRegister_OnDoneAddingNativeTagsCallOrRegister_OnDoneAddingNativeTagsDelegate此名称从未存在;GameplayTagsManager.h:429
UGameplayTagsManager::GetSingleTagContainer(Tag)FindTagNode(Tag) 或 FGameplayTag::GetSingleTagContainer()GameplayTagsManager.h:472 中的 UE_DEPRECATED(5.4)
FGameplayTagContainer::AddParentsForTagParseParentTags 或 UGameplayTagsManager::ExtractParentTagsGameplayTagContainer.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_MatchAnyTagsExactMakeQuery_ExactMatchAnyTagsGameplayTagContainer.h:857
DEFINE_GAMEPLAY_TAG / DECLARE_GAMEPLAY_TAG_EXTERN(不带 UE_)UE_DEFINE_GAMEPLAY_TAG / UE_DECLARE_GAMEPLAY_TAG_EXTERNNativeGameplayTags.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 资源

评论

支持 Markdown