Text Animator

The Text Animator (UTextAnimator) is Texturge’s runtime animation pipeline engine. It runs as a standalone UObject, independent of Actor or ActorComponent lifecycles, held by Slate widgets and driven frame-by-frame via Tick().

Responsibilities

Input                          Processing                     Output
──────────────────────────────────────────────────────────────────────
PlainText / RichText    →    ExecutePipeline()          →   FAnimationFrameData[]
TextAnimationBlueprint  →    Compile AnimInstance       →   DisplayRichText
AnimationDataAsset      →    Build RenderTree           →   OnCharacterRevealed delegate
PlayMode / CPS / Speed  →    AdvanceFrame()             →   OnAnimationComplete delegate

Two Pipelines

The animator supports two mutually exclusive animation pipelines, automatically selected based on input type:

Plain Text Pipeline

Used by UAnimatedTextBlock, a single animation blueprint drives the entire text:

  1. Word Breaking — Perform character breakpoint analysis on PlainText (considering CJK surrogate pairs and Unicode boundaries)
  2. Compilation — Create and compile a UTextAnimInstance from the TextAnimationBlueprint
  3. Frame GenerationEvaluateTime() samples all tracks at each time point, blending to produce per-character frame data
  4. Timing Calculation — Compute per-character reveal time based on PlaybackMode (Duration / CPS)

Rich Text Pipeline

Used by UAnimatedRichTextBlock, driven by a render tree for multi-layer animation:

  1. ParsingFTagParser parses rich text into a tag tree
  2. Render Tree ConstructionFRenderTreeBuilder classifies nodes (AnimationLayer / StyleLayer / TextContent / Decorator)
  3. Per-Layer Compilation — Each AnimationLayer node gets its own UTextAnimInstance
  4. Per-Layer Frame Generation — Each animation layer’s frame data is evaluated independently
  5. Timing Calculation — Based on the longest total duration across all layers

Playback Control

Play Modes

ModeEnum ValueBehavior
NormalNormalForward playback, stops at end
Fast ForwardFastForwardAccelerated forward playback (rate controlled by PlaySpeedMultiplier)
ReverseReverseReverse character-by-character hiding
Ping PongPingPongForward → reverse → forward cycle
LoopLoopRestarts from beginning after completion (controlled by MaxLoopCount, ≤0 for infinite)

Timing Modes

ModeDescription
DurationFixed total duration. Animation completes within TotalDuration seconds, with equal time per character.
CPSCharacters Per Second. Characters revealed at a fixed rate (CPS, default 10 chars/sec).

Public API

// Input
void SetPlainText(const FString& InText);
void SetRichText(const FString& InRichText);
void SetAnimationData(UTextAnimationDataAsset* InData);
void SetAnimationBlueprint(UTextAnimationBlueprint* InBlueprint);

// Control
void StartAnimating(bool bRevealAll = false);
void TickAnimation(float InDeltaTime);
void Pause();
void Resume();
void Stop();
void SkipToEnd();

// Query
bool IsAnimating() const;
int32 GetCurrentCharIndex() const;
int32 GetCharacterCount() const;
const FString& GetPlainText() const;
const FString& GetDisplayRichText() const;
const TArray<FAnimationFrameData>& GetCurrentFrameDataArray() const;
FAnimationFrameData GetCurrentFrameData(int32 CharIndex) const;

Frame Data Cache

CurrentFrameDataCache is the animator’s core output, updated every Tick — a TArray<FAnimationFrameData> with length equal to the text character count. This cache is directly consumed by the Slate widget’s OnPaint():

  • Revealed characters: evaluated animation frame data in the cache
  • Unrevealed characters: identity frame (Opacity=0) in the cache, producing no rendering

UpdateCurrentFrameDataCache() is called once per Tick, iterating all character indices and calling GetCurrentFrameData() for each revealed character.

Editor Preview

SetPreviewFrameDataOverride() allows injecting frame data overrides from external sources (the Sequencer editor). When HasPreviewFrameData() returns true, GetCurrentFrameData() returns the injected preview data instead of real-time evaluation results — this is the key mechanism for real-time preview when dragging the Sequencer playhead in the editor.

Animation Completion & Delegates

DelegateSignatureTrigger
OnCharacterRevealed(int32 CharIdx, TCHAR Char)Broadcast each time a character is revealed
OnAnimationComplete()Broadcast when animation fully completes

In loop modes (Loop/PingPong), OnLoopRestart() is called when the animation completes to reset state and continue playing — OnAnimationComplete is NOT broadcast. The completion event is only broadcast on final completion when MaxLoopCount is reached.

Debounce Protection

MaxAdvanceFramesPerTick (constant 5) limits the maximum frames advanced per Tick, preventing a single frame from skipping the entire animation due to a hitch. When DeltaTime is so large that more than 5 frames would advance, only 5 are advanced, with the remainder caught up in subsequent Ticks.

images/animator-pipeline-flowchart.png — Complete animator lifecycle flowchart: StartAnimating → pipeline selection → compile tracks → Tick AdvanceFrame → BroadcastCurrentChar → UpdateCache → Slate OnPaint → complete or loop