Skip to content

Stage05|Performance ​

Lesson068|Label、Animation、Particle 的常见性能问题 ​

Tags: #Creator2.x #Stage05 #Label #Animation #ParticleDifficulty: ⭐⭐⭐☆☆


真实问题:战斗 HUD 单项都不慢,合在一起为什么掉帧 ​

单测 100 个 Label、100 个 Animation、1000 个 Particle 都勉强可接受;真实战斗中三者同时在峰值发生:

text
伤害数字生成与改字
技能节点播放 Animation
粒子大量发射与透明叠加
→ CPU、GC、DrawCall、Overdraw 同时上升

性能预算必须按真实并发组合验收。

一、本课目标 ​

本课不把 Label、Animation、Particle 简单归类为“性能差”,而是分析它们在什么使用方式下容易产生高成本。


二、Label 的常见成本 ​

Label 变化时可能涉及:

text
字符串处理
字体图集或字形查找
顶点/UV 更新
布局尺寸变化
渲染批次变化

高风险用法:

  • 每帧给大量 Label 赋相同字符串。
  • 动态字体频繁改变内容。
  • 长文本不断触发换行和布局。
  • 数字滚动创建大量临时字符串。

优化方向:只在值变化时更新、缓存格式、限制刷新频率、减少不可见 Label。


三、Animation 的常见成本 ​

动画可能同时更新:

text
位置、旋转、缩放、颜色、激活状态、事件

大量动画对象会增加采样、曲线计算和属性写入。需要检查:

  • 是否真的需要逐帧动画。
  • 不可见对象是否仍在播放。
  • 多个动画是否写同一属性。
  • 动画结束后是否清理引用和回调。

四、Particle 的常见成本 ​

粒子系统通常包含:

text
粒子生成
生命周期更新
顶点数据更新
透明混合
Overdraw

全屏、大尺寸、半透明、长生命周期粒子特别容易增加 GPU Fill Rate 和 CPU 更新成本。


五、案例:战斗数字和特效 ​

text
每次命中创建一个 Label
→ 播放缩放 Tween
→ 生成粒子
→ 透明淡出
→ 销毁

高频时可能同时造成:

  • JS 分配和 GC。
  • 节点创建销毁。
  • Label 更新。
  • 粒子 Overdraw。
  • DrawCall 和纹理切换。

应分别采样,而不是只关闭某一个效果。


六、常见误区 ​

误区一:把 Label 换成 Sprite 就一定更快 ​

Sprite 数量、纹理、DrawCall 和更新频率也会改变成本。

误区二:不可见动画自动没有成本 ​

需要确认节点、组件和动画是否真的停止更新。

误区三:粒子越少越好 ​

应根据视觉目标减少粒子数、生命周期、覆盖面积和采样次数。

误区四:性能问题只属于 CPU 或 GPU 其中一边 ​

Label、Animation、Particle 常常同时影响脚本、数据更新、提交和像素处理。


七、练习与答案 ​

  1. Label 频繁更新可能触发哪些工作?
  2. 为什么不可见动画仍需检查 enabled 和 activeInHierarchy?
  3. 粒子为什么容易引起 Overdraw?
  4. 战斗飘字应该从哪些维度采样?

答案:

  1. 字符串、字形/图集、顶点、布局和渲染数据更新。
  2. 不可见不代表调度一定停止。
  3. 粒子通常是多层半透明片元覆盖。
  4. 对象创建、GC、Label 更新、粒子 CPU/GPU、DrawCall 和内存。

Creator 2.4.x 综合实验:三个负载旋钮 ​

ComponentCostProbe.ts ​

ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class ComponentCostProbe extends cc.Component {
    @property(cc.Node)
    labelGroup: cc.Node = null;

    @property(cc.Node)
    animationGroup: cc.Node = null;

    @property(cc.Node)
    particleGroup: cc.Node = null;

    setLabels(active: boolean) {
        this.labelGroup.active = active;
    }

    setAnimations(active: boolean) {
        this.animationGroup.active = active;
    }

    setParticles(active: boolean) {
        this.particleGroup.active = active;
    }
}

实验矩阵 ​

text
只 Label
只 Animation
只 Particle
Label + Animation
Animation + Particle
三者同时

每组记录 CPU、DrawCall、p99、粒子覆盖和内存。组合成本可能不是简单相加,因为 GC、热降频和批次交错会出现。

Label 的重点证据 ​

  • 每秒字符串写入次数;
  • 动态字符集合;
  • Cache Mode;
  • 节点尺寸与 Layout;
  • 同时可见数量;
  • 对象池内 Label 是否仍持有字形/资源。

伤害数字常只需固定数字字符集、固定宽度、有限数量和低频更新。

Animation 的重点证据 ​

  • 活跃 Animation 数;
  • 每条 Clip 的曲线/属性数;
  • 是否动画大父树 Transform;
  • 不可见时是否仍播放;
  • 同一属性是否同时被 Tween/Layout 写入;
  • 事件帧回调数量。

动画节点 inactive/离屏不等于所有自定义动画系统自动暂停。

Particle 的重点证据 ​

  • 最大活跃粒子;
  • 发射率和生命周期;
  • Quad 尺寸;
  • 纹理透明留白;
  • Blend 与材质;
  • 是否离屏仍模拟;
  • 同屏多特效峰值。

真实项目故障:暴击连击时每次都卡 ​

一次暴击触发 30 个伤害 Label、6 套骨骼动画、10 个粒子发射器和震屏。优化按优先级:

  1. 合并同类事件,同帧限制最大伤害数字;
  2. 伤害数字使用容量有限的池并完整 reset;
  3. 粒子按设备档位限制发射;
  4. 不可见/远处动画降频;
  5. 相同效果资源共享;
  6. 峰值脚本跨帧调度但不改变战斗语义;
  7. 回放相同战斗数据比较 p99。

诊断与练习答案 ​

  1. Animation 数量少为何仍可能贵? 单个 Clip 可驱动大子树、多属性或复杂骨骼,成本看工作量而非组件数。
  2. Particle DrawCall 低为何仍慢? 大量重叠透明 Quad 可造成 Fill Rate。
  3. Label 对象池为何仍要限制容量? 池节点、组件和资源继续占内存,过大峰值不应永久保留。
  4. 为什么要测组合峰值? 实际玩法同时触发多个系统,资源竞争、GC 和热效应会放大长帧。

组合实验的回归标准 ​

只关闭某个系统看帧率,容易把“少了功能”误判成优化。每次组合实验应同时记录:

text
功能触发次数
可见 Label 数
活跃 Animation 数
活跃粒子数
CPU p95/p99
GPU/DrawCall
纹理与 JS 内存

例如把粒子数量减半后,除了帧时间,还要确认:

  • 伤害反馈仍然可辨认;
  • 粒子生命周期和对象池回收正确;
  • 动画事件不会提前/重复;
  • Label 文本没有因复用显示旧值;
  • 低端档与高端档降级规则一致。

分阶段验收 ​

  1. 单独开启 Label,确认文本和字体回归。
  2. 单独开启 Animation,确认曲线、事件和停止。
  3. 单独开启 Particle,确认发射、回收和透明效果。
  4. 开启三者组合,确认峰值。
  5. 快速开关页面和切场景,确认监听、Tween、粒子和池状态清理。

八、本课总结 ​

text
专项组件没有绝对好坏,成本取决于数量、频率、可见区域和资源配置。
优化时要拆开 CPU、GPU、内存和生命周期成本。

九、下一课预告 ​

text
Lesson069|启动速度、首屏时间与分阶段加载