Skip to content

Tags and containers

Hierarchical vs exact matching

A gameplay tag is a dot-separated path. Owning a deeper tag satisfies checks against any of its parents:

ts
const fire = requestGameplayTag("Ability.Element.Fire");
const element = requestGameplayTag("Ability.Element");

fire.matchesTag(element);      // true
element.matchesTag(fire);      // false — parents do not match children
fire.matchesTagExact(element); // false
fire.matchesTagDepth(element); // 2 — shared leading segments

GameplayTagContainer applies the same rules to sets of tags:

ts
const owned = makeGameplayTagContainer([
  "Ability.Element.Fire",
  "Character.Class.Mage"
]);

owned.hasTag(requestGameplayTag("Ability"));            // true
owned.hasAll(makeGameplayTagContainer(["Ability", "Character"])); // true
owned.hasAnyExact(makeGameplayTagContainer(["Ability"]));         // false

Membership checks are O(1): containers index explicit tags and their implicit parents by key.

Filtering

filter keeps tags that hierarchically match the other container; filterExact requires exact hits:

ts
const abilityTags = owned.filter(makeGameplayTagContainer(["Ability"]));
abilityTags.toJSON(); // ["Ability.Element.Fire"]

owned.filterExact(makeGameplayTagContainer(["Ability"])).toJSON(); // []

Mutation

ts
owned.addTag(requestGameplayTag("Character.State.Stunned"));
owned.removeTag(requestGameplayTag("Character.State.Stunned"));
owned.appendTags(otherContainer);
owned.removeTags(otherContainer);

// addLeafTag replaces explicit parents the new leaf makes redundant:
const container = new GameplayTagContainer(requestGameplayTag("Note.Status"));
container.addLeafTag(requestGameplayTag("Note.Status.Draft"));
container.toJSON(); // ["Note.Status.Draft"]

Containers never hold duplicates and treat tag names case-insensitively, mirroring Unreal's FName semantics.

Serialization

Containers serialize as plain arrays of tag names, so they embed cleanly in any payload:

ts
JSON.stringify({ actorId: "mage-01", gameplayTags: owned });
// {"actorId":"mage-01","gameplayTags":["Ability.Element.Fire","Character.Class.Mage"]}

const restored = requestGameplayTagContainer(saved.gameplayTags, false);

The second argument of requestGameplayTagContainer controls whether unknown tags throw (true, default) or are skipped (false).