CrimsonCommon · Lesson 4 of 7

Track Counts with Tag Stacks

Beginner5 minRuntimeMultiplayer

Before you start

  • Completed: Adopt the Modular Base Classes
  • A replicated actor to own the stacks (a Player State is ideal)
Video coming soon

Chapters

One container, many counters

A tag stack is a replicated map of GameplayTag → count. FCrimsonTagStackContainer holds integer counts (ammo, charges, currency) and FCrimsonFloatTagStackContainer holds floats (buildup, meters). Both ride on a FFastArraySerializer, so only the stacks that changed replicate — not the whole container.

Declare and replicate

Add the container as a replicated property on a replicated actor and register it for replication:

cpp
// MyPlayerState.h
UPROPERTY(Replicated)
FCrimsonTagStackContainer TagStacks;
// MyPlayerState.cpp
void AMyPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AMyPlayerState, TagStacks);
}
The most common mistake
Forgetting the DOREPLIFETIME line compiles fine and silently never replicates — the server sees correct counts while every client reads zero. If clients disagree with the server, check this first.

Add, remove, read

Mutate on the server only: AddStack, RemoveStack, and ClearStacks (the float container adds SetStack). Read from anywhere — server or client — with GetStackCount and ContainsTag.

Blueprint uses the UCrimsonTagStackStatics function library — Add Tag Stack, Remove Tag Stack, Get Tag Stack Count, Contains Tag Stack — always behind a Has Authority branch when writing:

Add Tag Stack behind a Has Authority branch — clients only read.
Server writes only
A client-side write isn't an error — it's just overwritten by the next server delta, which makes it a confusing 'my value keeps resetting' bug. Gate every mutation on HasAuthority().