Renderer Architecture
Combat text has a uniquely spiky performance profile: a boss fight can spawn dozens of numbers in a single frame, while most of the game they sit idle. MageIt Combat Text solves this by not forcing you into a single rendering strategy — instead it ships three renderers behind one API, so you pick the right cost/flexibility tradeoff per damage type.
1. One Event, Three Backends
Section titled “1. One Event, Three Backends”Every renderer consumes the same FMageItCombatTextEvent struct through the same
RenderDamage entry point. This means you can register a cheap Niagara
renderer for high-frequency chip damage, and a fully animated UMG renderer for rare critical
hits or boss telegraphs — all routed automatically by FGameplayTag.
2. Renderer Comparison
Section titled “2. Renderer Comparison”| Renderer | Backend | Best for | Tradeoff |
|---|---|---|---|
| UMG | Pooled UUserWidgets | Rich animation, accumulating damage, custom widget logic | Higher per-instance cost; pool size caps concurrency |
| Niagara | GPU-driven SDF sprites | Hundreds of numbers on screen at once | Less per-number Blueprint customization at runtime |
| Slate | Direct SLeafWidget paint | Lightweight built-in pop/float/fade text with zero widget allocation | Visuals are code-driven, not designer-driven |
3. Efficient Architecture
Section titled “3. Efficient Architecture”ULocalPlayerSubsystem: By inheriting fromULocalPlayerSubsystem, the plugin avoids spawning invisible “Manager Actors” into your world. The subsystem exists solely in memory, tied directly to the player, and requires zero manual lifecycle management.- Lazy Renderer Instantiation: By default, renderers are only constructed the first time a
FGameplayTagneeds them (GetRenderer), and cached afterwards. A game that never triggers a Niagara-tagged event never pays for a Niagara renderer instance. Any renderer can opt out of this and warm up eagerly at subsystem startup instead, viabInitializeAtBeginPlay— useful for renderers whose first-use cost would otherwise cause a visible hitch mid-gameplay. - Widget Pooling: The UMG renderer pre-allocates a pool of widgets on
Initializeand reuses them viaReturnWidgetToPool, avoiding per-hitCreateWidgetallocations. An optionalMaxPoolSizeceiling lets the pool grow under pressure instead of silently dropping events. - Batched Niagara Updates: The Niagara renderer doesn’t push one Niagara array update
per damage event. It buffers events in
PendingEventsBatchand flushes the whole batch once perTick, keeping the number ofSetNiagaraArrayVector4calls constant regardless of how many hits land in a frame. - Cached Text Measurement: The Slate renderer caches
FSlateFontMeasureresults per unique rendered string inTextSizeCache, since damage numbers repeat heavily (lots of identical small integers) and text measurement is one of the pricier Slate calls. - Compile-Time Speed: The plugin’s C++ source code strictly adheres to the IWYU (Include What You Use) principle, utilizing forward declarations wherever possible, so adding this plugin to a C++ project will not bloat your compilation times.