引言
在 2D 游戏开发中,纹理图集(Texture Atlas)是优化渲染性能的核心技术。通过将多张小图合并到一张大图中,可以显著减少 Draw Call 次数,提升游戏帧率。Cocos Creator 支持 TexturePacker 生成的 plist 格式图集文件,不仅可以静态引用图片,还能通过解析 plist 数据实现帧动画播放。本文将深入解析 plist 文件的数据结构、各字段含义,以及在实际开发中的应用技巧。
为什么需要纹理图集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| ┌─────────────────────────────────────────────────────────────────────┐ │ 纹理图集优化原理 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 不使用图集(3 个 Draw Call) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 图片1 │ │ 图片2 │ │ 图片3 │ │ │ │ .png │ │ .png │ │ .png │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ └────────────┴────────────┘ │ │ │ │ │ 3 次 GPU 绘制 │ │ │ │ ──────────────────────────────────────────────────────────────── │ │ │ │ 使用图集(1 个 Draw Call) │ │ ┌─────────────────────────┐ │ │ │ ┌───┬───┬───┐ │ │ │ │ │ 1 │ 2 │ 3 │ │ 一张大图 │ │ │ └───┴───┴───┘ │ │ │ │ 图集.png │ │ │ └──────────┬──────────────┘ │ │ │ │ │ 1 次 GPU 绘制 │ │ │ │ 优势: │ │ • 减少 Draw Call(从 N 次降到 1 次) │ │ • 减少内存碎片 │ │ • 方便实现帧动画 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
| 方案 |
Draw Call |
内存占用 |
加载速度 |
| 独立图片 |
N 次 |
碎片化 |
慢 |
| 纹理图集 |
1 次 |
连续 |
快 |
plist 文件结构解析
整体结构
plist 文件本质上是一个 XML 或 JSON 格式的数据文件,描述了大图中每个小图的位置信息:
1 2 3 4 5 6 7 8
| { "frames": { }, "metadata": { } }
|
frames 字段详解
frames 字段包含了每张原始小图在大图中的位置信息:
1 2 3 4 5 6 7 8 9 10 11
| { "frames": { "sprite_01.png": { "frame": "{{2, 2}, {100, 80}}", "offset": "{0, 0}", "rotated": false, "sourceColorRect": "{{0, 0}, {100, 80}}", "sourceSize": "{100, 80}" } } }
|
| 字段 |
类型 |
说明 |
frame |
字符串 |
小图在大图中的位置和尺寸 {x, y}, {width, height} |
offset |
字符串 |
小图中心点相对于原始图中心点的偏移 |
rotated |
布尔 |
是否顺时针旋转 90 度 |
sourceColorRect |
字符串 |
去除透明部分后的实际内容位置和尺寸 |
sourceSize |
字符串 |
原始图片的完整尺寸 |
1 2 3 4 5 6 7 8
| { "metadata": { "format": 2, "size": "{512, 256}", "textureFileName": "atlas.png", "premultiplyAlpha": false } }
|
| 字段 |
说明 |
format |
plist 格式版本(1-3) |
size |
大图的总尺寸 |
textureFileName |
对应的大图文件名 |
premultiplyAlpha |
是否预乘 Alpha |
关键字段深度解析
frame 字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────────────┐ │ frame 字段解析 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 大图 (atlas.png) │ │ ┌────────────────────────────────────────┐ │ │ │ (0,0) │ │ │ │ ┌──────────┐ │ │ │ │ │ sprite_1 │ (x=2, y=2) │ │ │ │ │ 100x80 │ 宽度=100, 高度=80 │ │ │ │ └──────────┘ │ │ │ │ ↑ │ │ │ │ frame: "{{2, 2}, {100, 80}}" │ │ │ │ │ │ │ └────────────────────────────────────────┘ │ │ │ │ 含义: │ │ • {2, 2}:小图左上角在大图中的坐标 │ │ • {100, 80}:小图的宽度和高度 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
rotated 字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────────────┐ │ rotated 字段解析 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 原始图片 (100x80) │ │ ┌──────────────────┐ │ │ │ │ │ │ │ │ │ │ └──────────────────┘ │ │ │ │ rotated = true 时(顺时针旋转 90°) │ │ ┌────────┐ │ │ │ │ │ │ │ │ │ │ │ │ 在大图中以 80x100 存储 │ │ │ │ 渲染时引擎会自动逆时针旋转 90° 还原 │ │ └────────┘ │ │ │ │ 目的:更紧密地排列图片,减少大图空白区域 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
offset 字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ┌─────────────────────────────────────────────────────────────────────┐ │ offset 字段解析 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ sourceSize: {112, 63} 原始图片大小 │ │ ┌────────────────────────┐ │ │ │ (透明区域) │ │ │ │ ┌──────────────────┐ │ │ │ │ │ 实际内容 │ │ sourceColorRect: {{4, 4}, {106, 59}} │ │ │ │ 106 x 59 │ │ │ │ │ └──────────────────┘ │ │ │ │ (透明区域) │ │ │ └────────────────────────┘ │ │ │ │ 原始中心点:{112/2, 63/2} = {56, 31.5} │ │ 实际内容中心点:{4 + 106/2, 4 + 59/2} = {57, 33.5} │ │ │ │ offset = {57 - 56, 33.5 - 31.5} = {1, 2} │ │ │ │ 注意:Cocos 坐标系 y 轴向上为正 │ │ 在 plist 中 offset = {1, -2} │ │ │ │ 含义:实际内容相对于原始内容中心点的偏移 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
MovieClip 动画格式
数据格式示例
Egret 引擎的 MovieClip 动画数据格式(Cocos Creator 可兼容解析):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| { "mc": { "character_walk": { "frameRate": 24, "labels": [ { "name": "fall", "frame": 1, "end": 3 }, { "name": "move", "frame": 4, "end": 6 } ], "events": [ { "name": "@fallhalf", "frame": 2 }, { "name": "@movehalf", "frame": 5 } ], "frames": [ { "res": "7ADF0F11", "x": 224, "y": 123 }, { "res": "69D013A4", "x": 228, "y": 122 } ] } }, "res": { "7ADF0F11": { "x": 1, "y": 87, "w": 105, "h": 80 } } }
|
mc 字段说明
| 字段 |
说明 |
mcName |
MovieClip 名称,可包含多个动画 |
frameRate |
帧率,默认 24fps |
labels |
帧标签列表,用于分段播放 |
events |
帧事件列表,以 @ 开头 |
frames |
关键帧数据列表 |
labels 标签
1 2 3 4 5
| { "name": "move", "frame": 4, "end": 6 }
|
| 字段 |
说明 |
name |
标签名称 |
frame |
标签起始帧 |
end |
标签结束帧(可在此范围内循环) |
res 资源字段
1 2 3 4 5 6 7 8
| { "7ADF0F11": { "x": 1, "y": 87, "w": 105, "h": 80 } }
|
| 字段 |
说明 |
x |
资源在纹理集中的 x 坐标 |
y |
资源在纹理集中的 y 坐标 |
w |
资源宽度 |
h |
资源高度 |
Cocos Creator 中使用图集
静态图片引用
1 2 3 4 5 6 7 8 9 10 11 12 13
| cc.loader.loadRes("atlas/plist", cc.SpriteAtlas, (err, atlas) => { if (err) { cc.error('加载图集失败:', err); return; }
const frame = atlas.getSpriteFrame('sprite_01');
this.node.getComponent(cc.Sprite).spriteFrame = frame; });
|
帧动画实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| const { ccclass, property } = cc._decorator;
@ccclass export default class FrameAnimation extends cc.Component {
@property(cc.SpriteAtlas) atlas: cc.SpriteAtlas = null;
@property frameRate: number = 12;
private _frames: cc.SpriteFrame[] = []; private _currentFrame: number = 0; private _isPlaying: boolean = false;
onLoad() { const spriteFrames = this.atlas.getSpriteFrames();
this._frames = spriteFrames.sort((a, b) => { return a.name.localeCompare(b.name); }); }
play(loop: boolean = false) { this._isPlaying = true; this._currentFrame = 0;
this.schedule(() => { this._updateFrame();
if (!loop && this._currentFrame >= this._frames.length - 1) { this.stop(); } }, 1 / this.frameRate); }
stop() { this._isPlaying = false; this.unscheduleAllCallbacks(); }
private _updateFrame() { if (this._frames.length === 0) return;
const sprite = this.getComponent(cc.Sprite); sprite.spriteFrame = this._frames[this._currentFrame];
this._currentFrame = (this._currentFrame + 1) % this._frames.length; } }
|
动态创建图集(运行时)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
function createAtlasFromTexture(texture: cc.Texture2D, frames: any[]): cc.SpriteAtlas { const atlas = new cc.SpriteAtlas();
for (const frameData of frames) { const rect = new cc.Rect( frameData.x, frameData.y, frameData.width, frameData.height );
const spriteFrame = new cc.SpriteFrame( texture, rect, frameData.rotated || false, cc.v2(0, 0), new cc.Size(frameData.sourceWidth, frameData.sourceHeight) );
spriteFrame.name = frameData.name; atlas.addSpriteFrame(spriteFrame); }
return atlas; }
|
TexturePacker 设置建议
导出配置
| 设置项 |
推荐值 |
说明 |
| Data Format |
Cocos2d |
生成 plist + png |
| Texture Format |
PNG |
支持透明度 |
| Size Constraints |
POT (Power of 2) |
兼容旧设备 |
| Allow Rotation |
勾选 |
提高空间利用率 |
| Trim Mode |
Trim |
去除透明边缘 |
| Algorithm |
MaxRects |
最佳排列算法 |
图集尺寸选择
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ┌─────────────────────────────────────────────────────────────────────┐ │ 图集尺寸选择指南 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 小图集 (512 x 512) │ │ ─────────────────── │ │ • UI 按钮、小图标 │ │ • 加载速度快 │ │ │ │ 中图集 (1024 x 1024) │ │ ───────────────────── │ │ • 角色动画帧 │ │ • 场景元素 │ │ │ │ 大图集 (2048 x 2048) │ │ ───────────────────── │ │ • 大型角色、特效 │ │ • 注意老设备可能不支持 │ │ │ │ 最大限制 │ │ ─────────── │ │ • OpenGL ES 2.0: 2048 x 2048 │ │ • OpenGL ES 3.0: 4096 x 4096 │ │ │ └─────────────────────────────────────────────────────────────────────┘
|
总结
Cocos Creator 纹理图集的核心要点:
- 优化原理:将多张小图合并为一张大图,减少 Draw Call 和内存碎片
- plist 结构:
frames 描述每张图的裁剪信息,metadata 描述图集元数据
- 关键字段:
frame 定位大图中的位置,offset 处理透明边缘偏移,rotated 标识旋转优化
- 动画应用:通过
cc.SpriteAtlas 获取 SpriteFrame 序列,实现帧动画播放
- 工具配置:TexturePacker 选择 Cocos2d 格式,启用 Trim 和 Allow Rotation 优化空间利用率
- 尺寸限制:注意目标平台的纹理最大尺寸限制,老设备通常只支持 2048x2048
通过深入理解 plist 格式和图集原理,可以更高效地管理游戏资源,优化渲染性能。