性能最佳实践
遵循以下实践可在复杂文本动画场景中保持稳定帧率与低 CPU 开销。
1. 控制字形与图层规模
字形数量
每增加一个字形,每帧增加 图层数 × 非空通道数 次曲线求值。建议:
- 简单 UI 文本(标签、按钮):< 30 字形
- 标题动画:< 80 字形
- 长文本(对话框、说明):考虑分段,每段 < 150 字形
- 极端场景(直播弹幕等):控制在 500 字形以内并配合控件池化
图层数量
每增加一个图层,每帧每个字形增加对应通道的求值。建议:
- 单效果动画:1 图层
- 标准多效果:2–3 图层(预设库默认 ≤ 3 层)
- 复杂动画:≤ 5 图层
超过 5 图层时,考虑合并效果到同一层(利用 Multiply 调制而非独立图层)。
2. 调度提前退出
FBakedGlyph::Evaluate 内置三种跳过机制,最大化”不工作”字形比例:
bIsStatic— 静态字形直接返回DefaultState(零曲线求值)- 未开始(
LocalTime < 0)— 返回恒等状态,不进入曲线求值 - 已结束(
HasFinished)— 同样跳过求值
设计建议:合理配置调度延迟与跳过标志,让大部分字形在任何时刻处于”未开始/已完成”状态:
Layer->SetStartDelayAt(-1, 0.0f); // 起始延迟基准
Layer->SetDelayAt(-1, 0.05f); // 逐字形错峰 → 打字机式活跃窗口
Layer->SkipAt(-1); // 先全部跳过
// 按分类恢复需要动画的字形
for (int32 i = 0; i < Layer->GetGlyphCount(); ++i)
{
if (!Layer->IsWhitespaceAt(i))
{
Layer->SetSkipAnimationAt(i, false);
}
}
3. 控件池化
频繁创建/销毁 UAnimatedTextBlock / UAnimatedRichTextBlock 会产生 GC 压力与烘焙开销。建议实现控件池:
// 池化使用模式(以 UAnimatedTextBlock 为例)
UAnimatedTextBlock* Widget = Pool->Dequeue(); // 从池取出
Widget->Stop(); // 重置状态
Widget->SetText(FText::FromString(NewText));
Widget->SetBlueprintVariable(TEXT("Speed"), 2.0f); // 设置参数
Widget->Play(); // 播放
// 使用完毕后
Widget->Stop();
Pool->Enqueue(Widget); // 归还
TIP
池大小按同时显示的动画文本峰值设定,通常 32–64 个即可满足 UI 场景。控件复用同一蓝图时各实例通过
ParameterOverrides独立隔离参数,互不影响。
4. 预编译与指纹冷却
- 所有动画曲线在需要时(初始化 / 文本变化 / 蓝图编译)由
FAnimationCompiler::BakeAllStages完成烘焙,运行时零烘焙开销 - 双指纹(
TextFingerprint+BlueprintFingerprint)缓存命中时跳过烘焙 - 重烘焙冷却
MinFramesBetweenRebakes = 3:不要在每帧修改参数——参数扫频时烘焙被节流,但连续修改会产生反复烘焙。批量参数修改后一次性Play更高效 - 发布前对全部
UTextAnimationBlueprint执行编译(Compile),避免首次运行烘焙卡顿
5. 富文本条目策略
UAnimatedRichTextBlock 的每个 <anim> 标签区域独立烘焙与播放,多条目同时播放开销线性增长:
- 将标签映射集中在
UTextAnimationDataAsset管理 - 长对话拆分为短段落,利用
bSequentialPlayback(顺序播放)代替全部同时播放 - 未执行条目设置
bHideFirstFrame,避免顺序播放时的首帧闪现 - 避免大量不同动画蓝图条目——共用蓝图 + 条目级参数覆盖比多个独立蓝图更省内存
6. 合批建议
UAnimatedTextBlock 继承 UMG 标准文本控件,渲染由 Slate 自动合批:
- 同字体、同材质的文本自动合批为少量 Draw Call
- 避免在动画中频繁修改字体属性——字体变更破坏合批
- 多字体效果使用复合字体(Composite Font)而非多控件叠加
- 逐字形变换(平移/缩放/旋转)会打断合批——变换动画集中在少量控件上
7. 循环与长动画
- 循环优先用层循环:
SetPlayMode(方向, 倍速, NumberOfLoops)的NumberOfLoops(0 = 无限)在求值时对LocalTime做 fmod 包裹,无需烘焙超长动画 - 无限循环层:
NumberOfLoops = 0时GetEffectiveDuration返回FLT_MAX(阶段永不结束)——混合无限循环层与有限层时注意整体时长语义 - 动画剪切(
SetAnimationClipAt)裁剪不用的前/后端曲线,减少键数
TIP
性能优化优先顺序:减少字形数(
SkipAt跳过空白)→ 减少图层数 → 减少非空通道数(删除未使用轨道)→ 复用蓝图 + 条目级参数 → 合批控件。