How-To: Author Styles in the Registry Editor
Goal: author a registry's styles and modifiers in the asset's own editor - picking every identifier from a dropdown instead of typing it from memory - preview the motion each row resolves to, and validate the whole asset before you hit play.
1. Open the registry
Double-click the registry asset. It opens in its own editor rather than the stock details panel: a tree on the left, details in the middle, and a Pop Preview on the right.
Images/CrimsonCombatText/howto_registry_editor.png| Tree section | What it lists |
|---|---|
| Styles | One row per Styles key, sorted by tag |
| Default Style | The single DefaultStyle used when nothing matches |
| Modifiers | One row per Modifiers key, badged with its ApplyOrder |
| Advanced Rules | One row per AdvancedRules entry, badged with its Priority |
| Sets | The asset references: MotionSet, PlacementSet, FilterSet, and the authoring text style tables |
Selecting a row shows only that row in the details pane, so you are editing one style at a time instead of scrolling a nested map. Row badges summarize what the row resolves to - its motion preset or inline, its placement or target, agg 0.50s for a row that declares its own time window, scope:Stagger for one that declares its own stagger window, summary:Summary for one that names a ScopeSummaryStyle, ledger:Damage for a row that inherits both window and scope from an ancestor, no ledger for a row under an aggregating ancestor that clears bInheritAggregation, and hold for a row whose AggregationDisplay is HoldWhileScoped. A badge turns amber when what it names is missing - a motion preset that is not in the set, a summary key that is not a Styles row - which is the same thing validation will tell you.
Styles keys under Styles. Clicking one swaps the details pane to that style alone, and clicking another swaps it again with no scrolling.Start from a worked example
Before authoring anything by hand, press Add Examples in the toolbar - or Add Example Styles on the asset itself, in the stock details panel. It writes a complete worked set: 11 styles, 2 modifiers and 2 advanced rules, every one of them illustrating a decision you are about to make yourself.
| Key | Text row | Motion | What it shows |
|---|---|---|---|
Damage | Default | FloatUp | The parent row, and the ledger root for the whole damage branch. Damage.Type.Physical has no row of its own on purpose and falls back to this one - hierarchical lookup doing its job. It carries every accumulation feature at once: AggregationWindow 0.6, so an attacker's hits climb one number on that attacker's own screen, and ScopeTag Status.Stagger with ScopeSummaryStyle Damage.Summary. Every more specific Damage.* row declares no window of its own and inherits this one, so Fire, Blocked and Poison hits all accumulate here |
Damage.Summary | Immune | CritPunch | The scope's payoff, and nothing else sends it. Its format is a plain <{ValueStyle}>{Value}</>. It sets bInheritAggregation to false, which keeps the payoff out of the ledger it is closing - a payoff that inherited its own root would feed the run it just paid off |
Damage.Type.Fire | Fire | ArcOut | The simplest child row: it changes the color and nothing else |
Damage.Type.Ice | Ice | Shiver | Two styles pointing at one preset |
Damage.Type.Lightning | Lightning | Shiver | The other half of that pair - retune Shiver once and both elements follow |
Damage.Type.Poison | Poison | Drift | No window of its own, deliberately: poison ticks join Damage's ledger and climb the same number as everything else hitting that target, instead of stacking a column of 3s. Author an AggregationWindow on this row and poison splits off into a number of its own again - that one field is the whole choice |
Damage.Heal | Heal | Pulse | PlacementPreset TargetBelow, so healing reads under the target while damage reads over it. It is also the one child row with a root of its own - AggregationWindow 0.6 - so healing climbs a separate number instead of adding into the damage total |
Damage.Result.Miss | Miss | Hover | A word rather than a number: {Value} never appears, so the format string carries the text |
Damage.Result.Immune | Immune | Hover | Shares Hover with Miss, and PlacementPreset ScreenNotification sends it to the HUD instead of over the target |
Damage.Result.Blocked | Default + Block prefix | Sink | The number falls away, because the blow did not land. A fully blocked hit is worth 0 and still joins Damage's ledger: it bumps the hit count and punches the running number without changing the figure or its look |
Damage.Result.Absorbed | Default + Absorb prefix | Sink | Shares Sink with Blocked - the third pair of styles on one motion |
Damage.Result.Crit (modifier) | Crit | CritPunch override | A modifier, not a style: 1.4x scale and a ! wrapper layered onto whatever style matched, so one row covers every damage type |
Damage.Result.ShieldBroken (modifier) | Block (on its companion) | Hover (on its companion) | A pure companion pop: every field that would change the number is left at its default, bSpawnCompanionPop is on, and the CompanionPop reads "Shield Broken" at offset (0, -36) so it sits above the figure. A shield breaking happened because of the damage rather than being a property of it, so it reads better as a second pop than as a wrapper |
Advanced rule, Priority 10 | Heal | CritPunch | A critical heal: the rule picks the base outright (plus sign, below the target, CritPunch) instead of the Damage.Heal row. The Crit modifier still layers on top - modifiers always apply after the base is chosen |
Advanced rule, Priority 5 | Default + Block prefix | Sink | An elemental hit that was blocked: blocked wins over the element, which the tag map cannot decide because both keys sit at the same depth |
AdvancedRules is empty - so pressing the button twice does nothing the first press did not already do. The Damage tags are shipped by CrimsonCombatText so the set resolves in a project with no damage taxonomy of its own; swap them for yours and the same rows keep working.PlacementPreset tags, so point the registry's PlacementSet at a placement set - the shipped DA_CrimsonCombatTextPlacements (/CrimsonCombatText/) already carries both rows - or validation (step 6) reports them as errors and both pops fall back to the default anchor.Damage.Result.Crit and Damage.Result.ShieldBroken under Modifiers - the second badged companion - and two rows under Advanced Rules badged 10 and 5. Selecting Damage shows AggregationWindow 0.6, ScopeTag Status.Stagger and ScopeSummaryStyle Damage.Summary - which the tree badges as agg 0.60s, scope:Stagger and summary:Summary. Damage.Type.Poison shows an empty AggregationWindow and is badged ledger:Damage; Damage.Summary shows bInheritAggregation unticked and is badged no ledger.2. Add a style or a modifier
Use the toolbar: Add Style, Add Modifier, Add Rule, Duplicate (copies the selected row to a new key) and Delete. Adding or duplicating opens a tag picker for the new key. Every edit is a transaction, so Ctrl+Z puts back a row you deleted.
Styles, Modifiers and ScopeTag match the tags your game already sends - Damage.Fire, Crit, State.Stagger - so constraining them would mean re-tagging your damage system to use the plugin. Only the plugin's own subtrees (CrimsonCombatText.Motion, CrimsonCombatText.Placement, CrimsonCombatText.Filter) are restricted.3. Fill the fields from dropdowns
Seven fields on a style, and three on a modifier, are identifiers that have to match something authored elsewhere. Each is a dropdown of what actually exists:
| Field | On | Offers |
|---|---|---|
PrefixStyleTag, ValueStyleTag | Style | Row names across the registry's TextStyleTables (step 4) |
AnimationName | Style | Animations on the widget at WidgetClassPath in Project Settings -> Crimson -> Crimson Combat Text |
MotionPreset | Style | Preset tags in the registry's MotionSet |
PlacementPreset | Style | The PlacementId of every row in the registry's PlacementSet |
ScopeSummaryStyle | Style | Keys of this registry's own Styles map |
ValueStyleTag | Modifier | The same rich text row names, enabled by the inline override checkbox |
MotionPresetOverride | Modifier | The same motion preset tags |
PlacementPresetOverride | Modifier | The same placement ids |
Every list is gathered fresh each time you open a combo, so a preset you just added in the motion set is offered immediately. (None) is always the first entry, because clearing a field has to be as easy as setting one, and a text style row's tooltip names the table that defines it.
(not in list) - rather than silently replaced. That is a typo or a row you deleted, and at runtime it falls back (to the inline motion, to the default anchor, to the widget's default text style) instead of doing what the field says.MotionSet assigned, no PlacementSet, no TextStyleTables or no widget class, the field falls back to its ordinary editor plus an amber hint naming exactly what to set. Nothing is hidden and nothing is blocked - you can always type a value the dropdown has no way to know about.bSpawnCompanionPop on a modifier and a complete FCrimsonCombatTextStyle opens up beneath it - its own format string, text rows, motion preset, placement and aggregation window, each with the same dropdowns and the same validation as a Styles row. Leave every other field of the modifier at its default and you get a pure companion: a second pop beside the number rather than a change to it. See Concept: Style Resolution, Motion & Rendering -> Companion pops.4. List your rich text table so row names can be picked
Select Sets in the tree and add the rich text style table your combat text widget uses to Text Style Tables (Authoring) - DT_NumberPop_TextStyle (/CrimsonCombatText/) if you are using the shipped widget. Its row names then fill the PrefixStyleTag / ValueStyleTag dropdowns, and validation can check them.
TextStyleTables is authoring convenience and is never read when the game runs. A pop resolves its row names against the table its own RichTextBlock carries in TextStyleSet - listing the table here only teaches this asset which names are legal. Point it at the same table the widget uses, or the dropdown will offer names the widget cannot draw.ValueStyleTag on a style now opens a list containing your table's row names (Default, Crit, Heal, Fire, Ice, Poison, Lightning, Miss, Block, Immune and Absorb for the shipped table) instead of a blank name field. Clear the array again and the field reverts to a plain name box with an amber hint.5. Preview the pop, in either editor
The registry editor's Pop Preview shows the pop itself, not just its path. It creates the widget from WidgetClassPath inside the editor and drives it through the same ApplyCombatText and UpdateView calls the game uses, so what you see is the rich text with the widget's own style table, the motion the row resolves to (the MotionSet preset when MotionPreset is set and the set has that row, otherwise the inline Motion), the value-emphasis scale and tint, and the widget animation named by AnimationName when the widget has one. Its inputs are a Value and a Concurrent hits count; a row that accumulates - its own window or an inherited one - shows one merged total instead of a fan, holding at MergeHoldAlpha and punching on a cycle exactly as it will in game, no rewind and no restart. Select a modifier and it previews that modifier layered on the Default Style - unless the modifier spawns a companion pop, in which case it previews the companion, which is the only pop that modifier actually produces. Placement is not simulated - the crosshair is the anchor. With no widget class configured, it falls back to a Slate rich-text render using the registry's first Text Style Table.
To change how a motion feels, edit the preset itself: double-click the motion set. It opens the same way - a tree of preset tags, details for the selected preset, and an animated preview - with a toolbar of Add Preset, Duplicate, Delete and Add Built-Ins, and a tag picker filtered to CrimsonCombatText.Motion.
Images/CrimsonCombatText/howto_motion_set_editor.pngMotionPreset is CrimsonCombatText.Motion.ArcOut previews an arc; clearing that preset previews the row's inline Motion instead. In the motion set editor, raising RiseStaggerPerSlot visibly ladders the preview's end heights - see How-To: Author Motion Presets.6. Validate the asset
Right-click the registry in the Content Browser -> Asset Actions -> Validate Assets (a project-wide validation pass runs the same checks). It catches what a tag map cannot:
| Check | Result |
|---|---|
MotionPreset / MotionPresetOverride set, but there is no MotionSet - or it is not a row in it | Error |
PlacementPreset / PlacementPresetOverride set, but there is no PlacementSet - or no row carries it as its PlacementId | Error |
ScopeSummaryStyle that is not a key in Styles | Error |
ScopeSummaryStyle naming a row that inherits back into the same ledger root - a payoff that would re-enter the run it closes | Error |
PrefixStyleTag / ValueStyleTag that no listed table defines | Warning |
Every message names where it came from - Styles[Damage.Fire], DefaultStyle, Modifiers[Crit], AdvancedRules[0].Style - and which field is wrong, so you can go straight to that row in the tree.
TextStyleTables means "not set up", not "every row name is wrong", so row names are skipped and the pass says so once as an info message. The summary-style error is about a real loop: a payoff that inherits the root it is closing would feed the very run it just ended, so give the summary row bInheritAggregation false or a window of its own.MotionPreset at a preset you then delete from the motion set, revalidate, and the error names that style's key. Fix it from the dropdown and the pass comes back clean.7. Confirm it resolves the way you authored it
Validation proves the identifiers exist. It does not prove the right row wins - that is tag resolution, and it is otherwise invisible.
In PIE, open the Dev Tools (Tools -> Combat Text Dev Tools, or Crimson.CombatText.DevTools) and use the Resolver tab: type a tag container and it names the winning Styles row, the Modifiers that layered on and in what order, the motion that came out, the ledger root the row accumulates under (its own, inherited from a named ancestor, or none) and which figure it shows. See How-To: Test Pops with the Dev Tools.
To do the same from a graph: Make Gameplay Tag Container (your event's tags) -> Resolve Style (Target = a reference to your registry asset), then break the returned struct and read Style Key and Style -> Motion Preset.
Damage.Fire + Crit names your Damage.Fire row (or its closest authored parent) and shows the Crit modifier applied, with the motion preset the modifier overrode to. Resolution reads only the asset, so the server resolving a hit's ledger root and a client resolving its look reach the same answer - what you see here is what every player gets.See also
- How-To: Author Motion Presets - the two new end-height fields, and shaping curves
- How-To: Test Pops with the Dev Tools - the Resolver tab, and firing bursts at a target
- Concept: Style Resolution, Motion & Rendering - the six resolution steps, and the end-height ladder
- API Reference -
UCrimsonCombatTextRegistry,FCrimsonCombatTextStyle,FCrimsonCombatTextStyleModifier