Native gameplay tags

Course day 8 · WarriorGameplayTags.h/.cpp · 6 min

In one breath

A gameplay tag is a hierarchical name like InputTag.Move that the engine registers once and compares cheaply. A native tag is declared in C++, so your code gets a variable for it. A typo is then a compile error instead of a silent bug at runtime. GAS uses tags for everything: input, states, cooldowns and immunities.

The two macros

// .h — "this tag exists somewhere" (like an extern variable)
namespace WarriorGamePlayTags {
    WADOFGORE_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(InputTag_Move)
}

// .cpp — "here it is, and this is its name"
namespace WarriorGamePlayTags {
    UE_DEFINE_GAMEPLAY_TAG(InputTag_Move, "InputTag.Move")
}

This is the normal C++ declare in header, define in one .cpp split. If you defined it in the header, every .cpp that included it would get its own copy and the linker would complain about duplicates. WADOFGORE_API exports the variable so other modules could use it too.

The namespace just groups the names: you write WarriorGamePlayTags::InputTag_Move, and it can't clash with anything else called InputTag_Move.

Why native instead of the Project Settings list?

Native (C++ macro)Ini / Project Settings
How C++ gets itA variable: WarriorGamePlayTags::InputTag_MoveFGameplayTag::RequestGameplayTag("InputTag.Move"), a string lookup
TypoWon't compileCompiles. Fails (or asserts) at runtime
Who adds themProgrammersDesigners, no recompile

Rule of thumb: tags that C++ code refers to should be native. Tags that only designers use in data can live in the ini. Both end up in the same tag tree in the editor.

Why tags instead of enums or names?

Where it's going

Right now there are two tags, InputTag.Move and InputTag.Look, used to find input actions. Expect many more: ability input tags (InputTag.LightAttack), ability tags, and status tags like "is blocking" or "is dead" that abilities check before they're allowed to activate.

Quiz yourself

Why does UE_DEFINE_GAMEPLAY_TAG go in the .cpp, not the header?

It defines a variable. In a header, every .cpp that included it would define its own copy, which gives a duplicate-symbol link error. The header gets the DECLARE_..._EXTERN ("it exists somewhere"); one .cpp gets the definition.

Main advantage of a native tag over RequestGameplayTag("InputTag.Move")?

A misspelled native tag fails to compile. A misspelled string compiles fine and fails at runtime.

A unit has State.Debuff.Stun. Does MatchesTag(State.Debuff) return true? MatchesTagExact?

MatchesTag: true, because parents match. MatchesTagExact: false.

What does meta = (Categories = "InputTag") on an FGameplayTag property do?

It filters the editor's tag picker to the InputTag branch, so designers can't pick an unrelated tag.

Interview-style: why would a studio prefer gameplay tags over a big EPlayerState enum?

States combine (stunned and burning); a container holds many tags, while an enum holds one value. Tags are hierarchical, so you can query a whole family. And adding one doesn't mean editing a central enum that every system depends on.