Data Structures Reference

Texturge defines a set of data structures for passing animation data between compile time, runtime, and Blueprint. They are split into UENUM / USTRUCT (Blueprint-reflectable) and pure C++ structs.

Enums

EAnimationStage

UENUM(BlueprintType, meta = (DisplayName = "动画阶段"))
enum class EAnimationStage : uint8
{
    Intro       UMETA(DisplayName = "入场"),
    Default     UMETA(DisplayName = "默认"),
    Outro       UMETA(DisplayName = "出场"),
    None        UMETA(Hidden),
    GlyphEditor UMETA(Hidden)
};
ValueDescription
Introintro stage
Defaultdefault stage
Outrooutro stage
Noneempty stage (Hidden)
GlyphEditoreditor-only glyph editing mode (Hidden)

ETextAnimationBlendMode

UENUM(BlueprintType, meta = (DisplayName = "文本动画混合模式"))
enum class ETextAnimationBlendMode : uint8
{
    Additive    UMETA(DisplayName = "加法混合"),
    Override    UMETA(DisplayName = "覆盖混合"),
    Multiply    UMETA(DisplayName = "乘法混合"),
    CrossFade   UMETA(DisplayName = "交叉淡化")
};
ValueDescription
Additivelayer result added to the base value
Overridelayer result replaces the base value
Multiplylayer result multiplied with the base value
CrossFadecross fade

EPlayMode

UENUM(BlueprintType, meta = (DisplayName = "播放方向"))
enum class EPlayMode : uint8
{
    Forward     UMETA(DisplayName = "前向"),
    Reverse     UMETA(DisplayName = "反向"),
    PingPong    UMETA(DisplayName = "乒乓")
};

NOTE

EPlayMode has only forward / reverse / pingpong direction semantics — no Loop enum value; looping is controlled independently by FPerLayerCurves::NumberOfLoops (0 = infinite loop).

EPlaybackMode (UTextAnimator)

UENUM(BlueprintType, meta = (DisplayName = "播放时序模式"))
enum class EPlaybackMode : uint8
{
    Duration    UMETA(DisplayName = "持续时间模式"),
    CPS         UMETA(DisplayName = "每秒字形数模式")
};
ValueDescription
Durationplayback speed controlled by the total duration
CPScontrolled by glyphs per second (SetCPS, floor 1.0)

EStagePlayDirection / EStagePlaybackState

UENUM(BlueprintType)
enum class EStagePlayDirection : uint8
{
    Forward, Reverse, PingPong
};

UENUM(BlueprintType)
enum class EStagePlaybackState : uint8
{
    Stopped, Playing, Paused, Complete
};

ETextAnimationValueType / ETextAnimationTrackType

UENUM(BlueprintType, meta = (DisplayName = "文本动画值类型"))
enum class ETextAnimationValueType : uint8
{
    Float, Vector2D, Color
};

UENUM(BlueprintType, meta = (DisplayName = "文本动画轨道类型"))
enum class ETextAnimationTrackType : uint8
{
    Opacity        = 0,   // Float
    LetterSpacing  = 1,   // Float
    Rotation       = 2,   // Float
    Translation    = 10,  // Vector2D
    Scale          = 11,  // Vector2D
    Shear          = 12,  // Vector2D
    Pivot          = 13,  // Vector2D
    ShadowOffset   = 14,  // Vector2D
    Color          = 20,  // Color
    ShadowColor    = 21   // Color
};

The 10 track types expand into 21 curve channels: 3 (Float) + 5 × 2 (Vector2D components) + 2 × 4 (Color components).

EGlyphOverrideOperation

UENUM(BlueprintType, meta = (DisplayName = "字形微调操作"))
enum class EGlyphOverrideOperation : uint8
{
    Multiply    UMETA(DisplayName = "乘算"),
    Divide      UMETA(DisplayName = "除算"),
    Add         UMETA(DisplayName = "加算"),
    Subtract    UMETA(DisplayName = "减算")
};

ERenderNodeType (not a UENUM)

enum class ERenderNodeType : uint8
{
    Unknown,
    AnimationLayer, // animation tag layer (matches a DataAsset Entry)
    StyleLayer,     // rich text style layer
    TextContent,    // plain text leaf
    Decorator       // self-closing decorator (e.g. image)
};

EAnimParamType (not a UENUM)

enum class EAnimParamType : uint8
{
    Float, Int, Bool, Vector, Color, Vector2D
};

Derived from the property type by GetAnimParamType(const FProperty*), driving template dispatch.

ETexturgePreviewSource (Editor module)

enum class ETexturgePreviewSource : uint8
{
    None,           // no preview — shows static text
    StagePreview,   // independent clock looping through three stages
    GlyphAnimation  // Sequencer playhead driven
};

USTRUCTs

FGlyphScheduleInfo

Per-glyph (Unicode code point) scheduling configuration at compile time. The first 6 fields are system-prefilled by BuildScheduleInfos (read-only); the last 2 are set by the designer (read/write).

USTRUCT(BlueprintType, meta = (DisplayName = "字形调度信息"))
struct TEXTURGE_API FGlyphScheduleInfo
{
    // —— system-prefilled (BlueprintReadOnly) ——
    int32   GlyphIndex;       // glyph index in the text
    FString GlyphString;      // single-glyph string (supports U+FFFC embedded object placeholder)
    bool    bIsWhitespace;    // whitespace glyph
    bool    bIsPunctuation;   // punctuation
    bool    bIsCJK;           // CJK characters
    bool    bIsDecorator;     // embedded object decorator

    // —— designer-set (BlueprintReadWrite) ——
    float   StartDelay;       // animation start delay (seconds)
    bool    bSkipAnimation;   // skip this glyph's animation in this stage
};

NOTE

This struct removed early-version fields such as ValueScale / ValueOffset / SeedJitter — curve-level tweaks are now uniformly handled by the FGlyphCurveOverride ordered operation steps.

FGlyphOverrideStep Family

USTRUCT(BlueprintType, meta = (DisplayName = "Float 微调步骤"))
struct FGlyphFloatOverrideStep
{
    EGlyphOverrideOperation Operation = EGlyphOverrideOperation::Multiply;
    float Value = 1.0f;
};

USTRUCT(BlueprintType, meta = (DisplayName = "Vec2 微调步骤"))
struct FGlyphVec2OverrideStep
{
    EGlyphOverrideOperation Operation = EGlyphOverrideOperation::Multiply;
    FVector2D Value = FVector2D(1.0f, 1.0f);
};

USTRUCT(BlueprintType, meta = (DisplayName = "颜色微调步骤"))
struct FGlyphColorOverrideStep
{
    EGlyphOverrideOperation Operation = EGlyphOverrideOperation::Multiply;
    FLinearColor Value = FLinearColor::White;
};

FGlyphCurveOverride

Glyph curve tweak overrides — ordered operation steps for 10 properties. Layer-level rewrites curve keys per layer in bake STEP 1.5; factory-level bakes into FBakedGlyph::FactoryOverride, applied once to the final blended state at evaluation.

PropertyStep array type
Opacity / LetterSpacing / RotationTArray<FGlyphFloatOverrideStep>
Translation / Scale / Shear / Pivot / ShadowOffsetTArray<FGlyphVec2OverrideStep>
Tint / Shadow tintTArray<FGlyphColorOverrideStep>

FGlyphAnimationState

Runtime single-glyph render state. Default construction equals the identity state (no transform, white opaque, transparent shadow).

USTRUCT(BlueprintType, meta = (DisplayName = "字形动画状态"))
struct TEXTURGE_API FGlyphAnimationState
{
    // transform
    FVector2D PositionOffset;   // position offset (pixels)
    FVector2D Scale;            // scale (default 1,1)
    float     Rotation;         // rotation (degrees)
    FVector2D Shear;            // shear transform
    FVector2D Pivot;            // pivot (0~1 normalized, default 0.5,0.5)

    // color & opacity
    FLinearColor Color;         // tint: RGB = tint color, A = tint share (0~1)
    float       Opacity;        // opacity (default 1.0)

    // shadow
    FVector2D   ShadowOffset;   // shadow offset (pixels)
    FLinearColor ShadowColor;   // shadow color (default Transparent)

    // typography
    float       LetterSpacing;  // letter spacing (pixels)

    static FGlyphAnimationState Identity();
};

NOTE

Tint semantics: Color.A is the tint share, not opacity — A=1.0 fully tinted, A=0.5 1:1 with the text color, A=0.0 untinted. Identity value Transparent (0,0,0,0). Tinting does not change glyph opacity (opacity is controlled separately by the Opacity channel).

Mixing algorithm: tinting uses Pigment-Based Mixing (PBM) physical pigment mixing (Texturge::GlyphRender::ApplyTint) — a real-time RGB pigment mixing algorithm built on Kubelka–Munk (K–M) two-flux theory + Duncan concentration superposition, with constants reference-calibrated against Mixbox 2.0, operating in sRGB display-value space — no spectral data, no look-up tables, O(1) constant time, strictly exchange-symmetric F(b, t, α) ≡ F(t, b, 1−α). It reproduces real paint behavior: yellow + blue makes green, complementary pairs mix into muddy colors (olive/brown/dark purple) instead of gray, and deep colors lightened with white stay saturated instead of turning gray. The algorithm is open-sourced under the MIT license (pigment-based-mixing).

FPerLayerCurves

Single-layer curve data — 21 TArray<FRichCurveKey>s + playback metadata. This is the fully-UPROPERTY storage scheme under the constraint that FRichCurve is not a USTRUCT: FRichCurve is built temporarily at evaluation.

USTRUCT()
struct TEXTURGE_API FPerLayerCurves
{
    // playback metadata
    ETextAnimationBlendMode BlendMode = ETextAnimationBlendMode::Override;
    EPlayMode PlayDirection = EPlayMode::Forward;
    int32     NumberOfLoops = 1;          // 0 = infinite loop
    float     PlaySpeedMultiplier = 1.0f;
    float     LayerDuration = 0.0f;       // precomputed at bake (max key - min key)

    // 21 curve channels (TArray<FRichCurveKey>)
    // Float (3): Curve_Opacity, Curve_LetterSpacing, Curve_Rotation
    // Vector2D (10): Curve_TranslationX/Y, Curve_ScaleX/Y,
    //                Curve_ShearX/Y, Curve_PivotX/Y, Curve_ShadowOffsetX/Y
    // Color (8): Curve_ColorR/G/B/A, Curve_ShadowColorR/G/B/A

    bool IsEmpty() const;                          // whether all curves are empty
    float GetEffectiveDuration() const;            // finite loop = LayerDuration × Loops; infinite = FLT_MAX
    void Evaluate(float LocalTime, FGlyphAnimationState& InOutState) const;
};

Evaluate semantics:

  • Only channels with NumKeys > 0 are evaluated and combined — empty channels do not participate, preserving existing InOutState values (no cross-layer contamination)
  • With PlayDirection != Forward and LayerDuration > 0, LocalTime is time-wrapped: Forward = fmod loop, Reverse = reversed fmod, PingPong = alternating
  • Combination rules: Override replaces / Additive adds / Multiply multiplies / CrossFade fixed 0.5 Lerp interpolation

FBakedGlyph

Baked single-glyph runtime data.

USTRUCT(BlueprintType, meta = (DisplayName = "烘焙字形"))
struct TEXTURGE_API FBakedGlyph
{
    UPROPERTY(BlueprintReadOnly) int32   OriginalIndex;   // original text index
    TCHAR Character;                                    // debug only (not UPROPERTY)
    UPROPERTY(BlueprintReadOnly) float   StartTime;      // relative start time within the stage (seconds)
    UPROPERTY(BlueprintReadOnly) float   Duration;       // animation duration (seconds)
    UPROPERTY(BlueprintReadOnly) bool    bIsStatic;      // static glyph skipping animation
    UPROPERTY(BlueprintReadOnly) FGlyphAnimationState DefaultState;  // fixed state for static glyphs
    UPROPERTY(BlueprintReadOnly) bool    bHideFirstFrame;// fully transparent before the animation starts
    TArray<FPerLayerCurves> LayerCurves;               // per-layer curves (0 bottom → N-1 top)
    UPROPERTY(BlueprintReadOnly) FGlyphCurveOverride FactoryOverride; // factory-level tweaks

    void Evaluate(float StageTime, FGlyphAnimationState& OutState) const;
    bool HasStarted(float StageTime) const;
    bool HasFinished(float StageTime) const;
    void AddLayer(const FGlyphCurveSet& Source, ETextAnimationBlendMode InBlendMode);
    // single-layer compatibility helpers: IsCurvesEmpty / GetCurvesTimeRange / CopyCurvesFrom /
    //                                     OffsetCurvesBy / BuildCurveSet
};

Evaluate semantics:

  • bIsStatic → directly returns DefaultState
  • Not started (LocalTime < 0) → returns the identity state (opacity 1 fully visible), transparent only when bHideFirstFrame is true
  • Per-layer independent evaluation then combined by BlendMode: OutState = Identity() → each layer Layer.Evaluate(LocalTime, OutState)

FBakedStage

Baked single-stage data. Fully UPROPERTY-composed, supporting use as a BlueprintNativeEvent return type (Kismet compiler safe).

USTRUCT(BlueprintType, meta = (DisplayName = "烘焙阶段"))
struct TEXTURGE_API FBakedStage
{
    EAnimationStage StageType = EAnimationStage::None;
    int32  GlyphCount = 0;
    float  TotalDuration = 0.0f;
    TArray<FBakedGlyph> Glyphs;

    bool IsValid() const;                    // GlyphCount > 0 and arrays match
    int32 GetStartedGlyphCount(float StageTime) const;
};

FStageTransitionConfig

USTRUCT(BlueprintType, meta = (DisplayName = "阶段过渡配置"))
struct TEXTURGE_API FStageTransitionConfig
{
    float IntroToDefaultCrossFade = 0.1f;                 // intro→default transition duration (seconds)
    float DefaultToOutroCrossFade = 0.1f;                 // default→outro transition duration (seconds)
    TEnumAsByte<EEasingFunc::Type> EasingFunc = EEasingFunc::Linear;  // transition easing
};

This configuration lives on UTextAnimationBlueprint::StageTransitionConfig and participates in the Blueprint fingerprint hash.

FAnimationEntry

The row data structure of UTextAnimationDataAsset::Entries[] — mapping tag names to animation Blueprints.

USTRUCT(BlueprintType, meta = (DisplayName = "动画条目"))
struct TEXTURGE_API FAnimationEntry
{
    FName TagName;                                          // rich text tag name
    TObjectPtr<UTextAnimationBlueprint> Type;               // animation Blueprint asset
    TMap<FName, float>         ParameterOverrides;          // Float overrides
    TMap<FName, int32>         IntParameterOverrides;       // Int overrides
    TMap<FName, bool>          BoolParameterOverrides;      // Bool overrides
    TMap<FName, FVector>       VectorParameterOverrides;    // Vector overrides
    TMap<FName, FLinearColor>  ColorParameterOverrides;     // Color overrides
    TMap<FName, FVector2D>     Vector2DParameterOverrides;  // Vector2D overrides
    bool bHideFirstFrame = false;                           // entries not yet executed are transparent during sequential playback
};

FSemanticAnchor

USTRUCT(BlueprintType, meta = (DisplayName = "语义锚点"))
struct TEXTURGE_API FSemanticAnchor
{
    FName   AnchorId;           // anchor identifier
    FString ContextualSnippet;  // contextual snippet
    int32   SourceOffset;       // source offset
};

Processed by ULocalizationSubsystem::RelocateAnchors: exact matching (case-insensitive) + LCS longest-common-substring approximate matching + confidence dedup (MaxTextLength = 10000 cap).

FTextAnimationSubTrackData

USTRUCT(meta = (DisplayName = "字形动画子轨道数据"))
struct TEXTURGE_API FTextAnimationSubTrackData
{
    ETextAnimationTrackType TrackType = ETextAnimationTrackType::Opacity;
    FName DisplayName;
    TArray<FMovieSceneFloatChannel> Channels;   // Float→1, Vector2D→2, Color→4
};

C++ Structs (not USTRUCT)

FBakedAnimation

The complete bake result. Not a USTRUCT (contains TUniquePtr), C++ layer only.

struct FBakedAnimation
{
    FString SourceText;
    int32   GlyphCount = 0;
    uint64  TextFingerprint = 0;        // CityHash64(SourceText)
    uint64  BlueprintFingerprint = 0;   // hash(path + asset signatures + BP variables + CompileCount + generated class address)

    TUniquePtr<FBakedStage> IntroStage;     // may be nullptr
    TUniquePtr<FBakedStage> DefaultStage;   // usually valid
    TUniquePtr<FBakedStage> OutroStage;     // may be nullptr

    const FBakedStage* GetStage(EAnimationStage Stage) const;
    bool HasStage(EAnimationStage Stage) const;
    EAnimationStage GetFirstValidStage() const;   // Intro first, then Default
    EAnimationStage GetNextStageAfter(EAnimationStage Current) const;
    float GetTotalDuration() const;              // sum of all present stage durations
};

FGlyphCurveSet

Internal C++ temporary computation container — 21 FRichCurves (FRichCurve is not a USTRUCT and cannot be a UPROPERTY).

struct TEXTURGE_API FGlyphCurveSet
{
    // Float: Opacity, LetterSpacing, Rotation
    // Vector2D: TranslationX/Y, ScaleX/Y, ShearX/Y, PivotX/Y, ShadowOffsetX/Y
    // Color: ColorR/G/B/A, ShadowColorR/G/B/A

    void Evaluate(float Time, FGlyphAnimationState& OutState) const;
    void OffsetBy(float Seconds);
    void CopyFrom(const FGlyphCurveSet& Source);
    TRange<float> GetTimeRange() const;
    bool IsEmpty() const;
};

FTagNode / FRenderNode

The two-level tree nodes of rich text parsing (pure C++).

struct TEXTURGE_API FTagNode
{
    FString TagName;                        // empty for the root
    TMap<FString, FString> Attributes;
    TArray<TSharedPtr<FTagNode>> Children;
    TWeakPtr<FTagNode> Parent;              // weak pointer prevents cycles
    int32 RangeBegin;                       // plain text start index (inclusive)
    int32 RangeEnd;                         // plain text end index (exclusive)
    bool bIsSelfClosing = false;
};

struct TEXTURGE_API FRenderNode
{
    ERenderNodeType NodeType = ERenderNodeType::Unknown;
    FString TagName;
    TMap<FString, FString> Attributes;
    TArray<TSharedPtr<FRenderNode>> Children;
    int32 RangeBegin = 0;
    int32 RangeEnd = 0;
    int32 EntryIndex = INDEX_NONE;          // AnimationLayer: Entries index
    FString Text;                           // TextContent: leaf text
};

UAnimParameterOverrides (UCLASS)

UCLASS(meta = (DisplayName = "动画参数覆盖"))
class TEXTURGE_API UAnimParameterOverrides : public UObject
{
    TMap<FName, float>         FloatOverrides;
    TMap<FName, int32>         IntOverrides;
    TMap<FName, bool>          BoolOverrides;
    TMap<FName, FVector>       VectorOverrides;
    TMap<FName, FLinearColor>  ColorOverrides;
    TMap<FName, FVector2D>     Vector2DOverrides;
};

21-Channel Mapping Table

#Curve fieldTrack typeState property
1Curve_OpacityOpacityOpacity
2Curve_LetterSpacingLetterSpacingLetterSpacing
3Curve_RotationRotationRotation
4Curve_TranslationXTranslationPositionOffset.X
5Curve_TranslationYTranslationPositionOffset.Y
6Curve_ScaleXScaleScale.X
7Curve_ScaleYScaleScale.Y
8Curve_ShearXShearShear.X
9Curve_ShearYShearShear.Y
10Curve_PivotXPivotPivot.X
11Curve_PivotYPivotPivot.Y
12Curve_ShadowOffsetXShadowOffsetShadowOffset.X
13Curve_ShadowOffsetYShadowOffsetShadowOffset.Y
14Curve_ColorRColorColor.R
15Curve_ColorGColorColor.G
16Curve_ColorBColorColor.B
17Curve_ColorAColorColor.A (tint share)
18Curve_ShadowColorRShadowColorShadowColor.R
19Curve_ShadowColorGShadowColorShadowColor.G
20Curve_ShadowColorBShadowColorShadowColor.B
21Curve_ShadowColorAShadowColorShadowColor.A

TIP

FGlyphAnimationState has 10 fields (not 21) — 21 refers to the curve channel count; Vector2D and Color properties expand into 2 / 4 channels each. New channels are driven by the 21-row member-pointer registry in GlyphChannelTable.h (static_assert(21)); adding a channel requires changes in about 5 places.

Data Structure Relationship Diagram

UTextAnimationBlueprint
    ├── GlyphAnimations: TArray<UGlyphAnimation*>      ← Sequencer curve assets
    │       └── MovieScene → UMovieSceneGlyphAnimationTrack
    │               └── UMovieSceneGlyphAnimationSection[] (sub-tracks)
    │                       └── FTextAnimationSubTrackData.Channels (FMovieSceneFloatChannel)
    ├── StageTransitionConfig
    └── [Compile] → UTextAnimationBlueprintGeneratedClass
            └── FAnimationCompiler::BakeAllStages(BP, Text, Customizer)
                    ├── ExtractCurves → FGlyphCurveSet (21 FRichCurves, cached)
                    ├── BuildScheduleInfos → TArray<FGlyphScheduleInfo>
                    └── UGlyphAnimationLayer / UGlyphAnimationFactory
                            └── Bake() → FBakedStage
                    └── FBakedAnimation
                            ├── IntroStage / DefaultStage / OutroStage → FBakedStage
                            │       └── Glyphs: TArray<FBakedGlyph>
                            │               └── LayerCurves: TArray<FPerLayerCurves> (21 channels × per layer)
                            │               └── FactoryOverride: FGlyphCurveOverride
                            ├── TextFingerprint / BlueprintFingerprint (uint64)

UTextAnimationStageController::EvaluateAllGlyphs
    └── FBakedGlyph::Evaluate(StageTime, OutState)
            └── per-layer FPerLayerCurves::Evaluate(LocalTime, OutState) → combined by BlendMode
                    └── FGlyphAnimationState

Memory Layout Considerations

  • FBakedAnimation holds the three stages with TUniquePtr; move semantics support Double-Buffer publish switching
  • FPerLayerCurves is the largest struct — 21 TArray<FRichCurveKey>s; FRichCurve is built temporarily at evaluation (minor performance cost; future optimization: caching with dirty flags)
  • FGlyphAnimationState is a compact POD type (10 members, ~64 bytes), suitable for stack allocation and batch processing
  • The runtime hot path (EvaluateAllGlyphsFBakedGlyph::Evaluate) uses stack variables throughout — no heap allocation

TIP

Always use stack-allocated FGlyphAnimationState variables on the hot path; avoid creating temporary TArray containers inside loops for maximum throughput. Watch the temporary curve-building cost of FPerLayerCurves::Evaluate with large text + many layers.

images/data-structures.png — Data structure relationship diagram: UTextAnimationBlueprint (GlyphAnimations + StageTransitionConfig) compiled by FAnimationCompiler into FBakedAnimation (three FBakedStage → FBakedGlyph → multi-layer FPerLayerCurves with 21 channels), with FGlyphScheduleInfo and FGlyphCurveOverride structure outlines on the right