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.
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.
| Step | Source | Wins when |
|---|---|---|
| Per-actor widget override | ICrimsonNameplateTarget::GetCrimsonNameplateWidgetOverride | It returns a non-null class. Overrides the world widget only - placement and slot behaviour still come from the preset. |
| Registry row | UCrimsonNameplateRegistry::Presets | A classification tag matches. Hierarchical, most-specific-wins: Enemy.Type.Boss.Final -> Enemy.Type.Boss -> Enemy.Type. |
| Default | UCrimsonNameplateRegistry::DefaultPreset | Nothing 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 field | Group | Purpose |
|---|---|---|
WidgetClass | World | World-anchored widget. Must implement ICrimsonIndicatorWidgetInterface - subclass UCrimsonNameplateWidget. Null means no world plate. |
HeightOffset | World | Centimetres above the capsule top the plate is anchored at. |
MaxDisplayDistance | World | Per-classification distance cull. 0 inherits the manager's value. |
IndicatorPriority | World | Sort priority on the indicator layer. Higher draws in front. |
bParticipateInScreenStacking | World | Opt into the indicator layer's vertical anti-overlap pass. |
Placement | Slot | World or Slot. |
SlotTag | Slot | The contended HUD slot, e.g. HUD.Slot.BossBar. |
SlotWidgetClass | Slot | Screen-space bar. Subclass UCrimsonNameplateSlotWidget. A hard reference, so it loads with the registry and never hitches when a boss appears. |
SlotPriority | Slot | Higher wins the slot when more targets qualify than the policy allows. |
SlotActivationDistance | Slot | Distance within which the target becomes a slot candidate. 0 inherits MaxDisplayDistance. |
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 function | What ACrimsonNPCBase returns |
|---|---|
GetCrimsonNameplateTags | NPCData->EnemyTypeTags |
GetCrimsonNameplateWidgetOverride | NPCData->NameplateWidget |
GetCrimsonNameplateDisplayName | NPCData->EnemyName (empty falls back to the actor name) |
IsCrimsonNameplateClassificationReady | NPCData != 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 GameplayTagContainerholding 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.
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.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.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:
- Place a
UCrimsonUIExtensionPointWidgetin the HUD layout withExtensionPointTagset to the slot tag. Use a vertical box if the slot stacks more than one bar. - Add
UCrimsonNameplateViewModelto that widget'sDataClasses. The extension point rejects data whose class is not listed.
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.
SlotWidgetClass through ICrimsonUIExtensionPayloadInterface. That is how a single HUD.Slot.BossBar point hosts a different bar per classification without any per-boss wiring.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 field | Default | Effect |
|---|---|---|
MaxConcurrent | 1 | How many bars the slot shows at once. 1 is a single boss bar; 2-3 stacks a multi-boss encounter. |
bOverflowFallsBackToWorld | true | Targets 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:
ForcePromote first-> SlotPriority descending-> incumbent first-> distance ascendingtake the first MaxConcurrent; the rest fall back to their world nameplate
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.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.
| Override | Effect |
|---|---|
ForcePromote | Always a slot candidate, and outranks every automatic candidate for that slot. |
ForceDemote | Never a slot candidate, whatever the preset says. |
None | Back 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.
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.
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.Manager settings
| Property | Default | Purpose |
|---|---|---|
Registry | null | The UCrimsonNameplateRegistry driving presentation. Required. |
MaxDisplayDistance | 3000 cm | Distance cull for world plates. 0 disables culling. A preset can override it. |
bShowSelfNameplate | false | Show a plate over the local player's own pawn. |
bShowWhenDead | false | Keep the plate after the target dies. |
UpdateInterval | 0.25 s | How often visibility, distance, pending binds, pending classification, and slot arbitration are evaluated. Read once in BeginPlay. |
bPromoteLockOnTargetToSlot | false | Push the lock-on target into LockOnSlotTag. |
LockOnSlotTag | none | Slot used for the lock-on target bar. |
| Function | Purpose |
|---|---|
ClearAllNameplates | Stops tracking everything and removes all indicators and slot registrations. |
RefreshNameplates | Rebuilds the tracked set from scratch. Useful after a level transition or a team change. |
SetSlotOverride | Forces a target into or out of its HUD slot. Applies immediately. |
IsTargetInSlot | True 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.