Assignments and Gameplay Tags: One Gather Job Into Many

How a data asset of Gameplay Tag pairs turns a single resource-gathering loop into wood, stone, and anything else in Unreal Engine. Each assignment maps an order tag to a resource tag, a carried actor, a Behavior Tree, a home marker, and an attach socket.

Nothing in the gather loop is hard-coded to “wood”. A worker harvests whatever its current order points at, and orders are just rows in a data asset. This card is about that data asset, the contract that turns one loop into many jobs. It belongs to Resource Gathering AI in Unreal Engine, and follows on from the Gatherer component, which reads it.

One assignment is a small contract

The data asset is a UDataAsset holding an array of FAssignmentDefinition structs. Each struct describes one kind of job:

USTRUCT(BlueprintType)
struct FAssignmentDefinition
{
    GENERATED_BODY()

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Assignment")
    FGameplayTag AssignmentTag;        // the order the player issues, e.g. Assignment.Wood

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Assignment")
    FGameplayTag ResourceTag;          // what the worker searches for, e.g. Resource.Wood

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Assignment")
    TSubclassOf<AActor> ExtractedResourceActorClass;   // what it carries home

    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Assignment")
    UBehaviorTree* AssignmentBehaviorTree;             // how it behaves

    UPROPERTY(EditDefaultsOnly, Category = "Assignment")
    TSubclassOf<AActor> HomeActor;     // optional marker spawned at the drop point

    UPROPERTY(EditDefaultsOnly, Category = "Assignment")
    FName ResourceSocketName;          // skeleton socket the carried item snaps to
};

Six fields, and between them they cover the entire job: the order tag that starts it, the resource tag that finds the target, the actor that gets carried, the tree that runs, an optional banner or flag to mark home, and the socket the haul rides on.

The Assignment Definitions data asset with two entries, Wood and Stone, each showing the six assignment fields

Tags are the matchmaker

The two Gameplay Tags are the heart of it. The Assignment Tag is what the player passes to TryBeginAssignment; the Resource Tag is what the worker’s search compares against every harvestable actor in range. The lookup is an exact-tag match:

for (const FAssignmentDefinition& Assignment : AvailableAssignments)
{
    if (Assignment.AssignmentTag.MatchesTagExact(AssignmentTag))
    {
        OutFoundAssignmentDefinition = Assignment;
        return true;
    }
}

MatchesTagExact means Assignment.Wood does not also match Assignment.Wood.Oak; the tags you issue and the tags you store have to be the same. Once the order resolves, the search side does the symmetrical thing with the resource tag (covered in the Gatherer component):

if (ActorResourceComponent->ResourceTag != ActiveAssignment.ResourceTag) continue;

So a wood order can only ever harvest actors tagged Resource.Wood. The two tags are a clean separation: one is the intent a player expresses, the other is the label on the world actor, and the data asset is the table that ties a given intent to a given label.

The assignment fields detail view: assignment tag, resource tag, extracted resource actor class, behavior tree, home actor, and resource socket name

Adding stone is a row, not a rewrite

Because every job is data, a second resource type costs you one array element. The example asset ships with two:

  1. Wood: Assignment.Wood / Resource.Wood, carrying BP_Extracted_Wood_Pile, running BT_Extract_Resource, with a banner home marker.
  2. Stone: Assignment.Stone / Resource.Stone, carrying BP_Extracted_Stone, running the same tree, with its own banner.

To add a third (clay, ore, whatever), you add a row with a fresh tag pair and a carried actor, tag your new world actors with the matching resource tag, and issue the new assignment tag. The Behavior Tree can be shared, because it only ever calls the generic Gatherer API. The component even stops and restarts a shared tree on reassignment so the restart is clean:

if (UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(AIController->GetBrainComponent()))
    BTComp->StopTree(EBTStopMode::Safe);
AIController->RunBehaviorTree(ActiveAssignment.AssignmentBehaviorTree);

What to take away

  • An assignment is six fields: two tags, a carried actor, a tree, an optional home marker, and a socket name.
  • The Assignment Tag is the order; the Resource Tag is the world label; the data asset maps one to the other, matched exactly with MatchesTagExact.
  • New resource types are new rows. Nothing is hard-coded, and jobs can share a Behavior Tree.

Continue with The Resource component to see the other end of that tag match, the harvestable actor itself. The full system is Resource Gathering Minions on FAB.

Frequently asked questions

How does a gather order find the right resource?
By Gameplay Tag. Each assignment pairs an Assignment Tag (the order, e.g. Assignment.Wood) with a Resource Tag (e.g. Resource.Wood). The worker searches the world for actors whose Resource component carries the matching Resource Tag, so a wood order only ever harvests wood. The lookup uses MatchesTagExact, so tags must match exactly.
How do I add a second resource type like stone?
Add another element to the Available Assignments array with its own assignment and resource tags, its own carried actor class, and a Behavior Tree, then tag your stone actors' Resource component with the matching resource tag. Nothing is hard-coded to wood, so a new job is just a new tag pair.
What fields make up one assignment?
Six: Assignment Tag (the order), Resource Tag (what to search for), Extracted Resource Actor Class (what to carry), Assignment Behavior Tree (how to behave), an optional Home Actor (a marker spawned at the drop point), and Resource Socket Name (where the carried item attaches on the skeleton).
Can different jobs reuse the same Behavior Tree?
Yes. Wood and stone can both point at the same tree because the tree only calls the generic Gatherer API; the tag pair and carried actor are what differ. The component stops and restarts the tree cleanly on reassignment so a shared tree is safe.