Performance Best Practices
Following these practices keeps frame rate stable and CPU cost low in complex text animation scenarios.
1. Control Glyph and Layer Scale
Glyph Count
Each additional glyph adds layer count × non-empty channel count curve evaluations per frame. Recommendations:
- Simple UI text (labels, buttons): < 30 glyphs
- Title animations: < 80 glyphs
- Long text (dialogs, descriptions): consider segmentation, < 150 glyphs per segment
- Extreme scenarios (live danmaku, etc.): stay within 500 glyphs and use widget pooling
Layer Count
Each additional layer adds evaluations for the corresponding channels per glyph per frame. Recommendations:
- Single-effect animations: 1 layer
- Standard multi-effects: 2–3 layers (preset library defaults to ≤ 3)
- Complex animations: ≤ 5 layers
Above 5 layers, consider merging effects into one layer (using Multiply modulation rather than separate layers).
2. Scheduling Early Exits
FBakedGlyph::Evaluate has three built-in skip mechanisms to maximize the share of “inactive” glyphs:
bIsStatic— static glyphs directly returnDefaultState(zero curve evaluation)- Not started (
LocalTime < 0) — returns the identity state without curve evaluation - Finished (
HasFinished) — evaluation skipped as well
Design suggestion: configure scheduling delays and skip flags so most glyphs are in a “not started / finished” state at any moment:
Layer->SetStartDelayAt(-1, 0.0f); // base start delay
Layer->SetDelayAt(-1, 0.05f); // per-glyph stagger → typewriter-style active window
Layer->SkipAt(-1); // skip all first
// restore the glyphs that need animation by classification
for (int32 i = 0; i < Layer->GetGlyphCount(); ++i)
{
if (!Layer->IsWhitespaceAt(i))
{
Layer->SetSkipAnimationAt(i, false);
}
}
3. Widget Pooling
Frequently creating/destroying UAnimatedTextBlock / UAnimatedRichTextBlock creates GC pressure and bake overhead. Consider a widget pool:
// Pooled usage pattern (with UAnimatedTextBlock as an example)
UAnimatedTextBlock* Widget = Pool->Dequeue(); // take from pool
Widget->Stop(); // reset state
Widget->SetText(FText::FromString(NewText));
Widget->SetBlueprintVariable(TEXT("Speed"), 2.0f); // set parameters
Widget->Play(); // play
// when done
Widget->Stop();
Pool->Enqueue(Widget); // return to pool
TIP
Size the pool for the peak number of simultaneously displayed animated texts; 32–64 is usually enough for UI scenarios. When widgets reuse the same Blueprint, each instance isolates parameters independently through
ParameterOverrideswithout interference.
4. Precompilation & Fingerprint Cooldown
- All animation curves are baked on demand (initialization / text changes / Blueprint recompile) by
FAnimationCompiler::BakeAllStages— zero bake overhead at runtime - Dual fingerprints (
TextFingerprint+BlueprintFingerprint) skip baking on cache hits - Re-bake cooldown
MinFramesBetweenRebakes = 3: do not modify parameters every frame — baking is throttled during parameter sweeps, but continuous modification still causes repeated bakes. OnePlayafter batch parameter changes is more efficient - Compile all
UTextAnimationBlueprints before shipping to avoid first-run bake hitches
5. Rich Text Entry Strategy
Each <anim> tag region of UAnimatedRichTextBlock bakes and plays independently; simultaneous multi-entry playback grows linearly in cost:
- Centralize tag mappings in a
UTextAnimationDataAsset - Split long dialogs into short segments, using
bSequentialPlayback(sequential playback) instead of playing everything at once - Set
bHideFirstFrameon entries not yet executed to avoid first-frame flashes during sequential playback - Avoid many distinct animation Blueprint entries — reusing one Blueprint with entry-level parameter overrides is more memory-efficient than many separate Blueprints
6. Batching Recommendations
UAnimatedTextBlock inherits UMG’s standard text widgets; rendering is batched automatically by Slate:
- Text with the same font and material batches into few draw calls automatically
- Avoid modifying font properties during animation — font changes break batching
- For multi-font effects use Composite Font instead of stacking multiple widgets
- Per-glyph transforms (translation/scale/rotation) break batching — concentrate transform animations on a small number of widgets
7. Loops & Long Animations
- Prefer layer loops:
SetPlayMode(direction, speed, NumberOfLoops)’sNumberOfLoops(0 = infinite) wrapsLocalTimewith fmod at evaluation, no need to bake very long animations - Infinite-loop layers: with
NumberOfLoops = 0,GetEffectiveDurationreturnsFLT_MAX(the stage never ends) — mind the overall duration semantics when mixing infinite-loop layers with finite ones - Animation clipping (
SetAnimationClipAt) trims unused front/back curves, reducing key counts
TIP
Performance optimization priority: reduce glyph count (
SkipAtto skip whitespace) → reduce layer count → reduce non-empty channel count (delete unused tracks) → reuse Blueprints + entry-level parameters → batch widgets.