Skip to content

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 TransformUVTexture/批次
移动 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
            );
        }
    }
}

观察 ​

  1. 改尺寸后,Simple、Sliced、Tiled 的视觉伸缩方式不同。
  2. Filled 的 fillRange 变化会改变实际可见几何范围。
  3. 同一 Frame 不保证各 Type 生成相同顶点数量。
  4. 改颜色与改尺寸都可能使显示数据更新,但更新内容不同。
  5. 用 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。后续源码阅读应固定到项目引擎版本。

  1. SpriteFrame 改变时哪些数据可能变? Texture、UV、原始/裁剪尺寸、旋转、边框和材质批次条件。
  2. Sliced 为什么通常比 Simple 顶点更多? 九宫格需要多个区域保持边角并拉伸中间,几何被划分为更多格。
  3. 同 Texture 换 Frame 一定不打断合批吗? 不一定,材质、状态、Mask、顺序和组件实现也必须兼容。
  4. 列表图标为何建议固定容器尺寸? 避免异步 Frame 的尺寸差异触发 Layout 重排和视觉跳动。

能力验收 ​

你应该能从 SpriteFrame 找到底层 Texture 和 UV 区域,说明 Simple、Sliced、Tiled、Filled 如何改变几何,并分别预测移动、改色、换同纹理 Frame、换 Texture 对 RenderData 与批次的影响。

本课总结 ​

text
SpriteFrame 描述纹理中的可用区域。
Sprite 组件把它转换为可绘制内容。
RenderData 保存顶点、UV、颜色和索引等中间数据。
不同属性变化会触发不同范围的更新。

十二、下一课预告 ​

text
Lesson042|Assembler 如何生成顶点、UV、颜色和索引数据?