数据结构参考

Texturge 定义了一组数据结构,用于在编译期、运行时与蓝图之间传递动画数据。按照是否可由蓝图反射分为 UENUM / USTRUCT 与纯 C++ 结构两类。

枚举类型

EAnimationStage

UENUM(BlueprintType, meta = (DisplayName = "动画阶段"))
enum class EAnimationStage : uint8
{
    Intro       UMETA(DisplayName = "入场"),
    Default     UMETA(DisplayName = "默认"),
    Outro       UMETA(DisplayName = "出场"),
    None        UMETA(Hidden),
    GlyphEditor UMETA(Hidden)
};
说明
Intro入场阶段
Default默认阶段
Outro出场阶段
None空阶段(Hidden)
GlyphEditor编辑器专用字形编辑模式(Hidden)

ETextAnimationBlendMode

UENUM(BlueprintType, meta = (DisplayName = "文本动画混合模式"))
enum class ETextAnimationBlendMode : uint8
{
    Additive    UMETA(DisplayName = "加法混合"),
    Override    UMETA(DisplayName = "覆盖混合"),
    Multiply    UMETA(DisplayName = "乘法混合"),
    CrossFade   UMETA(DisplayName = "交叉淡化")
};
说明
Additive层结果叠加到基础值
Override层结果覆盖基础值
Multiply层结果与基础值相乘
CrossFade交叉淡化

EPlayMode

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

NOTE

EPlayMode 只有前向 / 反向 / 乒乓三种方向语义,没有 Loop 枚举值——循环由 FPerLayerCurves::NumberOfLoops(0 = 无限循环)独立控制。

EPlaybackMode(UTextAnimator)

UENUM(BlueprintType, meta = (DisplayName = "播放时序模式"))
enum class EPlaybackMode : uint8
{
    Duration    UMETA(DisplayName = "持续时间模式"),
    CPS         UMETA(DisplayName = "每秒字形数模式")
};
说明
Duration按总时长控制播放速度
CPS按每秒字形数控制(SetCPS,下限 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
};

10 种轨道类型展开为 21 条曲线通道:3(Float)+ 5 × 2(Vector2D 分量)+ 2 × 4(Color 分量)。

EGlyphOverrideOperation

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

ERenderNodeType(非 UENUM)

enum class ERenderNodeType : uint8
{
    Unknown,
    AnimationLayer, // 动画标签层(匹配 DataAsset Entry)
    StyleLayer,     // 富文本样式层
    TextContent,    // 纯文本叶子
    Decorator       // 自闭合装饰器(如图片)
};

EAnimParamType(非 UENUM)

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

GetAnimParamType(const FProperty*) 从属性类型推导,驱动模板分派。

ETexturgePreviewSource(Editor 模块)

enum class ETexturgePreviewSource : uint8
{
    None,           // 无预览 — 显示静止文本
    StagePreview,   // 独立时钟循环播放三阶段
    GlyphAnimation  // Sequencer 播放头驱动
};

USTRUCT 结构体

FGlyphScheduleInfo

编译时每个字形(Unicode 码点)的调度配置。前 6 个字段由 BuildScheduleInfos 系统预填(只读),后 2 个由设计师设置(可读写)。

USTRUCT(BlueprintType, meta = (DisplayName = "字形调度信息"))
struct TEXTURGE_API FGlyphScheduleInfo
{
    // —— 系统预填(BlueprintReadOnly)——
    int32   GlyphIndex;       // 字形在文本中的索引
    FString GlyphString;      // 单字形字符串(支持 U+FFFC 内嵌对象占位符)
    bool    bIsWhitespace;    // 空白字形
    bool    bIsPunctuation;   // 标点符号
    bool    bIsCJK;           // 中日韩文字
    bool    bIsDecorator;     // 内嵌对象装饰器

    // —— 设计师设置(BlueprintReadWrite)——
    float   StartDelay;       // 动画起始延迟(秒)
    bool    bSkipAnimation;   // 跳过此字形在本阶段的动画
};

NOTE

该结构已移除早期版本的 ValueScale / ValueOffset / SeedJitter 等字段——曲线级微调统一由 FGlyphCurveOverride 有序操作步骤承担。

FGlyphOverrideStep 系列

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

字形曲线微调覆盖——10 个属性的有序操作步骤集合。Layer 级在烘焙 STEP 1.5 逐层改写曲线键;Factory 级烘焙进 FBakedGlyph::FactoryOverride,求值时对混合后的最终状态整体应用一次。

属性步骤数组类型
不透明度 / 字间距 / 旋转TArray<FGlyphFloatOverrideStep>
平移 / 缩放 / 修剪 / 枢纽 / 阴影偏移TArray<FGlyphVec2OverrideStep>
染色 / 阴影染色TArray<FGlyphColorOverrideStep>

FGlyphAnimationState

运行时单字形渲染状态。默认构造等于恒等状态(无变换、白色不透明、阴影透明)。

USTRUCT(BlueprintType, meta = (DisplayName = "字形动画状态"))
struct TEXTURGE_API FGlyphAnimationState
{
    // 变换
    FVector2D PositionOffset;   // 位置偏移(像素)
    FVector2D Scale;            // 缩放(默认 1,1)
    float     Rotation;         // 旋转(度)
    FVector2D Shear;            // 剪切变换
    FVector2D Pivot;            // 枢轴(0~1 归一化,默认 0.5,0.5)

    // 颜色与不透明度
    FLinearColor Color;         // 染色:RGB = 染色色,A = 染色占比(0~1)
    float       Opacity;        // 不透明度(默认 1.0)

    // 阴影
    FVector2D   ShadowOffset;   // 阴影偏移(像素)
    FLinearColor ShadowColor;   // 阴影颜色(默认 Transparent)

    // 排版
    float       LetterSpacing;  // 字间距(像素)

    static FGlyphAnimationState Identity();
};

NOTE

染色语义Color.A 是染色占比而非透明度——A=1.0 完全染色、A=0.5 与文本色 1:1、A=0.0 不染色。恒等值 Transparent (0,0,0,0)。染色不改变字形透明度(透明度由 Opacity 通道单独控制)。

混色算法:染色采用 Pigment-Based Mixing(PBM) 物理颜料混色(Texturge::GlyphRender::ApplyTint)——基于 Kubelka–Munk(K–M)双流理论 + Duncan 浓度叠加、常数参考标定于 Mixbox 2.0 的实时 RGB 颜料混色,在 sRGB 显示值空间工作,无光谱数据、无查找表、O(1) 常数时间,严格交换对称 F(b, t, α) ≡ F(t, b, 1−α)。效果贴合真实颜料:黄+蓝变绿,互补色对混合产生泥色(橄榄/棕/暗紫)而非灰色,深色加白得到饱和浅色而非灰白。算法以 MIT 协议开源(pigment-based-mixing)。

FPerLayerCurves

单层曲线数据——21 条 TArray<FRichCurveKey> + 播放元数据。这是 FRichCurve 非 USTRUCT 约束下完全 UPROPERTY 化的存储方案:求值时临时构建 FRichCurve

USTRUCT()
struct TEXTURGE_API FPerLayerCurves
{
    // 播放元数据
    ETextAnimationBlendMode BlendMode = ETextAnimationBlendMode::Override;
    EPlayMode PlayDirection = EPlayMode::Forward;
    int32     NumberOfLoops = 1;          // 0 = 无限循环
    float     PlaySpeedMultiplier = 1.0f;
    float     LayerDuration = 0.0f;       // 烘焙时预计算(max key - min key)

    // 21 条曲线通道(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;                          // 所有曲线是否全为空
    float GetEffectiveDuration() const;            // 有限循环 = LayerDuration × Loops;无限 = FLT_MAX
    void Evaluate(float LocalTime, FGlyphAnimationState& InOutState) const;
};

Evaluate 语义

  • 仅对 NumKeys > 0 的通道求值并组合——空通道不参与,保留 InOutState 已有值(层间无交叉污染)
  • PlayDirection != ForwardLayerDuration > 0 时对 LocalTime 做时间包裹:Forward = fmod 循环、Reverse = 反向 fmod、PingPong = 来回交替
  • 组合规则:Override 直接覆盖 / Additive 相加 / Multiply 相乘 / CrossFade 固定 0.5 Lerp 插值

FBakedGlyph

烘焙后的单字形运行时数据。

USTRUCT(BlueprintType, meta = (DisplayName = "烘焙字形"))
struct TEXTURGE_API FBakedGlyph
{
    UPROPERTY(BlueprintReadOnly) int32   OriginalIndex;   // 原始文本索引
    TCHAR Character;                                    // 调试用(非 UPROPERTY)
    UPROPERTY(BlueprintReadOnly) float   StartTime;      // 阶段内相对起始时间(秒)
    UPROPERTY(BlueprintReadOnly) float   Duration;       // 动画持续时间(秒)
    UPROPERTY(BlueprintReadOnly) bool    bIsStatic;      // 跳过动画的静态字形
    UPROPERTY(BlueprintReadOnly) FGlyphAnimationState DefaultState;  // 静态字形固定状态
    UPROPERTY(BlueprintReadOnly) bool    bHideFirstFrame;// 动画开始前完全透明
    TArray<FPerLayerCurves> LayerCurves;               // 逐层曲线(0 最底 → N-1 最顶)
    UPROPERTY(BlueprintReadOnly) FGlyphCurveOverride FactoryOverride; // 工厂级微调

    void Evaluate(float StageTime, FGlyphAnimationState& OutState) const;
    bool HasStarted(float StageTime) const;
    bool HasFinished(float StageTime) const;
    void AddLayer(const FGlyphCurveSet& Source, ETextAnimationBlendMode InBlendMode);
    // 单层兼容辅助: IsCurvesEmpty / GetCurvesTimeRange / CopyCurvesFrom /
    //              OffsetCurvesBy / BuildCurveSet
};

Evaluate 语义

  • bIsStatic → 直接返回 DefaultState
  • 尚未开始(LocalTime < 0)→ 返回恒等状态(不透明度 1 完全显示),仅 bHideFirstFrame 为真时透明
  • 逐层独立求值后按 BlendMode 组合:OutState = Identity() → 每层 Layer.Evaluate(LocalTime, OutState)

FBakedStage

烘焙后的单阶段数据。完全由 UPROPERTY 构成,支持作为 BlueprintNativeEvent 返回类型(Kismet 编译器安全)。

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 且数组匹配
    int32 GetStartedGlyphCount(float StageTime) const;
};

FStageTransitionConfig

USTRUCT(BlueprintType, meta = (DisplayName = "阶段过渡配置"))
struct TEXTURGE_API FStageTransitionConfig
{
    float IntroToDefaultCrossFade = 0.1f;                 // 入场→默认过渡时长(秒)
    float DefaultToOutroCrossFade = 0.1f;                 // 默认→出场过渡时长(秒)
    TEnumAsByte<EEasingFunc::Type> EasingFunc = EEasingFunc::Linear;  // 过渡缓动
};

该配置位于 UTextAnimationBlueprint::StageTransitionConfig,参与蓝图指纹哈希。

FAnimationEntry

UTextAnimationDataAsset::Entries[] 的行数据结构——标签名到动画蓝图的映射。

USTRUCT(BlueprintType, meta = (DisplayName = "动画条目"))
struct TEXTURGE_API FAnimationEntry
{
    FName TagName;                                          // 富文本标签名
    TObjectPtr<UTextAnimationBlueprint> Type;               // 动画蓝图资产
    TMap<FName, float>         ParameterOverrides;          // Float 覆盖
    TMap<FName, int32>         IntParameterOverrides;       // Int 覆盖
    TMap<FName, bool>          BoolParameterOverrides;      // Bool 覆盖
    TMap<FName, FVector>       VectorParameterOverrides;    // Vector 覆盖
    TMap<FName, FLinearColor>  ColorParameterOverrides;     // Color 覆盖
    TMap<FName, FVector2D>     Vector2DParameterOverrides;  // Vector2D 覆盖
    bool bHideFirstFrame = false;                           // 顺序播放时未执行条目提前透明
};

FSemanticAnchor

USTRUCT(BlueprintType, meta = (DisplayName = "语义锚点"))
struct TEXTURGE_API FSemanticAnchor
{
    FName   AnchorId;           // 锚点标识符
    FString ContextualSnippet;  // 上下文片段
    int32   SourceOffset;       // 源偏移量
};

ULocalizationSubsystem::RelocateAnchors 处理:精确匹配(忽略大小写)+ LCS 最长公共子串近似匹配 + 置信度去重(MaxTextLength = 10000 上限)。

FTextAnimationSubTrackData

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

C++ 结构体(非 USTRUCT)

FBakedAnimation

完整烘焙结果。不是 USTRUCT(内含 TUniquePtr),仅在 C++ 层使用。

struct FBakedAnimation
{
    FString SourceText;
    int32   GlyphCount = 0;
    uint64  TextFingerprint = 0;        // CityHash64(SourceText)
    uint64  BlueprintFingerprint = 0;   // 哈希(路径 + 资产签名 + BP 变量 + CompileCount + 生成类地址)

    TUniquePtr<FBakedStage> IntroStage;     // 可为 nullptr
    TUniquePtr<FBakedStage> DefaultStage;   // 通常有效
    TUniquePtr<FBakedStage> OutroStage;     // 可为 nullptr

    const FBakedStage* GetStage(EAnimationStage Stage) const;
    bool HasStage(EAnimationStage Stage) const;
    EAnimationStage GetFirstValidStage() const;   // 优先 Intro,其次 Default
    EAnimationStage GetNextStageAfter(EAnimationStage Current) const;
    float GetTotalDuration() const;              // 所有存在阶段 Duration 之和
};

FGlyphCurveSet

C++ 内部临时计算容器——21 条 FRichCurveFRichCurve 非 USTRUCT,不能作 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

富文本解析的两级树节点(纯 C++)。

struct TEXTURGE_API FTagNode
{
    FString TagName;                        // 根节点为空
    TMap<FString, FString> Attributes;
    TArray<TSharedPtr<FTagNode>> Children;
    TWeakPtr<FTagNode> Parent;              // 弱指针防循环引用
    int32 RangeBegin;                       // 纯文本起始索引(包含)
    int32 RangeEnd;                         // 纯文本结束索引(不包含)
    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 索引
    FString Text;                           // TextContent: 叶子文本
};

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 通道映射总表

#曲线字段轨道类型所属状态属性
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(染色占比)
18Curve_ShadowColorRShadowColorShadowColor.R
19Curve_ShadowColorGShadowColorShadowColor.G
20Curve_ShadowColorBShadowColorShadowColor.B
21Curve_ShadowColorAShadowColorShadowColor.A

TIP

FGlyphAnimationState 是 10 字段(非 21 字段)——21 是指曲线通道数,Vector2D 与 Color 类型属性各自展开为 2 / 4 条通道。新增通道由 GlyphChannelTable.h 的 21 行成员指针注册表驱动(static_assert(21)),新增一条通道只需改动约 5 处。

数据结构关系图

UTextAnimationBlueprint
    ├── GlyphAnimations: TArray<UGlyphAnimation*>      ← Sequencer 曲线资产
    │       └── MovieScene → UMovieSceneGlyphAnimationTrack
    │               └── UMovieSceneGlyphAnimationSection[](子轨道)
    │                       └── FTextAnimationSubTrackData.Channels (FMovieSceneFloatChannel)
    ├── StageTransitionConfig
    └── [Compile] → UTextAnimationBlueprintGeneratedClass
            └── FAnimationCompiler::BakeAllStages(BP, Text, Customizer)
                    ├── ExtractCurves → FGlyphCurveSet(21 FRichCurve,缓存)
                    ├── BuildScheduleInfos → TArray<FGlyphScheduleInfo>
                    └── UGlyphAnimationLayer / UGlyphAnimationFactory
                            └── Bake() → FBakedStage
                    └── FBakedAnimation
                            ├── IntroStage / DefaultStage / OutroStage → FBakedStage
                            │       └── Glyphs: TArray<FBakedGlyph>
                            │               └── LayerCurves: TArray<FPerLayerCurves>(21 通道 × 每层)
                            │               └── FactoryOverride: FGlyphCurveOverride
                            ├── TextFingerprint / BlueprintFingerprint (uint64)

UTextAnimationStageController::EvaluateAllGlyphs
    └── FBakedGlyph::Evaluate(StageTime, OutState)
            └── 逐层 FPerLayerCurves::Evaluate(LocalTime, OutState) → 按 BlendMode 组合
                    └── FGlyphAnimationState

内存布局考量

  • FBakedAnimation 使用 TUniquePtr 持有三个舞台,move 语义支持 Double-Buffer 发布切换
  • FPerLayerCurves 是最大结构体——21 个 TArray<FRichCurveKey>,求值时临时构建 FRichCurve(性能代价微小,未来可缓存 + 脏位优化)
  • FGlyphAnimationState 是紧凑 POD 类型(10 个成员,约 64 字节),适合栈分配与批量处理
  • 运行时热路径(EvaluateAllGlyphsFBakedGlyph::Evaluate)全程栈变量,无堆分配

TIP

热路径中始终使用栈分配的 FGlyphAnimationState 变量;避免在循环内创建临时 TArray 容器以最大化吞吐量。大文本 + 多层时关注 FPerLayerCurves::Evaluate 的临时曲线构建开销。

images/data-structures.png — 数据结构关系图:从 UTextAnimationBlueprint(GlyphAnimations + StageTransitionConfig)经 FAnimationCompiler 编译为 FBakedAnimation(三阶段 FBakedStage → FBakedGlyph → 多层 FPerLayerCurves 21 通道),右侧为 FGlyphScheduleInfo 与 FGlyphCurveOverride 的结构示意 — 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