Nameplates

Goal: give each kind of target its own nameplate - a plain bar over a trash mob, a distinct plate over an elite, and a screen-space boss bar in a fixed HUD slot - and define what happens when several targets want that slot at once.

Prerequisites
A UCrimsonIndicatorLayer widget in the active HUD layout (world-anchored plates render through it; without one nothing appears). Targets need a UCrimsonHealthComponent - that is the entire tracking filter. ACrimsonPlayerController already adds UCrimsonNameplateManagerComponent as a default subobject.

How presentation is chosen

The manager resolves one FCrimsonNameplatePreset per tracked target. A preset says both which widget to use and where it is drawn, so "boss enemies take the boss bar" is a single authored row rather than two parallel systems.

StepSourceWins when
Per-actor widget overrideICrimsonNameplateTarget::GetCrimsonNameplateWidgetOverrideIt returns a non-null class. Overrides the world widget only - placement and slot behaviour still come from the preset.
Registry rowUCrimsonNameplateRegistry::PresetsA classification tag matches. Hierarchical, most-specific-wins: Enemy.Type.Boss.Final -> Enemy.Type.Boss -> Enemy.Type.
DefaultUCrimsonNameplateRegistry::DefaultPresetNothing matched, or the target carries no classification tags.

1. Create the registry asset

Create a UCrimsonNameplateRegistry data asset and assign it to Registry on the nameplate manager in your player controller Blueprint. Fill DefaultPreset.WidgetClass with a Blueprint subclass of UCrimsonNameplateWidget. With no registry assigned the manager logs a warning in BeginPlay and creates nothing.

Preset fieldGroupPurpose
WidgetClassWorldWorld-anchored widget. Must implement ICrimsonIndicatorWidgetInterface - subclass UCrimsonNameplateWidget. Null means no world plate.
HeightOffsetWorldCentimetres above the capsule top the plate is anchored at.
MaxDisplayDistanceWorldPer-classification distance cull. 0 inherits the manager's value.
IndicatorPriorityWorldSort priority on the indicator layer. Higher draws in front.
bParticipateInScreenStackingWorldOpt into the indicator layer's vertical anti-overlap pass.
PlacementSlotWorld or Slot.
SlotTagSlotThe contended HUD slot, e.g. HUD.Slot.BossBar.
SlotWidgetClassSlotScreen-space bar. Subclass UCrimsonNameplateSlotWidget. A hard reference, so it loads with the registry and never hitches when a boss appears.
SlotPrioritySlotHigher wins the slot when more targets qualify than the policy allows.
SlotActivationDistanceSlotDistance within which the target becomes a slot candidate. 0 inherits MaxDisplayDistance.
Verify
Play. Every actor with a health component shows the default plate. If nothing appears, check the UCrimsonIndicatorLayer is in the HUD layout and watch the log for the missing-registry warning.

2. Classify your targets

Classification comes from ICrimsonNameplateTarget. ACrimsonNPCBase already implements it, so tagging an archetype is all a Crimson NPC needs: set EnemyTypeTags to Enemy.Type.Elite on its UCrimsonNPCData, then add an Enemy.Type.Elite row to the registry pointing at the elite widget.

Interface functionWhat ACrimsonNPCBase returns
GetCrimsonNameplateTagsNPCData->EnemyTypeTags
GetCrimsonNameplateWidgetOverrideNPCData->NameplateWidget
GetCrimsonNameplateDisplayNameNPCData->EnemyName (empty falls back to the actor name)
IsCrimsonNameplateClassificationReadyNPCData != nullptr

Any other actor - a player character, a destructible - can implement the interface itself. All four functions are BlueprintNativeEvent, so a Blueprint actor can answer them without C++.

On the actor Blueprint: Class Settings -> Interfaces -> Add -> Crimson Nameplate Target. Then in My Blueprint -> Interfaces, right-click each event and Implement event:

  • Get Crimson Nameplate Tags -> return a Make GameplayTagContainer holding your classification tag.
  • Is Crimson Nameplate Classification Ready -> return true once whatever replicated variable backs your tags has arrived.
  • Get Crimson Nameplate Display Name -> return your authored name, or an empty text to keep the actor name.
  • Get Crimson Nameplate Widget Override -> leave returning null unless this one actor needs a bespoke plate.
Answer from replicated state, never from loose gameplay tags
Nameplates are built independently on every client, so the interface runs on simulated proxies. ACrimsonNPCBase::SetNPCData also pushes EnemyTypeTags into the ASC with AddLooseGameplayTags, but that path is server-only and does not replicate - an ASC tag query would classify every NPC as untyped on clients. That is exactly why the implementation reads the replicated NPCData pointer instead.
Late classification is expected
On a client the data backing the answers replicates after the actor spawns, so the manager retries IsCrimsonNameplateClassificationReady on its update tick and rebuilds the nameplate once the answer changes. A rebuild rather than a re-skin is required: the indicator layer reads the widget class once when the indicator is added and pools widgets by class.
Verify
Play As Client with 1 server + 1 client. The elite plate must appear on the client too, not only on the listen server. Seeing the default plate on the client and the elite plate on the server means your classification is coming from server-only state.

3. Add a boss bar in a HUD slot

Set a preset's Placement to Slot, give it a SlotTag (HUD.Slot.BossBar ships registered), and set SlotWidgetClass to a Blueprint subclass of UCrimsonNameplateSlotWidget. Two HUD steps make the slot real:

  1. Place a UCrimsonUIExtensionPointWidget in the HUD layout with ExtensionPointTag set to the slot tag. Use a vertical box if the slot stacks more than one bar.
  2. Add UCrimsonNameplateViewModel to that widget's DataClasses. The extension point rejects data whose class is not listed.
Missing DataClasses is the silent failure
With UCrimsonNameplateViewModel absent from DataClasses the extension never passes the point's contract, so the bar simply never appears - no error, no log. Check this first.

UCrimsonNameplateSlotWidget derives from UCrimsonNameplateWidget, so a boss bar is authored exactly like a world plate: the same On Health Visual, On Name Visual, On Team Color Visual, On Death Visual, and On Type Tags Visual events, driven by the same view model. Distance scaling defaults to off, since a screen-space bar has a fixed size.

One slot, a different bar per boss
The view model is the extension payload, and it reports the resolved SlotWidgetClass through ICrimsonUIExtensionPayloadInterface. That is how a single HUD.Slot.BossBar point hosts a different bar per classification without any per-boss wiring.
Verify
Walk a boss-tagged NPC into range. The bar appears in the slot and its world plate disappears. Kill it and the bar retires on the next update tick.

4. Decide what happens when two bosses show up

Each slot tag has a FCrimsonNameplateSlotPolicy, authored in the registry's SlotPolicies. A slot with no row uses the defaults: one occupant, overflow falls back to world.

Policy fieldDefaultEffect
MaxConcurrent1How many bars the slot shows at once. 1 is a single boss bar; 2-3 stacks a multi-boss encounter.
bOverflowFallsBackToWorldtrueTargets that qualified but lost the slot keep their world plate. Turn off for a cleaner screen at the cost of a nearby second boss having no health readout.

Occupants are picked every update tick by a fixed sort:

text
ForcePromote first
-> SlotPriority descending
-> incumbent first
-> distance ascending
take the first MaxConcurrent; the rest fall back to their world nameplate
Why incumbents are favoured
Without the incumbent bias a target sitting right on SlotActivationDistance would swap with a rival on every update and the bar would flicker several times a second. Once a target holds the slot it keeps it until it becomes ineligible or a strictly higher-priority candidate turns up.
Verify
Spawn three boss-tagged NPCs with MaxConcurrent = 1. Exactly one bar shows; the other two show world plates. Walk back and forth across the activation distance - the bar must not flicker. Raise MaxConcurrent to 3 and confirm the stack order follows SlotPriority.

5. Drive the bar from an encounter (optional)

Distance-based promotion is convenient, but most boss fights want the bar tied to the encounter - a fog gate, an arena trigger, a cutscene ending. SetSlotOverride does that, and applies immediately rather than waiting for the next update tick.

OverrideEffect
ForcePromoteAlways a slot candidate, and outranks every automatic candidate for that slot.
ForceDemoteNever a slot candidate, whatever the preset says.
NoneBack to automatic distance-based promotion.

In the arena trigger Blueprint: Event ActorBeginOverlap -> Get Player Controller (0) -> Get Component by Class (Crimson Nameplate Manager Component) -> Set Slot Override (Target = the boss actor, Override = Force Promote). Call it again with None when the encounter ends. Guard the whole chain on Is Local Player Controller - nameplates are a local concern, so run this on each client, not on the server.

Verify
With a closer, equally-tagged boss already holding the slot, ForcePromote the far one. The bar switches to it immediately and stays there until you clear the override.

6. Show a bar for the lock-on target (optional)

Tick bPromoteLockOnTargetToSlot on the manager and set LockOnSlotTag (HUD.Slot.TargetBar ships registered). The current lock-on target - hard, else soft - is pushed into that slot whatever its classification, using its preset's SlotWidgetClass or the registry default's.

This is a separate single-occupant slot, not part of the contention above. It is skipped for a target that already holds an arbitrated slot, so a boss never gets two bars, and it leaves the world plate alone - the target bar is supplementary. Place a second UCrimsonUIExtensionPointWidget for the tag, again with UCrimsonNameplateViewModel in its DataClasses.

The lock-on state lives on the pawn
UCrimsonLockOnComponent sits on the camera-owning pawn, not the controller, so the manager re-resolves and rebinds it on OnPossessedPawnChanged. Nothing extra is needed on your side.
Verify
Lock onto a normal enemy: the target bar appears and its world plate stays. Release the lock and the bar clears. Lock onto a boss that already holds the boss bar and no second bar appears.

Manager settings

PropertyDefaultPurpose
RegistrynullThe UCrimsonNameplateRegistry driving presentation. Required.
MaxDisplayDistance3000 cmDistance cull for world plates. 0 disables culling. A preset can override it.
bShowSelfNameplatefalseShow a plate over the local player's own pawn.
bShowWhenDeadfalseKeep the plate after the target dies.
UpdateInterval0.25 sHow often visibility, distance, pending binds, pending classification, and slot arbitration are evaluated. Read once in BeginPlay.
bPromoteLockOnTargetToSlotfalsePush the lock-on target into LockOnSlotTag.
LockOnSlotTagnoneSlot used for the lock-on target bar.
FunctionPurpose
ClearAllNameplatesStops tracking everything and removes all indicators and slot registrations.
RefreshNameplatesRebuilds the tracked set from scratch. Useful after a level transition or a team change.
SetSlotOverrideForces a target into or out of its HUD slot. Applies immediately.
IsTargetInSlotTrue while the target occupies an arbitrated HUD slot.

Multiplayer

  • The manager is gated on IsLocalController() and nothing it owns replicates. Every value comes off replicated health attributes and replicated classification data, so it is correct on simulated proxies.
  • Slot registrations are made against the owning ULocalPlayer, so a split-screen viewport only receives its own bars.
  • On a simulated proxy the health component binds only once the attribute set has replicated. The nameplate stays hidden until it does, so a brief startup delay is expected rather than a bug.

See also

  • Camera Abilities - the lock-on abilities that drive the target bar.
  • CrimsonUI -> Concept: UI Extension Points - how a HUD slot receives and spawns widgets.
  • CrimsonCamera -> Concept: Shared Camera State - lock-on target selection.