Rich Text Tag Syntax
Texturge Beta binds animations to text content through XML-like tags. Tags are parsed by FTagParser into an FTagNode tree, then FRenderTreeBuilder builds a render tree (FRenderNode) with UTextAnimationDataAsset for UAnimatedRichTextBlock.
NOTE
This is not XML — closing tags uniformly use
</>(closes the most recently opened tag); no need to repeat the tag name.
Tag Structure
Animation Tags
<anim id="wave">content</>
- Opening tag:
<anim id="...">— tag name isanim(case-insensitive), matched againstFAnimationEntry::TagNameinUTextAnimationDataAsset::Entries[]via theidattribute (also case-insensitive) - Closing tag:
</>— closes the most recently opened tag, no tag name needed - An
<anim>tag matching no Entry is processed as StyleLayer with a[RenderTreeBuilder] <anim id="..."> does not match any Entrywarning - Content can be plain text or other nested tags
Self-Closing Tags
<img src="icon" />
Self-closing tags with no children are classified as Decorator (decorator nodes, e.g. embedded images).
Syntax Rules
- Opening tags use angle brackets
<>:<tagname attr="value"> - Closing tags are uniformly
</>— each closes the most recently opened tag (write multiple</>from inside out when nested, or use explicit closing tags for style tags) - Self-closing tags end with
/> - Tag names consist of
[A-Za-z0-9_-], max 32 characters - Attributes support both quoted and unquoted forms; attribute values support escaping (
<>&"') - Unclosed tags are auto-closed at parse end (
AutoCloseRemaining, oneUE_LOGwarning each)
Nesting Examples
<anim id="wave"><color style="red">colorful waving text</color></>
<anim id="typewriter">typewriter text with<anim id="shake">some shake</></>
<color>...</color>is a style tag using an explicit closing tag (required by the engine’s rich text style system)<anim>tags close with</>(the inner</>closesshakefirst, the outer</>then closestypewriter)
Parsing Pipeline
Step 1: FTagParser
FTagParser::Parse(const FString& InRichText, FString& OutPlainText) parses the raw text into an FTagNode tree:
- Scans character by character; on
<, enters tag parsing mode - Recognizes tag names (
ParseTagName) and attributes (ParseAttributes) - Invalid tags do not error —
<is output as plain text </>closes the most recently opened tag;</name>searches the stack from the top for a matching tag nameAutoCloseRemainingauto-closes unpaired tags (oneUE_LOGwarning each)- After tag stripping + escape decoding, outputs
OutPlainText(plain text, with code point index baseline)
Step 2: FRenderTreeBuilder
FRenderTreeBuilder::Build(TSharedPtr<FTagNode> ParseRoot, const FString& PlainText, UTextAnimationDataAsset* AnimationData) converts the parse tree into a render tree:
- DFS traversal of the
FTagNodetree, auto-insertingTextContentnodes between siblings (carrying plain-text Ranges) ClassifyNodeclassifies each tag node (see below)AnimationLayernodes resolveEntryIndexviaFindEntryIndex(matched byidinEntries[])- Outputs the
TSharedPtr<FRenderNode>root node
Serialization helpers:
| Function | Description |
|---|---|
SerializeToDisplayText(FRenderNode) | render tree → display text: AnimationLayer skips tags and outputs children, StyleLayer outputs fully-wrapped tags, Decorator outputs self-closing tags |
SerializeSubstring(FRenderNode, int32 MaxCharIndex) | slices the revealed portion by glyph index with style wrapping, auto-unescaping |
Node Classification
FRenderTreeBuilder::ClassifyNode determines in the following order (ERenderNodeType):
| Enum value | Condition | Description |
|---|---|---|
AnimationLayer | tag name anim (case-insensitive) and id attribute matches an Entry | animation layer, EntryIndex points into Entries[] |
Decorator | self-closing (/>) with no children | decorator node (e.g. image placeholder) |
StyleLayer | all other tags | style layer, delegated to the UE5 rich text style system |
TextContent | — | plain-text leaf (non-tag node) |
Unknown | empty tag name / invalid node | exceptional state |
UTextAnimationDataAsset
The core mapping asset mapping animation ids to animation Blueprints:
| Property | Type | Description |
|---|---|---|
Entries | TArray<FAnimationEntry> | tag → animation Blueprint mapping array |
FAnimationEntry Fields
| Field | Type | Description |
|---|---|---|
TagName | FName | matching animation id (compared with <anim id="...">, case-insensitive) |
Type | TObjectPtr<UTextAnimationBlueprint> | animation Blueprint asset reference |
ParameterOverrides | TMap<FName, float> | Float parameter overrides |
IntParameterOverrides | TMap<FName, int32> | Int parameter overrides |
BoolParameterOverrides | TMap<FName, bool> | Bool parameter overrides |
VectorParameterOverrides | TMap<FName, FVector> | Vector parameter overrides |
ColorParameterOverrides | TMap<FName, FLinearColor> | Color parameter overrides |
Vector2DParameterOverrides | TMap<FName, FVector2D> | Vector2D parameter overrides |
bHideFirstFrame | bool | entries not yet executed are transparent ahead during sequential playback |
The 6 override maps correspond one-to-one with the UAnimParameterOverrides type structure, written into the temporary instance via ApplyAnimParamOverridesToInstance<FAnimationEntry> during baking.
UAnimatedRichTextBlock
UAnimatedRichTextBlock (inherits URichTextBlock) drives rich text animation:
- The widget holds an
AnimationData(UTextAnimationDataAsset*) property - On text change, the private
FRichTextMarshallerinternally completes parsing and render-tree construction - Each
AnimationLayernode instantiates the corresponding animation entry;UTextAnimatororchestrates multi-entry playback (bSequentialPlaybacksequential / simultaneous) - Style tags (StyleLayer) cooperate with the engine
URichTextBlock’s style sets, decorators, and data tables
// C++ setup flow
AnimatedRichTextBlock->AnimationData = MyDataAsset;
AnimatedRichTextBlock->SetText(FText::FromString(
TEXT("Hello <anim id=\"wave\">World</>!")));
Nesting Rules
- Nesting must pair legally (inner tags’ closing tags appear before outer tags’)
animtags can nest other style tags (<anim id="wave"><color ...>...</color></>)- Unmatched
<anim>is processed as StyleLayer (tag preserved, text not animated)
NOTE
There is no hard nesting depth limit, but overly deep structures increase
FTagParserparsing and render-tree build cost; keep within 4 levels.
Legal Nesting Examples
<anim id="wave"><color style="red">colorful waving text</color></>
<anim id="typewriter">typewriter text with<anim id="shake">some shake</></>