How-To: Switch to the Batched Niagara Renderer
FormatString markup, {Prefix} and {Custom} are stripped. Take this path only when you genuinely need hundreds of simultaneous numbers and can give up formatting to get them.When it is worth it
| Renderer | Cost | Rich text | Use when |
|---|---|---|---|
CrimsonCombatTextRenderer_Widget (default) | One pooled UUserWidget per instance | Yes | Almost always |
CrimsonCombatTextRenderer_Niagara | One Niagara system for everything | No | ARPG / bullet-hell densities |
This renderer needs two assets that cannot ship inside the plugin - a Niagara system and a glyph-atlas material. Everything below is the contract the C++ renderer pushes data against; build the assets to match and it works.
1. Glyph atlas texture
A single texture divided into a grid of equal cells, one glyph per cell, read in row-major order (left to right, top to bottom). Cell order must be exactly this:
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19glyph: 0 1 2 3 4 5 6 7 8 9 . , - + K M B T ! ?
A 5x4 grid of 20 cells fits exactly. Characters outside this set are skipped rather than drawn as a placeholder, so unsupported text simply does not appear.
UCrimsonCombatTextRenderer_Niagara::GetGlyphAtlasOrder() returns this exact string. If you lay your atlas out differently, change that string to match - do not guess, the two must agree or every glyph renders as the wrong character.2. Material
A translucent, unlit, camera-facing sprite material that:
- samples the atlas using the particle's SubUV / sub-image index as the cell selector
- multiplies by the particle color - the renderer supplies the per-instance tint here
- multiplies alpha by the particle alpha - the renderer supplies the opacity curve output here
- ignores depth, if you want text to read through geometry (typical for combat text)
3. Niagara system
Name it whatever you like; the renderer only cares about the parameter contract.
Required user parameters
All arrays are parallel and hold one entry per glyph, not per number - a 1234 contributes four entries. Index them by particle ID.
| Parameter | Type | Meaning |
|---|---|---|
GlyphPositions | Position array | World position of this glyph, motion already applied |
GlyphIndices | int32 array | Atlas cell index (see the order above) |
GlyphColors | Color array | Per-instance tint, repeated for each of its glyphs |
GlyphScales | float array | Final scale, repeated for each of its glyphs |
GlyphAlphas | float array | 0..1 opacity, repeated for each of its glyphs |
GlyphCount | int32 | Number of valid entries; the arrays may be longer |
Emitter behaviour
Spawn exactly GlyphCount particles, or keep a fixed pool and cull above GlyphCount. Then per particle, with i as the particle index:
- position =
GlyphPositions[i] - sub-image index =
GlyphIndices[i] - color =
GlyphColors[i], alpha =GlyphAlphas[i] - size = base sprite size *
GlyphScales[i]
bAutoDestroy = false and lives for the whole session.4. Wire it up
- Create a Blueprint subclass of
CrimsonCombatTextRenderer_Niagara. - Assign your system to its
NiagaraSystemproperty. - Tune
LayoutUnitsToWorldandGlyphAdvanceuntil motion and spacing read correctly at your typical combat distance. - Point Project Settings > Crimson > Crimson Combat Text > Renderer Class at that Blueprint.
InitializeRenderer returns false and the subsystem uses the widget renderer instead, with a warning in the log. You get working combat text and a clear message, never an empty screen.Known limitations
| Limitation | Detail |
|---|---|
| No rich text | SupportsRichText() returns false. Markup, {Prefix} and {Custom} are stripped; the subsystem warns once when this renderer is active. |
| Motion is projected, not native | Motion is authored in screen-space layout units and projected onto the camera's right/up axes by LayoutUnitsToWorld. It will not track a screen-space path exactly under strong perspective. |
MaxGlyphsPerFrame truncates | Above the cap, remaining glyphs are dropped for that frame and a warning is logged. Truncation is never silent. |
See also
- Concept: Style Resolution, Motion & Rendering - where the renderer sits in the pipeline
- How-To: Author Motion Presets - motion is renderer-agnostic and authored the same way either path