Published on

Native Gameplay Tags

Authors
  • avatar
    Name
    michael
    Twitter

Gameplay Tags

Unreal's Gameplay Tags made their debut in UE4 and remain a core part of UE5 as an incredibly useful tool for developers looking to easily iterate and prototype features with simple Boolean logic using hierarchical labels organised in tag containers.

Significantly exposed and well-supported by Unreal's Blueprints system; non-native Gameplay Tags only fall short in cases where speed is paramount, whereby it is preferred to manage them natively in C++.

There are a few solid approaches out there but as native Gameplay Tags are locked down quite early in the engine's life cycle to permit replication we don't see that much variation in practice.

Option 1: Module

You could create an engine module which allows early access to UGameplayTagsManager’s OnLastChanceToAddNativeTags delegate.

Tags.h
#pragma once

#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Modules/ModuleManager.h"

class FTagsModule : public IModuleInterface
{
public:
	/** IModuleInterface implementation */
	virtual void StartupModule() override;

	FGameplayTag ExampleTag;

	void RegisterNativeTags();

	virtual void ShutdownModule() override;
};
Tags.cpp
#include "Tags.h"
#include "GameplayTagsManager.h"

#define LOCTEXT_NAMESPACE "FTagsModule"

void FTagsModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
	UGameplayTagsManager::Get().OnLastChanceToAddNativeTags().AddRaw(this, &FTagsModule::RegisterNativeTags);
}

void FTagsModule::RegisterNativeTags()
{
	UGameplayTagsManager& TagsManager = UGameplayTagsManager::Get();

	ExampleTag =  TagsManager.AddNativeGameplayTag("ExampleTag", TEXT("ExampleTag Commentary"));

	TagsManager.OnLastChanceToAddNativeTags().RemoveAll(this);
}

The final container and data arrangement depends on your time complexity constraints. In this basic example you could access them via the FModuleManager pointer:

FTagsModule* TagModule = FModuleManager::Get().GetModulePtr<FTagsModule>("Tags");

if(TagModule)
	TagModule->ExampleTag;

Option 2: Macro

A somewhat easier alternative to consider is using UE’s own native tag macro definitions found in "NativeGameplayTags.h"

NativeGameplayTags.h
/**
 * Declares a native gameplay tag that is defined in a cpp with UE_DEFINE_GAMEPLAY_TAG to allow other modules or code to use the created tag variable.
 */
#define UE_DECLARE_GAMEPLAY_TAG_EXTERN(TagName) extern FNativeGameplayTag TagName;

/**
 * Defines a native gameplay tag that is externally declared in a header to allow other modules or code to use the created tag variable.
 */
#define UE_DEFINE_GAMEPLAY_TAG(TagName, Tag) FNativeGameplayTag TagName(UE_PLUGIN_NAME, UE_MODULE_NAME, Tag, TEXT(""), ENativeGameplayTagToken::PRIVATE_USE_MACRO_INSTEAD); static_assert(UE::GameplayTags::Private::HasFileExtension(__FILE__, ".cpp"), "UE_DEFINE_GAMEPLAY_TAG can only be used in .cpp files, if you're trying to share tags across modules, use UE_DECLARE_GAMEPLAY_TAG_EXTERN in the public header, and UE_DEFINE_GAMEPLAY_TAG in the private .cpp");

/**
 * Defines a native gameplay tag such that it's only available to the cpp file you define it in.
 */
#define UE_DEFINE_GAMEPLAY_TAG_STATIC(TagName, Tag) static FNativeGameplayTag TagName(UE_PLUGIN_NAME, UE_MODULE_NAME, Tag, TEXT(""), ENativeGameplayTagToken::PRIVATE_USE_MACRO_INSTEAD); static_assert(UE::GameplayTags::Private::HasFileExtension(__FILE__, ".cpp"), "UE_DEFINE_GAMEPLAY_TAG_STATIC can only be used in .cpp files, if you're trying to share tags across modules, use UE_DECLARE_GAMEPLAY_TAG_EXTERN in the public header, and UE_DEFINE_GAMEPLAY_TAG in the private .cpp");

So, for a cleaner implementation we would only have to write the following:

UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_EXAMPLE, "EXAMPLE");

and access it via TAG_EXAMPLE

Although these macros simplify the process you still have to be mindful of each macro’s respective dependency limitations to ensure the native tag’s declaration is propagated appropriately through your codebase.

Example.h
#include "NativeGameplayTags.h"

UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_NAME);
Example.cpp
#include "Example.h"

UE_DEFINE_GAMEPLAY_TAG(TAG_NAME, "Tag.Example");

Happy tagging :)