【Unreal Engine】Building Flexible Game Systems with Gameplay Tags

Created: 2026-02-07

Learn how to use UE's Gameplay Tags for hierarchical tag-based design patterns. Replace brittle Bool/Enum state management with Tag Containers and Tag Queries for scalable game logic.

Overview

Tested with: UE 5.4+

"I'm managing enemy types and states with Bools and Enums, but the branching logic is getting out of hand..." — As projects grow, flags like bIsStunned, bIsSwimming, and bIsFlying tend to multiply endlessly. This is a pain point shared by many developers.

UE's Gameplay Tags replace this kind of state management with a hierarchical tag structure, enabling flexible and scalable game systems. You can extend game logic simply by adding or removing tags, with minimal changes to existing code.

What Are Gameplay Tags

Gameplay Tags are dot-separated string labels with a hierarchical structure. They conceptually label in-game objects and drive logic based on those labels.

For example, you can represent concepts like:

  • Object attributes: Character.Enemy.Zombie — indicates a character is an enemy and a zombie
  • States or abilities: Movement.Mode.Swimming — character is swimming
  • Events: GameplayEvent.RequestReset — a game reset request

A tag like Event.Movement.Dash has three hierarchy levels. This enables partial matching — you can check all tags under Event.Movement at once. When you want to add a new movement type, just add Event.Movement.Teleport and any logic already watching Event.Movement will automatically recognize it. No need to modify Enum definitions.

How to Define Tags

To use Gameplay Tags in your project, you first need to register them in the tag dictionary. There are three methods, and you can choose based on project scale and workflow.

1. Add Directly in Project Settings

The simplest method. Suitable for small projects and prototypes.

  1. Project Settings > GameplayTags > Enable Import Tags From Config
  2. Open the Gameplay Tag Manager via the Manage Gameplay Tags button
  3. Click Add (+) and enter Name, Comment, and Source

Tags can be renamed, deleted, or have sub-tags added via right-click. For large projects, splitting tags into feature-specific .ini files under Config/Tags helps avoid merge conflicts across teams.

2. Import from Data Tables

Create a Data Table with row type GameplayTagTableRow and register it in Gameplay Tag Table List in Project Settings. Since .csv / .json imports are supported, this is convenient for managing tags with external tools or spreadsheets. Changes to Data Tables also take effect while the editor is running.

3. Define in C++ (Native Tags)

Defining tags in C++ guarantees their existence at compile time, preventing typo-related bugs. Use macros provided by NativeGameplayTags.h.

// MyTags.h
#pragma once
#include "NativeGameplayTags.h"

UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Movement_Walking);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Movement_Swimming);
// MyTags.cpp
#include "MyTags.h"

UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_Movement_Walking,
    "Movement.Mode.Walking", "Default character movement");
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_Movement_Swimming,
    "Movement.Mode.Swimming", "Character is swimming");

Required: Add "GameplayTags" to PublicDependencyModuleNames in your Build.cs. Without this, you'll get linker errors.

Here's a list of available macros.

MacroPurpose
UE_DECLARE_GAMEPLAY_TAG_EXTERNExtern declaration in .h files
UE_DEFINE_GAMEPLAY_TAGDefinition without comment in .cpp files
UE_DEFINE_GAMEPLAY_TAG_COMMENTDefinition with tooltip comment in .cpp files
UE_DEFINE_GAMEPLAY_TAG_STATICFile-local tag definition (no DECLARE pairing needed)

For a large-scale implementation example, see LyraGameplayTags.h / .cpp in the Lyra Sample Game. You can see the pattern of organizing tags by category in header files.

Runtime Tag Retrieval

You can also dynamically retrieve tags from strings without defining native tags.

FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName("Movement.Mode.Walking"));

This is convenient when loading tag names from Data Tables or configs, but since typos won't be caught at compile time, native definitions are recommended for frequently accessed tags.

Tag Container and Tag Query

Tag Container (FGameplayTagContainer)

In-game objects typically hold multiple tags simultaneously. For example, a character might be an "enemy," a "zombie," and "poisoned" all at once. FGameplayTagContainer is the container for managing multiple tags together.

// Basic Tag Container operations
FGameplayTagContainer TagContainer;

// Add/Remove tags
TagContainer.AddTag(TAG_Movement_Walking);
TagContainer.RemoveTag(TAG_Movement_Walking);

// Condition checks
bool bHas = TagContainer.HasTag(TAG_Movement_Walking);  // Has specific tag
bool bAny = TagContainer.HasAny(OtherContainer);         // Has any of these
bool bAll = TagContainer.HasAll(OtherContainer);         // Has all of these

HasTag matches parent tags in the hierarchy. For example, calling HasTag with Movement.Mode on a container that holds Movement.Mode.Walking returns true. This is how partial matching works. Use HasTagExact if you need exact matching only.

MethodMatching behavior
HasTagHierarchical match (true for parent tags)
HasTagExactExact match only

Note: When passing an empty FGameplayTagContainer as an argument to condition functions, all except HasAll return false. HasAll returns true because it interprets "no missing tags." Watch out for this edge case in your logic.

Tag Query (FGameplayTagQuery)

For more complex condition evaluation, use FGameplayTagQuery. It bundles multiple conditions into a single variable, eliminating the need to chain numerous nodes in Blueprint.

ExpressionMeaning
Any Tags MatchAt least one query tag exists in the container
All Tags MatchAll query tags exist in the container
No Tags MatchNone of the query tags exist in the container

Sub-expression combinations (Any/All/No Expressions Match) are also supported. For example, "has Movement.Swimming AND does not have Status.Stunned" can be expressed in a single Blueprint node or C++ variable.

C++ Tag Definition and Efficient Access

IGameplayTagAssetInterface

When heavily using Gameplay Tags in C++, it's cumbersome to cast objects every time to access their tag containers. Implementing IGameplayTagAssetInterface allows cast-free tag retrieval from objects.

// TaggedActor.h
UCLASS()
class AMyTaggedActor : public AActor, public IGameplayTagAssetInterface
{
    GENERATED_BODY()

public:
    virtual void GetOwnedGameplayTags(
        FGameplayTagContainer& TagContainer) const override
    {
        TagContainer = OwnedTags;
    }

protected:
    UPROPERTY(EditAnywhere, Category = "Tags")
    FGameplayTagContainer OwnedTags;
};

Actors implementing this interface can be treated as IGameplayTagAssetInterface regardless of their actual type. You can aggregate tags from multiple containers or access Blueprint-declared tags through customization in the GetOwnedGameplayTags override.

The ALyraTaggedActor class in Lyra Sample Game is a practical implementation example.

Restricting Tag Editing

In team development, there's a risk of important tags used by core systems being accidentally modified. UE provides a mechanism to restrict tag editing permissions.

In Project Settings > Advanced Gameplay Tags > Restricted Config Files, specify the .ini files to restrict and the list of Owners who have editing permissions. If users not listed as owners attempt to edit these tags, a warning message is displayed asking for confirmation.

Note: Restricted tags cannot be deleted from the editor. If deletion is needed, you must directly edit the .ini file.

Summary

  • Gameplay Tags are a hierarchical tag system replacing Bools/Enums, easily scalable even for large projects
  • Three definition methods: Project Settings, Data Tables, and C++ macros. C++ definitions catch typos at compile time
  • Tag Container manages multiple tags, and Tag Query expresses complex conditions concisely
  • IGameplayTagAssetInterface enables cast-free tag access
  • In team development, use restriction settings to protect critical tags

Further Reading