How-To: Write Your Own Placement

Goal: position combat text by rules the built-in placements do not cover - orbiting the target, snapping to a grid, tracking a spline, following the mouse.

Prerequisites
You completed How-To: Position Pops and have a placement set assigned to your registry.

1. Understand the contract

A placement answers one question - where does this pop sit - and owns its own projection. The subsystem never projects anything itself, which is why a placement is free to be screen-native.

Entry pointWhenJob
AcquirePlacementOnce, at spawnCache what to follow and produce the first position. Return false to decline the pop
UpdatePlacementEvery frame, while bUpdateEveryFrameReturn this frame's state
Placements must be stateless
One placement object is SHARED by every pop that resolves to it. Both entry points are const for that reason. Anything you need to remember about one particular pop goes in FCrimsonCombatTextPlacementState, which is handed back to you each frame - never in a member variable.

2. Know the two structs

FCrimsonCombatTextPlacementContext is what you are told - target and instigator actors, the event's world location, the style key, the value, the viewing player, viewport size and DPI scale, and the pop's age.

Do not store the context
Its actor pointers are raw and are rewritten every frame. Read it inside the call you received it in and nowhere else.

FCrimsonCombatTextPlacementState is what you return. The fields that matter most:

FieldPurpose
SpaceWorld or Screen. Screen skips camera-distance culling, because a HUD pop must not vanish when its cause is far away
ScreenPositionThe final anchor in layout units. Always fill this, in both spaces
WorldAnchorOnly meaningful in World space. Drives the distance curve and world-space renderers
bOnScreenfalse hides the pop this frame
bUpdateEveryFramefalse freezes this state for the rest of the pop's life
SpreadOwner / SpreadGroupWhat this pop fans against. Pops sharing both deflect away from each other
UserVector / UserScalarScratch for anything you sample once at spawn. The plugin ignores them

3. Subclass and implement

Both entry points are BlueprintNativeEvent, so a Blueprint-only project can write a placement with no C++ at all. UCrimsonCombatTextPlacementLibrary carries the maths.

Content Browser -> Blueprint Class, search for CrimsonCombatTextPlacement, and pick either the abstract base or an existing placement to extend.

In the graph, override Acquire Placement and Update Placement. Both hand you a Context and expect a State back; Acquire Placement also returns a bool - return false to decline the pop and let the fallback chain try.

Typical Update chain: Get Actor Anchor Location (Target Actor from Context) -> Project World To Layout (Context) -> set Screen Position on the State -> return it.

Available on UCrimsonCombatTextPlacementLibrary: Project World To Layout, Clamp To Viewport Edge, Viewport Anchor To Layout, Get Actor Anchor Location.

Verify
The class appears in the placement set's row class picker.

4. Add it to the placement set

Add a row to your placement set, choose your class, and give it a Placement Id under CrimsonCombatText.Placement. Point a style's Placement Preset at it.

Ids must be unique
Two rows carrying the same PlacementId make the later one unreachable. The asset logs a warning naming the duplicate when that happens.
Verify
Route a style to your id and check the dev tools Resolver tab - the Placement policy: line shows your class's description.

See also

  • How-To: Position Pops - the built-in world placements.
  • How-To: Show Pops on the HUD or a Health Bar - the built-in screen and widget placements.
  • API Reference -> UCrimsonCombatTextPlacement - the full contract.