Ddia Principles
luoling8192/ai-coding-principles
Designing Data-Intensive Applications (DDIA) distilled reference guide by Martin Kleppmann.
A skill your agent uses when defining, querying or replicating Gameplay Tags, or wiring decoupled event messaging between gameplay systems in C++.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-gameplay-tags-messaging --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/ue-gameplay-tags-messaging .claude/skills/ue-gameplay-tags-messaging && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "ue-gameplay-tags-messaging" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messaging into .claude/skills/ue-gameplay-tags-messaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-gameplay-tags-messaging", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messagingType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-gameplay-tags-messaging --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/ue-gameplay-tags-messaging .agents/skills/ue-gameplay-tags-messaging && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "ue-gameplay-tags-messaging" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messaging into .agents/skills/ue-gameplay-tags-messaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-gameplay-tags-messaging", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-gameplay-tags-messaging --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/ue-gameplay-tags-messaging .cursor/skills/ue-gameplay-tags-messaging && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "ue-gameplay-tags-messaging" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messaging into .cursor/skills/ue-gameplay-tags-messaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-gameplay-tags-messaging", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/quodsoler/unreal-engine-skills.git --path skills/ue-gameplay-tags-messaging--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-gameplay-tags-messaging --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/ue-gameplay-tags-messaging .gemini/skills/ue-gameplay-tags-messaging && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "ue-gameplay-tags-messaging" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messaging into .gemini/skills/ue-gameplay-tags-messaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-gameplay-tags-messaging", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install quodsoler/unreal-engine-skills ue-gameplay-tags-messagingInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/ue-gameplay-tags-messaging .github/skills/ue-gameplay-tags-messaging && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "ue-gameplay-tags-messaging" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messaging into .github/skills/ue-gameplay-tags-messaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-gameplay-tags-messaging", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-gameplay-tags-messaging --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/ue-gameplay-tags-messaging .opencode/skills/ue-gameplay-tags-messaging && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "ue-gameplay-tags-messaging" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-gameplay-tags-messaging into .opencode/skills/ue-gameplay-tags-messaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-gameplay-tags-messaging", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
ue-gameplay-tags-messagingA skill your agent uses when defining, querying or replicating Gameplay Tags, or wiring decoupled event messaging between gameplay systems in C++.
Ue Gameplay Tags Messaging is an agent skill from quodsoler/unreal-engine-skills. Use when defining, querying or replicating Gameplay Tags, or wiring decoupled event messaging between gameplay systems in C++. Also use when the user mentions 'FGameplayTag', 'FGameplayTagContainer', 'FGameplayTagQuery', 'UEDEFINEGAMEPLAYTAG', 'native gameplay tag', 'DefaultGameplayTags.ini', 'GameplayTagList', 'tag redirect', 'MatchesTag', 'HasTagExact', 'RequestGameplayTag', 'UGameplayTagsManager', 'Categories meta', 'tag picker', 'gameplay message', 'message bus', 'event bus' or 'AsyncMessageSystem'. For…
Its SKILL.md is about 6.9k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files, including reference files (for example `references/gameplay-messaging.md` and `references/gameplay-tag-reference.md`).
It sits in Databases, covering Event-driven systems, On-page SEO and Database administration. It works with C++. The repository describes itself as: Unreal Engine C++ skills for AI coding agents. 27 skills covering gameplay, rendering, networking, animation, and more. Works with Claude Code, Cursor, Windsurf, and any agent… The licence is MIT.
Read from SKILL.md and the folder at commit f3742d7. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are cpp and ini).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Ue Gameplay Tags Messaging loads about 6.9k tokens when it runs, and up to ~17k if it reads all its reference files. Until then it costs about 168 tokens; SKILL.md has 2,289 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from quodsoler/unreal-engine-skills at commit f3742d7, republished under its MIT licence (© quodsoler). 2,289 words, ~6,940 tokens.
.claude/skills/ue-gameplay-tags-messaging/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.Target engine: UE 5.8. APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
Gameplay Tags are a built-in runtime module, not a plugin: add "GameplayTags" to PublicDependencyModuleNames in your Build.cs and include GameplayTagContainer.h / NativeGameplayTags.h / GameplayTagsManager.h. This skill owns tag definition (native and ini), FGameplayTag, FGameplayTagContainer, FGameplayTagQuery, UGameplayTagsManager, editor filtering, and decoupled gameplay messaging — including the AsyncMessageSystem plugin (Experimental in 5.8).
Read .agents/ue-project-context.md if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
| Request is about… | Go to |
|---|---|
| Declaring tags in C++, native tag macros, module setup | Native Tag Definitions |
Declaring tags in .ini, data tables, renaming a tag | Ini and Data Table Tag Sources |
| Testing a tag, container membership, parent vs exact | Matching Semantics |
| Multi-tag conditions, AND/OR/NOT, designer-authored rules | Tag Queries |
| Actors exposing tags, gates and states built from tags | Tag-Driven Design |
| Sending tags over the network, tag cost, hot paths | Replication and Performance |
| Broadcasting events between systems without hard references | Decoupled Messaging |
Tag pickers, Categories filtering, per-team tag files | Editor UX and Filtering |
A tag must be registered before it can be requested. Four sources feed the one global dictionary held by UGameplayTagsManager:
| Source | Where | Use for |
|---|---|---|
| Native | UE_DEFINE_GAMEPLAY_TAG* in a module .cpp | Tags C++ compares against by name |
| Default ini | Config/DefaultGameplayTags.ini, +GameplayTagList= | Tags authored by designers |
| Extra ini | Config/Tags/*.ini and plugin config dirs | Per-team or per-plugin tag lists |
| Data table | UDataTable of FGameplayTagTableRow listed in GameplayTagTableList | Bulk import from a spreadsheet |
EGameplayTagSourceType (Native, DefaultTagList, TagList, RestrictedTagList, DataTable, Invalid) names these in the editor. Tags are hierarchical: registering MyGame.State.Stunned implicitly registers MyGame.State and MyGame.
Declare in a public header, define in exactly one .cpp of the same module. The macros static_assert that they are used from a .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");| Macro | Parameters | Effect |
|---|---|---|
UE_DECLARE_GAMEPLAY_TAG_EXTERN | (TagName) | extern FNativeGameplayTag TagName; — goes in the header, prefixed with your _API macro |
UE_DEFINE_GAMEPLAY_TAG | (TagName, Tag) | Defines the variable with an empty dev comment |
UE_DEFINE_GAMEPLAY_TAG_COMMENT | (TagName, Tag, Comment) | Same plus a developer comment shown in the editor |
UE_DEFINE_GAMEPLAY_TAG_STATIC | (TagName, Tag) | static — visible only inside that one .cpp, with no matching declaration in a header |
Tag and Comment are bare string literals: the macro wraps Comment in TEXT() itself. Writing TEXT("…") there does not compile.
FNativeGameplayTag registers on static construction and unregisters when the module unloads, so tags from a plugin disappear cleanly with the plugin. It converts implicitly to FGameplayTag and also exposes GetTag():
const FGameplayTag Stunned = TAG_MyGame_State_Stunned.GetTag();
if (OwnedTags.HasTag(TAG_MyGame_State_Stunned))
{
return;
}One definition rule: exactly one UE_DEFINE_GAMEPLAY_TAG (or _COMMENT / _STATIC) per tag variable per binary. Two modules defining the same tag string is fine (the manager reference-counts registrations), but two definitions of the same variable is a link error, and a definition in a header breaks every translation unit that includes it.
To register a tag whose name is only known at runtime, call the manager directly:
const FGameplayTag Runtime = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName(TEXT("MyGame.Generated.Slot01")), TEXT("Generated at startup"));AddNativeGameplayTag after startup invalidates fast replication (see Replication and Performance). Register from CallOrRegister_OnAddNativeTagsDelegate, and run code that consumes tags from 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);
}If registration already finished, CallOrRegister_OnDoneAddingNativeTagsDelegate runs the delegate immediately and CallOrRegister_OnAddNativeTagsDelegate runs it at the end of the current module load (GameplayTagsManager.h:419-427), so there is no ordering hazard in calling them late.
UGameplayTagsSettings is UCLASS(config=GameplayTags, defaultconfig) deriving from UGameplayTagsList, so Project Settings → Gameplay Tags writes 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")| Key | Type | Meaning |
|---|---|---|
ImportTagsFromConfig | bool | Master switch for ini-defined tags; read back through UGameplayTagsManager::ShouldImportTagsFromINI() |
GameplayTagList | TArray<FGameplayTagTableRow> | Tag (FName) plus DevComment (FString) |
GameplayTagTableList | TArray<FSoftObjectPath> | Data tables of FGameplayTagTableRow, loaded by LoadGameplayTagTables |
GameplayTagRedirects | TArray<FGameplayTagRedirect> | OldTagName → NewTagName; inherited from UGameplayTagsList |
CategoryRemapping | TArray<FGameplayTagCategoryRemap> | Remaps an engine Categories filter onto project categories |
RestrictedConfigFiles | TArray<FRestrictedConfigInfo> | Top-level tags only a named owner may edit |
InvalidTagCharacters | FString | Characters rejected in tag names, beyond newline and friends |
Extra ini sources live in Config/Tags/ (added automatically) and in any directory passed to UGameplayTagsManager::Get().AddTagIniSearchPath(RootDir); RemoveTagIniSearchPath takes them away again. A Game Feature plugin's config directory is registered this way, which is why its tags appear only while the plugin is active.
Renaming a tag requires a redirect. FGameplayTagRedirect is { OldTagName, NewTagName }; FGameplayTagRedirectors::Get().RedirectTag(OldName, OutTag) is what serialization uses (engine-internal: the class is not exported). Without a redirect, every asset that referenced the old name silently loads an invalid tag.
Full ini and data-table details are in gameplay-tag-reference.md.
FGameplayTag is one FName. A container keeps the explicitly added tags plus a transient expanded parent list, so parent-aware checks are array lookups, not string parsing.
| Call | Receiver | Semantics | {"A.1"} vs A |
|---|---|---|---|
MatchesTag(TagToCheck) | FGameplayTag | This tag or any parent equals TagToCheck | A.1.MatchesTag(A) → true; A.MatchesTag(A.1) → false |
MatchesTagExact(TagToCheck) | FGameplayTag | Names equal | A.1.MatchesTagExact(A) → false |
MatchesAny(ContainerToCheck) | FGameplayTag | Parent-expanded match against any tag in the container | A.1.MatchesAny({A,B}) → true |
MatchesAnyExact(ContainerToCheck) | FGameplayTag | Container explicitly holds this tag | A.1.MatchesAnyExact({A,B}) → false |
HasTag(TagToCheck) | FGameplayTagContainer | Explicit or parent list contains it | {A.1}.HasTag(A) → true |
HasTagExact(TagToCheck) | FGameplayTagContainer | Explicit list only | {A.1}.HasTagExact(A) → false |
HasAny / HasAnyExact | FGameplayTagContainer | At least one; false for an empty argument | {A.1}.HasAny({A,B}) → true |
HasAll / HasAllExact | FGameplayTagContainer | All; true for an empty argument | {A.1,B.1}.HasAll({A,B}) → true |
Filter(OtherContainer) | FGameplayTagContainer | New container of tags matching any tag in OtherContainer, parents expanded | {A.1,C}.Filter({A}) → {A.1} |
FilterExact(OtherContainer) | FGameplayTagContainer | True intersection, exact names | {A.1,C}.FilterExact({A}) → {} |
The empty-argument asymmetry is deliberate: "any of nothing" is false, "all of nothing" is true. Gates written as RequiredTags.IsEmpty() || Owned.HasAll(RequiredTags) are redundant.
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;
}Parent navigation: FGameplayTag::RequestDirectParent() returns x for x.y, FGameplayTag::GetGameplayTagParents() returns a container holding the tag plus every parent, and FGameplayTagContainer::GetGameplayTagParents() does the same for a whole container. ParseParentTags(TArray<FGameplayTag>&) does it without touching the manager.
AddTagFast skips the Contains check but still fills parents; use it when building a container from data you already know is unique, never in place of AddTag on user input.
FGameplayTagQuery is a compiled token stream — cheap to evaluate, expensive to build. Build it once, store it, evaluate it many times.
// 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);
}The shortcut form covers the common cases without an expression tree:
FGameplayTagContainer Blocking;
Blocking.AddTag(TAG_MyGame_State_Stunned);
const FGameplayTagQuery BlockedQuery = FGameplayTagQuery::MakeQuery_MatchAnyTags(Blocking);Shortcuts on FGameplayTagQuery: MakeQuery_MatchAnyTags, MakeQuery_MatchAllTags, MakeQuery_MatchNoTags, MakeQuery_ExactMatchAnyTags, MakeQuery_ExactMatchAllTags, MakeQuery_MatchTag. Expression verbs on FGameplayTagQueryExpression: AnyTagsMatch, AllTagsMatch, NoTagsMatch, AnyTagsExactMatch, AllTagsExactMatch, AnyExprMatch, AllExprMatch, NoExprMatch, fed by AddTag, AddTags and AddExpr.
Evaluate from either side — Query.Matches(Container) or Container.MatchesQuery(Query). Both call the same evaluator.
Expose a query as a UPROPERTY and designers get the query editor with no extra work:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Gating")
FGameplayTagQuery ActivationQuery;ReplaceTagsFast(Tags) and ReplaceTagFast(Tag) swap the tag dictionary of a cached query without rebuilding the token stream; the new container must be the same size.
Anything that owns tags should expose them through IGameplayTagAssetInterface so unrelated systems can ask without casting:
// 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 and HasAnyMatchingGameplayTags come from the interface for free — they call your GetOwnedGameplayTags. From Blueprint the equivalents are UBlueprintGameplayTagLibrary::DoesTagAssetInterfaceHaveTag, HasAnyMatchingGameplayTags and GetOwnedGameplayTags.
Counted tags: an FGameplayTagContainer is a set, so two sources granting Stunned and one removing it leaves nothing. When several sources can grant the same tag, use FGameplayTagCountContainer (GameplayEffectTypes.h:1102, in the GameplayAbilities plugin; it is what an ASC uses for owned tags). UpdateTagCount(Tag, +1/-1, EGameplayTagReplicationState::None) adjusts the count (the single-tag overload has no default for the third argument; UpdateTagCount(Container, Delta) takes two), GetTagCount(Tag) includes child tags (A.B and A.C added make GetTagCount(A) == 2), GetExplicitTagCount does not, GetExplicitGameplayTags() returns the current set for GetOwnedGameplayTags, and RegisterGameplayTagEvent(Tag, EGameplayTagEventType::NewOrRemoved | AnyCountChange) returns an FOnGameplayEffectTagCountChanged (void(const FGameplayTag, int32 NewCount)) to bind. With GAS in the project, use the ASC's loose tags instead of owning one.
Design rules that hold up:
MyGame.State.Stunned and MyGame.Damage.Fire are good; MyGame.Actor.Goblin03 is a class reference wearing a tag.HasTag(MyGame.Damage) is a meaningful question.FGameplayTagContainer or FGameplayTagQuery properties and let data decide, instead of if (Tag == SpecificTag) chains.FName or FString on a gameplay type. Store FGameplayTag; it validates, redirects and replicates.An FGameplayTag is a single FName — copy it freely, pass it by value. Cost lives in registration and in serialization.
| Concern | Rule |
|---|---|
| Lookup | RequestGameplayTag walks a map every call. Resolve once into a native tag or a member, never per tick or per hit |
| Container build-up | AddTag refills the parent array each call; AddTagFast skips the uniqueness check; FGameplayTagContainer::CreateFromArray builds parents once for a whole array |
| Wire format | FGameplayTag::NetSerialize and FGameplayTagContainer::NetSerialize are registered via TStructOpsTypeTraits — a replicated tag property already uses them |
FastReplication=True | Replicates tags by net index instead of name. Client and server dictionaries must be identical, so it breaks if a plugin adds tags on only one side |
bDynamicReplication=True | Per-connection index assignment; slightly more expensive than FastReplication but tolerates differing dictionaries. Ignored when FastReplication is on |
NetIndexFirstBitSegment | Bits in the first index segment; tags listed in CommonlyReplicatedTags get the low indices and fit in one segment |
NumBitsForContainerSize | Bits used for a replicated container's element count — size it to your real containers |
UGameplayTagsManager::Get().DoneAddingNativeTags() flushes native registration; after it, adding more tags is unsafe with fast replication because the net index table has already been built and hashed (GetNetworkGameplayTagNodeIndexHash).
For the wire cost of the properties that carry these containers, and for push-model replication, see ue-networking-replication.
Pick the lightest mechanism that fits:
| Need | Use |
|---|---|
| A few known listeners inside one game | A dynamic multicast delegate on a UGameInstanceSubsystem or UWorldSubsystem |
| Tag-addressed, hierarchical, cross-thread, typed payloads | AsyncMessageSystem plugin (Experimental in 5.8) |
| Ability activation and GAS-side events | SendGameplayEventToActor / RegisterGameplayTagEvent, owned by ue-gameplay-abilities |
A dynamic multicast delegate on a subsystem is often enough, and it costs nothing extra:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FMyScoreChanged, AActor*, Scorer, int32, NewScore);
UPROPERTY(BlueprintAssignable, Category = "Score")
FMyScoreChanged OnScoreChanged;Reach for AsyncMessageSystem when listeners must be addressed by tag hierarchy or must run off the game thread. Enable the plugin in the .uproject and add "AsyncMessageSystem" to PublicDependencyModuleNames; it already depends on GameplayTags. The recipe, verbatim signatures, binding options and endpoints are in 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));
}Not in the engine: UGameplayMessageSubsystem and its GameplayMessageRouter are Lyra sample code, copied into the Lyra project, not shipped in Engine/. Do not #include "GameFramework/GameplayMessageSubsystem.h" — it does not resolve. Port the Lyra files into your own module, or use AsyncMessageSystem.
meta = (Categories = "…") restricts a tag picker to one subtree. The metadata keys are Categories and GameplayTagFilter, declared in namespace GameplayTagsManager in ObjectMacros.h:
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 reads Categories first and falls back to GameplayTagFilter; GetCategoriesMetaFromFunction(Func, ParamName) is what resolves the filter for a Blueprint pin. Both accept a comma-separated list of roots.
FGameplayTagCategoryRemap (BaseCategory → RemapCategories) in UGameplayTagsSettings redirects an engine-side filter such as a Mover or GAS Categories value onto your own subtree, so engine properties show your tags. UGameplayTagsDeveloperSettings (config=EditorPerProjectUserSettings) holds the per-user FavoriteTagSource and DeveloperConfigName used when adding a new tag from the picker. IGameplayTagsModule::OnGameplayTagTreeChanged and OnTagSettingsChanged let editor tooling refresh when the dictionary changes.
| Do not emit | Use in 5.8 | Source |
|---|---|---|
UGameplayTagsManager::OnLastChanceToAddNativeTags() | CallOrRegister_OnAddNativeTagsDelegate, or AddNativeGameplayTag directly | UE_DEPRECATED(5.8) in GameplayTagsManager.h:415 |
CallOrRegister_OnDoneAddingNativeTags | CallOrRegister_OnDoneAddingNativeTagsDelegate | Name never existed; GameplayTagsManager.h:429 |
UGameplayTagsManager::GetSingleTagContainer(Tag) | FindTagNode(Tag) or FGameplayTag::GetSingleTagContainer() | UE_DEPRECATED(5.4) in GameplayTagsManager.h:472 |
FGameplayTagContainer::AddParentsForTag | ParseParentTags or UGameplayTagsManager::ExtractParentTags | UE_DEPRECATED(5.4) in GameplayTagContainer.h:615 |
UGameplayTagsManager::ShouldClearInvalidTags() | Nothing — invalid tags are never cleared | UE_DEPRECATED(5.5) in GameplayTagsManager.h:594 |
ClearInvalidTags ini key | Remove it | UE_DEPRECATED(5.5) in GameplayTagsSettings.h:115 |
+GameplayTagRedirects under [/Script/Engine.Engine] | Same key under [/Script/GameplayTags.GameplayTagsSettings] | "deprecated location" error, GameplayTagRedirectors.cpp:26-56 |
EGameplayTagMatchType::Explicit / IncludeParentTags | The Exact suffix on each call (HasTagExact, MatchesTagExact) | Enum does not exist in 5.8 |
Container.HasTag(Tag, MatchTypeA, MatchTypeB) | HasTag(Tag) or HasTagExact(Tag) | One-argument forms only, GameplayTagContainer.h:299,316 |
Container.MatchesAll(Other) | HasAll(Other) / HasAllExact(Other) | No such member; GameplayTagContainer.h:379,402 |
Container.Matches(Query) | Container.MatchesQuery(Query) or Query.Matches(Container) | GameplayTagContainer.h:464,803 |
MakeQuery_MatchAnyTagsExact | MakeQuery_ExactMatchAnyTags | GameplayTagContainer.h:857 |
DEFINE_GAMEPLAY_TAG / DECLARE_GAMEPLAY_TAG_EXTERN (no UE_) | UE_DEFINE_GAMEPLAY_TAG / UE_DECLARE_GAMEPLAY_TAG_EXTERN | NativeGameplayTags.h:31,41 |
IGameplayTagsModule::Get().GetGameplayTagsManager() | UGameplayTagsManager::Get() | Module exposes only the two change delegates, GameplayTagsModule.h:47-50 |
UGameplayMessageSubsystem (Lyra sample, not engine) | A subsystem delegate, or AsyncMessageSystem | Not present anywhere under Engine/Source or Engine/Plugins |
Requesting a tag before the manager has it: RequestGameplayTag(FName("MyGame.X")) during static init or early module startup ensures and returns an empty tag. Use a native tag variable, or defer the lookup into CallOrRegister_OnDoneAddingNativeTagsDelegate.
Two definitions of the same native tag: UE_DEFINE_GAMEPLAY_TAG in a header, or the same variable defined in two .cpp files, is a duplicate-symbol link error. Declare once with UE_DECLARE_GAMEPLAY_TAG_EXTERN in the header, define once in a .cpp. Use UE_DEFINE_GAMEPLAY_TAG_STATIC when the tag is file-local.
MatchesTag where exactness was meant: Owned.HasTag(TAG_MyGame_Damage_Fire) is true for a container holding MyGame.Damage.Fire.Napalm, and DamageType.MatchesTag(FireTag) is true for any fire subtype. When you need the leaf and only the leaf, use HasTagExact / MatchesTagExact.
Hard-coded tag strings in gameplay code: RequestGameplayTag(FName("MyGame.State.Stunned")) sprinkled through a class survives a rename with no compile error and no redirect. One native tag, referenced everywhere.
Module dependency: Engine.Build.cs lists "GameplayTags" as a public dependency, so any module that depends on Engine already compiles and links tag code without it. Add "GameplayTags" explicitly anyway (public if tag types appear in your public headers, private otherwise) so the dependency survives if Engine is ever dropped and IWYU tools see it. Modules that do not depend on Engine (rare) do need it.
Renaming an ini tag without a redirect: every asset referencing the old name loads an invalid tag and WarnOnInvalidTags at most logs it. Add +GameplayTagRedirects=(OldTagName="Old",NewTagName="New") in the same commit as the rename.
Calling AddNativeGameplayTag late with FastReplication=True: the net index table is already built, so client and server disagree. Register during module startup, or use bDynamicReplication instead.
Building an FGameplayTagQuery per frame: BuildQuery emits a token stream. Build it in the constructor or BeginPlay, store it, and call Matches in the hot path.
Expecting a Lyra message router: #include "GameFramework/GameplayMessageSubsystem.h" does not resolve in a clean project — that subsystem ships with the Lyra sample, not the engine. Use a subsystem delegate or the AsyncMessageSystem plugin.
ue-gameplay-abilities — GAS-side tag usage: ability activation gates, granted tags, RegisterGameplayTagEvent and gameplay eventsue-state-trees — State Tree conditions and tasks that evaluate tag containers and queriesue-game-features — plugin-scoped tag ini sources and the activation lifecycle that registers themue-ai-navigation — Smart Object and behaviour tag filteringue-cpp-foundations — UPROPERTY/UFUNCTION specifiers, delegates, subsystems that host messagingue-networking-replication — replicating tag properties, push model, RPC costue-data-assets-tables — UDataTable assets used as a tag source and for tag-keyed data© quodsoler, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 2 other files (references) in skills/ue-gameplay-tags-messaging of quodsoler/unreal-engine-skills.
Open the folder on GitHubat commit f3742d7
Ue Gameplay Tags Messaging next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Ue Gameplay Tags Messaging this skillquodsoler/unreal-engine-skills | 362 | — | ~6.9k | Automated safety check: Pass | MIT | |
| Ddia Principlesluoling8192/ai-coding-principles | 172 | — | ~4.7k | Automated safety check: Pass | MIT | |
| Triage Tt Metal Assertstenstorrent/tt-mlir | 314 | — | ~7.8k | Automated safety check: Pass | Apache-2.0 | |
| Databricks Zerobus Ingestdatabricks/databricks-agent-skills | 345 | — | ~6.3k | Automated safety check: Pass | Custom licence | |
| Ue Specifiersfjz13/UnrealSpecifiers | 1.1k | — | ~1.4k | Automated safety check: Pass | Custom licence | |
| Azure Resource Manager Redis Dotnetmicrosoft/skills | 3.1k | 5 repos | ~3k | Automated safety check: Pass | MIT |
luoling8192/ai-coding-principles
Designing Data-Intensive Applications (DDIA) distilled reference guide by Martin Kleppmann.
tenstorrent/tt-mlir
Triage a tt-metal uplift diff or digest of TTFATAL validation changes against what tt-mlir guarantees at each optimization level (0: workarounds only, 1: optimizer with DRAM-only fallback, 2: L1…
databricks/databricks-agent-skills
Push records from apps, services, devices, or scripts directly into Unity Catalog Delta tables with Zerobus Ingest: SDKs (Python, TypeScript, Go, Java, Rust, C++, .NET), REST, OTLP, MQTT, Arrow…
fjz13/UnrealSpecifiers
A skill your agent uses when writing, editing, reviewing, validating, or explaining Unreal Engine C++ reflected declarations: UCLASS, USTRUCT, UENUM, UINTERFACE, UPROPERTY, UFUNCTION, UPARAM, UMETA…
microsoft/skills
Azure Resource Manager SDK for Redis in .NET. An agent skill from microsoft/skills.
ArabelaTso/Skills-4-SE
Automatically generate TLA+ specifications from source code (C/C++, Python) for formal verification of distributed systems.
quodsoler/unreal-engine-skills
Unreal Engine C++ guidance for AActor and UActorComponent: lifecycle hooks, spawning, component creation and attachment, ticking, interfaces and composition.
quodsoler/unreal-engine-skills
Guides Unreal Engine C++ AI work across AI controllers, behavior trees, blackboards, perception, navmesh pathfinding, EQS and Smart Objects.
quodsoler/unreal-engine-skills
Reference for writing and debugging Unreal Engine 5.8 animation C++: AnimInstance and proxy, montages, notifies, curves, blend spaces, state machines, linked layers and root motion.
quodsoler/unreal-engine-skills
Picks and applies the right Unreal Engine C++ concurrency API, from UE::Tasks and FPipe to ParallelFor, FRunnable, locks and timers, with game-thread safety rules.
quodsoler/unreal-engine-skills
A skill your agent uses when playing, mixing or debugging sound in Unreal Engine — one-shot SFX, looping ambience, music, dialogue or procedural audio.
quodsoler/unreal-engine-skills
Reference for exposing Unreal Engine C++ to Blueprint and calling Blueprint from C++, covering UFUNCTION and UPROPERTY specifiers, latent and async nodes, and interfaces.
Works with
Categories
A skill your agent uses when defining, querying or replicating Gameplay Tags, or wiring decoupled event messaging between gameplay systems in C++. Ue Gameplay Tags Messaging is an agent skill from quodsoler/unreal-engine-skills. Use when defining, querying or replicating Gameplay Tags, or wiring decoupled event messaging between gameplay systems in C++.
Ue Gameplay Tags Messaging fits situations like: replicating Gameplay Tags; wiring decoupled event messaging between gameplay systems in C++; the user mentions FGameplayTag; FGameplayTagContainer.
Run `npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a claude-code`. Or copy the skill folder (skills/ue-gameplay-tags-messaging in quodsoler/unreal-engine-skills) into .claude/skills/ue-gameplay-tags-messaging in your project. Claude Code loads it when a task matches its description.
Run `npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a codex`. Or copy the skill folder (skills/ue-gameplay-tags-messaging in quodsoler/unreal-engine-skills) into .agents/skills/ue-gameplay-tags-messaging in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add quodsoler/unreal-engine-skills --skill ue-gameplay-tags-messaging -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ue-gameplay-tags-messaging, .gemini/skills/ue-gameplay-tags-messaging, .github/skills/ue-gameplay-tags-messaging and .opencode/skills/ue-gameplay-tags-messaging in your project.
SKILL.md names no scripts, command-line tools or credentials: Ue Gameplay Tags Messaging is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Ue Gameplay Tags Messaging is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.9k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 9.8k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Ue Gameplay Tags Messaging: Ddia Principles (luoling8192/ai-coding-principles, 172 stars), Triage Tt Metal Asserts (tenstorrent/tt-mlir, 314 stars), Databricks Zerobus Ingest (databricks/databricks-agent-skills, 345 stars) and Ue Specifiers (fjz13/UnrealSpecifiers, 1.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
quodsoler (a GitHub user) maintains it in quodsoler/unreal-engine-skills, which has 362 GitHub stars. The repository holds 31 skills in this directory. The repository was last updated on September 28, 2026.
Source: quodsoler/unreal-engine-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.