外观
Lesson 041|Sprite 从 SpriteFrame 到 RenderData 经历了什么?
Tags: #Creator2.x #Stage04 #Sprite #SpriteFrame #RenderDataDifficulty: ⭐⭐⭐⭐☆
真实问题:换了 SpriteFrame 后按钮为什么跳了一下
列表 Item 使用 Sprite.SizeMode.TRIMMED。异步加载新图标后,图标节点尺寸改变,Layout 重新排列,整行出现跳动。
代码只有:
ts
this.icon.spriteFrame = newFrame;但新 Frame 可能带来新的纹理、原始尺寸、裁剪信息、UV 和九宫格边界。Sprite 必须决定节点尺寸是否变化、几何类型如何生成以及是否还能与相邻组件合批。
🎯 本课目标
本课追踪一个最常见的 2D 渲染组件:
text
SpriteFrame
→ Sprite 组件
→ RenderData
→ Renderer理解它们的分工后,才能解释换图、改颜色、改尺寸和改节点位置分别会触发什么成本。
二、SpriteFrame 不是一张独立图片
SpriteFrame 通常描述:
text
使用哪张 Texture
从 Texture 的哪个矩形取图
旋转或裁剪信息
原始尺寸和边界因此:
text
Texture:大的像素资源
SpriteFrame:Texture 中的一块可使用区域
Sprite:把 SpriteFrame 作为渲染内容的组件图集中的多个 SpriteFrame 可以共享同一张 Texture。
三、Sprite 需要准备什么数据
典型四边形需要:
text
顶点位置
UV 坐标
顶点颜色
索引
纹理
材质
世界变换RenderData 是引擎为 Renderer 准备的中间数据,不等于 GPU 最终显存中的所有内容。
四、换 SpriteFrame 会发生什么
ts
this.sprite.spriteFrame = nextFrame;可能导致:
text
更新纹理区域和尺寸信息
↓
重新计算 UV
↓
检查顶点尺寸和 Anchor
↓
更新 RenderData
↓
可能改变 Texture / Material 分组如果新图来自另一张 Texture,还可能影响合批。
五、改颜色和改位置的差异
ts
this.node.color = cc.Color.RED;通常主要影响顶点颜色或材质输入。
ts
this.node.x = 100;主要影响世界变换和最终顶点位置。
ts
this.sprite.spriteFrame = frame;可能同时影响 UV、尺寸、纹理和批次。
不同属性的更新范围不同,不能用“改了 Sprite 所以全部重建”概括。
六、Simple、Sliced、Tiled 的差异
Sprite 不同类型可能生成不同几何数据:
text
Simple:通常是一个四边形
Sliced:根据边界切成多个区域
Tiled:重复生成纹理区域
Filled:根据填充比例裁剪或生成几何几何数量、UV 处理和重建成本会不同。一个带九宫格和填充效果的 UI 不一定和普通 Sprite 成本相同。
七、RenderData 的更新时机
可能触发更新的情况:
- SpriteFrame 改变。
- Content Size 改变。
- Anchor 改变。
- Fill 参数改变。
- Color 或 Opacity 改变。
- Node Transform 改变。
引擎会根据脏标记决定更新范围。业务代码不应依赖私有缓存状态,而应通过公开属性和合理的批量更新减少频繁修改。
八、案例:列表滚动中的 Sprite
滚动列表中每个 Item 都有 Sprite:
text
ScrollView 移动内容节点
↓
多个 Sprite 世界变换变化
↓
Renderer 重新计算或读取变换如果同时频繁替换 SpriteFrame、改尺寸、改颜色,CPU 和渲染数据更新都会增加。应结合对象池、可见 Item 数量和图集规划优化。
九、常见误区
误区一:SpriteFrame 就是 Texture
SpriteFrame 可能只是 Texture 的一个区域。
误区二:改颜色一定会换材质
颜色可能作为顶点属性传入,也可能受材质和组件实现影响。
误区三:所有 Sprite 都只有四个顶点
Sliced、Tiled、Filled 等模式可能生成更多几何数据。
误区四:更换同图集图片一定打断合批
同图集通常更容易共享纹理,但仍要看材质、渲染状态和其他条件。
十、深入推导:Sprite 准备 RenderData 时要回答什么
先把 Texture、SpriteFrame 和 Sprite 的身份彻底分开:
text
Texture
→ 一块可被 GPU 采样的像素资源
SpriteFrame
→ 描述从哪张 Texture 的哪个矩形采样
→ 还可能记录原始尺寸、裁剪偏移、旋转等信息
Sprite
→ 场景中的渲染组件
→ 决定如何把 Frame 放进当前 Node 的尺寸和颜色中一张 Atlas Texture 可以包含几十个 SpriteFrame。两个 Frame 名字和矩形不同,但 getTexture() 可能返回同一个 Texture,因此它们仍有共享纹理绑定的机会。
Trim 为什么会让“同样大小的图”看起来跳动
假设原图画布是 100×100,真正非透明内容只占中间 60×80。导入裁剪后,Frame 既要记住裁剪矩形,也要记住内容相对原画布的偏移。
换 Frame 时必须同时考虑:
text
原始尺寸是否相同?
裁剪后矩形是否相同?
Trim 偏移是否相同?
Sprite SizeMode 是否跟随 Frame?
Node Anchor 是否相同?
父 Layout 是否读取 Content Size?如果 SizeMode 根据新 Frame 修改 Node 尺寸,按钮不仅 UV 变了,布局和锚点位置也可能变化,所以画面会“跳一下”。
以 Simple Sprite 为例:
text
Node 当前世界变换是什么
SpriteFrame 在 Texture 中的矩形是什么
节点尺寸和锚点是什么
颜色与透明度是什么
使用哪一个 Material
顶点位置与 UV 是否需要重建
当前批次是否兼容RenderData 可以理解为 Renderer 提交前的 CPU 侧几何描述。它不是业务层应直接修改的稳定 API。
十一、四种常见变化为什么影响不同
判断更新成本时可以使用“哪部分答案变了”:
| 修改 | 顶点局部形状 | World Transform | UV | Texture/批次 |
|---|---|---|---|---|
| 移动 Node | 通常不变 | 变化 | 不变 | 通常不变 |
| 修改颜色 | 通常不变 | 不变 | 不变 | 可能仍兼容 |
| 换同 Texture Frame | 尺寸模式可能改变 | 可能连锁 | 变化 | 仍可能兼容 |
| 换另一 Texture | 尺寸模式可能改变 | 可能连锁 | 变化 | 可能拆批 |
表中的“通常”和“可能”很重要。Simple、Sliced、Tiled、Filled,自定义 Assembler 与具体 Renderer 路径可能有不同数据组织,必须用当前版本实验。
只移动节点
主要改变 Transform/世界顶点相关结果,UV 和 SpriteFrame 区域不变。
只改颜色
顶点颜色或渲染颜色相关数据变化,几何拓扑通常不变。
换同一 Texture 内的 SpriteFrame
UV、尺寸/裁剪信息可能变化;Texture 与材质可能仍相同。
换到另一张 Texture
除了 UV/尺寸,还改变采样资源,可能打断合批。
这就是为什么“属性修改”不能统一估价。
十二、Creator 2.4.x 实验:比较 Sprite 类型
图片导入、SpriteFrame 边框和 Sprite Type 通过 Creator 编辑器配置,不手改资源序列化文件。
场景准备
建立四个相同尺寸节点:
text
SimpleSprite
SlicedSprite
TiledSprite
FilledSprite使用同一张具有明显边框的 SpriteFrame。通过 Sprite 编辑器设置九宫格边界。
SpriteDataProbe.ts
ts
const { ccclass, property } = cc._decorator;
@ccclass
export default class SpriteDataProbe extends cc.Component {
@property([cc.Sprite])
sprites: cc.Sprite[] = [];
resizeAll() {
for (let i = 0; i < this.sprites.length; i++) {
const node = this.sprites[i].node;
node.width = node.width === 200 ? 320 : 200;
node.height = node.height === 100 ? 160 : 100;
}
}
changeColor() {
for (const sprite of this.sprites) {
sprite.node.color = cc.Color.RED;
}
}
changeFill() {
const filled = this.sprites.find(
value => value.type === cc.Sprite.Type.FILLED
);
if (filled) {
filled.fillRange = Math.random();
}
}
logFrames() {
for (const sprite of this.sprites) {
const frame = sprite.spriteFrame;
cc.log(
sprite.node.name,
'type=', sprite.type,
'size=', sprite.node.width, sprite.node.height,
'frame=', frame && frame.name,
'texture=', frame && frame.getTexture().name
);
}
}
}观察
- 改尺寸后,Simple、Sliced、Tiled 的视觉伸缩方式不同。
- Filled 的
fillRange变化会改变实际可见几何范围。 - 同一 Frame 不保证各 Type 生成相同顶点数量。
- 改颜色与改尺寸都可能使显示数据更新,但更新内容不同。
- 用 Profiler 比较大量静态 Sprite 与每帧改 fillRange 的 Sprite。
不要读取 _assembler、_renderData 等私有字段作为正式课程实验;内部结构可能随小版本改变。
十三、SizeMode 为什么会引发布局问题
SizeMode 并不是单纯的编辑器显示选项。它决定换 Frame 时 Node Content Size 是否跟随资源:
text
RAW:更接近原始未裁剪尺寸语义
TRIMMED:使用裁剪后区域尺寸语义
CUSTOM:保留业务设置的节点尺寸当父节点有 Layout 时,Content Size 是布局输入。换图如果改变尺寸,就会形成:
text
SpriteFrame 改变
→ Content Size 改变
→ Layout 重排
→ 兄弟位置改变
→ 多个 World Matrix 更新因此列表图标通常需要明确统一尺寸策略,而不是依赖每张资源的导入尺寸碰巧一致。
换 Frame 时:
text
RAW
→ 节点尺寸可能使用原始图片尺寸
TRIMMED
→ 节点尺寸可能使用裁剪后尺寸
CUSTOM
→ 保留业务设定尺寸若 Item 需要稳定布局,通常应明确节点尺寸策略,而不是让每张异步图片决定布局。可以使用固定容器、Aspect Fit 逻辑或统一图标规范。
十四、真实项目故障:装备列表换图导致 DrawCall 飙升
现象
原本所有图标来自同一 Atlas。新活动装备动态加载独立 PNG 后,列表滚动时 DrawCall 大幅增加。
根因链
text
相邻 Item 原本共享 Atlas Texture
→ 新图标各自使用独立 Texture
→ 渲染顺序中频繁切换 Texture
→ 批次被切开修复方向
- 活动图标按生命周期组织 Atlas/Bundle;
- 使用 Dynamic Atlas 前确认适用条件;
- 保持列表渲染顺序中的纹理连续;
- 用实际 DrawCall 和纹理内存共同评估;
- 不为了合批把所有生命周期资源塞进一张巨型 Atlas。
十五、故障诊断
换 Frame 后尺寸跳变
检查 SizeMode、Frame 原始/裁剪尺寸、Layout 和 Widget。固定 UI 应使用稳定容器或 CUSTOM 尺寸。
图片显示区域错误
检查 Sprite Type、九宫格边界、trim/rotate 信息、fill 参数和 Atlas 导入。
修改属性但画面不变
确认组件 enabled、节点 active、Frame 有效、颜色 alpha、材质和 Camera;不要试图手动调用私有 assembler 更新。
十六、练习、推导答案与版本边界
本课的 RenderData/Assembler 是 Creator 2.4.x 高层模型,私有字段不作为业务 API。后续源码阅读应固定到项目引擎版本。
- SpriteFrame 改变时哪些数据可能变? Texture、UV、原始/裁剪尺寸、旋转、边框和材质批次条件。
- Sliced 为什么通常比 Simple 顶点更多? 九宫格需要多个区域保持边角并拉伸中间,几何被划分为更多格。
- 同 Texture 换 Frame 一定不打断合批吗? 不一定,材质、状态、Mask、顺序和组件实现也必须兼容。
- 列表图标为何建议固定容器尺寸? 避免异步 Frame 的尺寸差异触发 Layout 重排和视觉跳动。
能力验收
你应该能从 SpriteFrame 找到底层 Texture 和 UV 区域,说明 Simple、Sliced、Tiled、Filled 如何改变几何,并分别预测移动、改色、换同纹理 Frame、换 Texture 对 RenderData 与批次的影响。
本课总结
text
SpriteFrame 描述纹理中的可用区域。
Sprite 组件把它转换为可绘制内容。
RenderData 保存顶点、UV、颜色和索引等中间数据。
不同属性变化会触发不同范围的更新。十二、下一课预告
text
Lesson042|Assembler 如何生成顶点、UV、颜色和索引数据?