使用cocos2d-x制作 Texture unpacker
使用cocos2d-x制作 Texture unpacker
没错,就是unpacker。
在大多数游戏包里面,可以找到很多纹理图集,他们基本上是用texture packer制作的,有plist文件和png图片组成。
如果原来的小图比较少,倒是可以自己在plist里面找名字,如果小图有几百张,那真的会找疯掉。所以今天就用cocos2d-x引擎制作了一个将纹理大图解包成一张张小图的工具。
1. 解析plist文件
cocos2d-x引擎中实现了解析plist纹理的逻辑,SpriteFrameCache类。可以看到SpriteFrameCache解析plist后,使用Map<std::string, SpriteFrame*>::_spriteFrames
存放这些小图。既然小图在这里面,那么我们将他们保存到文件中不就可以了吗~
SpriteFrameCache类没有提供获取_spriteFrames的接口,那么我们更改一下SpriteFrameCache类,提供一个获取该成员的接口即可:
const Map<std::string, SpriteFrame*>& SpriteFrameCache::getSpriteframes()
{
return _spriteFrames;
}
2. 生成图片
从SpriteFrameCache中获取到的是SpriteFrame,SpriteFrame是不能直接保存的,所以我们需要将它渲染到一张纹理上,再保存。
1) 将SpriteFrame渲染成一张纹理
由于在cocos3.x版本中渲染方式已经和2.x版本中的方式不一样了(使用渲染命令,而非2.x版本中的直接渲染),所以在生成纹理的时候需要注意一下:
Sprite* pSp = Sprite::createWithSpriteFrame(pSpriteFrame /*one sprite frame*/);
RenderTexture* texture = RenderTexture::create(pSp->getContentSize().width, pSp->getContentSize().height);
texture->setName(m_savePath + tempBuf);
texture->begin();
pSp->setPosition(pSp->getContentSize()/2); //--- be careful
pSp->visit();
texture->end();
以上代码只是添加了渲染纹理的命令,真正渲染完成这张纹理是在下一帧的时候,所以添加一个schedule,在下一帧将这个texture保存为图片。
2) 保存为图片
Image* image = texture->newImage(true); //frame渲染出的一个texture
if (image)
{
image->saveToFile("filename.png", false);
}
CC_SAFE_DELETE(image);
其实RenderTexture类提供了saveToFile的接口,为什么没有直接调用?因为该接口会将图片保存在doc目录下,我想在win32上把它保存在其他磁盘。
3. 除去无效图片
由于打纹理图集的时候,添加了一下加密操作,这样会导致plist文件里面解析出来会有很多无效图片(如:宽高只有1像素,多张完全一样的图片),明明有效图片只有10多张,解析出来后有几十张
1) 去除宽高只有1像素的frame
plist中的配置:
<key>1002_effup/0000</key>
<dict>
<key>frame</key>
<string>{{440,56},{1,1}}</string>
<key>offset</key>
<string>{-479.5,319.5}</string>
<key>rotated</key>
<false/>
<key>sourceColorRect</key>
<string>{{0,0},{1,1}}</string>
<key>sourceSize</key>
<string>{960,640}</string>
</dict>
<key>1002_effup/0001</key>
<dict>
<key>frame</key>
<string>{{440,56},{1,1}}</string>
<key>offset</key>
<string>{-479.5,319.5}</string>
<key>rotated</key>
<false/>
<key>sourceColorRect</key>
<string>{{0,0},{1,1}}</string>
<key>sourceSize</key>
<string>{960,640}</string>
</dict>
如上这两个frame宽高都是1像素,解析出来是无用的,所以需要剔除。
2) 去除重复的图片
plist中的配置:
<key>1002_effup/0010</key>
<dict>
<key>frame</key>
<string>{{440,56},{102,88}}</string>
<key>offset</key>
<string>{5,7}</string>
<key>rotated</key>
<false/>
<key>sourceColorRect</key>
<string>{{363,355},{212,50}}</string>
<key>sourceSize</key>
<string>{960,640}</string>
</dict>
<key>1002_effup/0093</key>
<dict>
<key>frame</key>
<string>{{440,56},{102,88}}</string>
<key>offset</key>
<string>{5,7}</string>
<key>rotated</key>
<false/>
<key>sourceColorRect</key>
<string>{{363,355},{212,50}}</string>
<key>sourceSize</key>
<string>{960,640}</string>
</dict>
如上所以,除了frame名称,其它字段均相同,这样的图片保存一张即可。
那么如何实现呢?
既然除了名称不一样,其他都一样,我们就用其他数据生成一个key,每保存一个frame,就把它的key缓存者,以后发现有相同key的直接舍弃。
Map<std::string, SpriteFrame*> framesMap = SpriteFrameCache::getInstance()->getSpriteframes();
for (Map<std::string, SpriteFrame*>::const_iterator itor = framesMap.begin(); itor != framesMap.end(); ++itor)
{
SpriteFrame* frame = itor->second;
GLuint textName = frame->getTexture()->getName();
const Rect& rect = frame->getRectInPixels();
bool isRotate = frame->isRotated();
const Vec2& offset = frame->getOffsetInPixels();
const Size& origSize = frame->getOriginalSizeInPixels();
// 去掉 过小的无效图片 (加密后?的plist会生成很多无效图片)
// #define INVALID_IMAGE_WIDTH 2
if (rect.size.width <= INVALID_IMAGE_WIDTH && rect.size.height <= INVALID_IMAGE_HEIGHT)
{
continue;
}
// key --- 去掉重复的图片 (加密后?的plist会有很多张重复图片)
sprintf(fileKeyBuf, "%d_%.1f%.1f%.1f%.1f_%s_%.1f%.1f_%.1f%.1f",
textName,
rect.origin.x, rect.origin.y, rect.size.width, rect.size.height,
isRotate ? "1" : "0",
offset.x, offset.y,
origSize.width, origSize.height);
if (m_textureList.find(fileKeyBuf) != m_textureList.end())
{
continue;
}
Sprite* pSp = Sprite::createWithSpriteFrame(itor->second);
RenderTexture* texture = RenderTexture::create(pSp->getContentSize().width, pSp->getContentSize().height);
texture->setName(itor->first + ".png");
texture->begin();
pSp->setPosition(pSp->getContentSize()/2); //--- be careful
pSp->visit();
texture->end();
m_textureList.insert(fileKeyBuf, texture);
++m_iFramesCount;
}
4. 按顺序重命名
当执行完上面第三步(删除无效、冗余图片)后,保存的每一个frame会出现不连续的情况。
如:frame0001.png过后就是frame0008.png
那么我们在保存图片的时候,就要重命名每一个frame,用m_iFramesCount记录当前是第几个了,然后根据它命名即可。
需要注意的是
for (Map<std::string, SpriteFrame*>::const_iterator itor = framesMap.begin(); itor != framesMap.end(); ++itor)
遍历前要先对framesMap排序。
5. 说明
该功能已经封装为一个类,放在github上了,需要的朋友可以自己去clone一份。
https://github.com/SongCF/TextureUnpacker
使用方法:
PlistTool *tool = new PlistTool();
std::vector<std::string> vec;
vec.push_back("Enemy.plist");
vec.push_back("1001_effup.plist");
tool->addUnpackList(vec);
tool->startUnpack([](){
MessageBox("unpack finished", "info");
});
这样就会在当前目录生成两个文件夹,存放两个plist解包出来的小图。
使用cocos2d-x制作 Texture unpacker的更多相关文章
- GDC 2016 神秘海域4中使用Substance制作Texture
TEXTURING UNCHARTED 4: A MATTER OF SUBSTANCE 原文链接 http://www.dualshockers.com/2016/03/16/amazing-unc ...
- cocos2d js 利用texture packer生成sprite
cc.spriteFrameCache.addSpriteFrames(res.winLose_plist,res.winLose_png); var frame = cc.spriteFrameCa ...
- 如何优化cocos2d程序的内存使用和程序大小
在我完成第一个游戏项目的时候,我深切地意识到"使用cocos2d来制作游戏的开发者们,他们大多会被cocos2d的内存问题所困扰".而我刚开始接触cocos2d的时候,社区里面的人 ...
- 如何优化cocos2d程序的内存使用和程序大小:第一部分
译者: 在我完成第一个游戏项目的时候,我深切地意识到“使用cocos2d来制作游戏的开发者们,他们大多会被cocos2d的内存问题所困扰”.而我刚开始接触cocos2d的时候,社区里面的人们讨论了一个 ...
- 如何优化cocos2d程序的内存使用和程序大小:第一部分_(转)
译者: 在我完成第一个游戏项目的时候,我深切地意识到“使用cocos2d来制作游戏的开发者们,他们大多会被cocos2d的内存问题所困扰”.而我刚开始接触cocos2d的时候,社区里面的人们讨论了一个 ...
- 如何优化cocos2d/x内存使用和程序大小的程序
从最初的:http://www.himigame.com/iphone-cocos2d/1043.html 译者: 在我完毕第一个游戏项目的时候.我深切地意识到"使用cocos2d来制作游戏 ...
- 优化cocos2d/x程序的内存使用和程序大小
本站文章均为李华明Himi原创,转载务必在明显处注明:转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/iphone-cocos2d/1043.html ☞ ...
- 怎样优化cocos2d/x程序的内存使用和程序大小
再次感谢原创者:Steffen Itterheim.原创博客原文地址: http://www.learn-cocos2d.com/2012/11/optimize-memory-usage-bundl ...
- [原创]cocos2dx加载网络图片&异步加载图片
[动机] 之前看到一款卡牌游戏,当你要看全屏高清卡牌的时候,游戏会单独从网络上下载,本地只存了非高清的,这样可以省点包大小,所以我萌生了实现一个读取网络图片的类. [联想] 之前浏览网页的时候经常看到 ...
随机推荐
- bzoj 3997 [TJOI2015]组合数学(DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3997 [题意] 给定一个nm的长方形,每次只能使经过格子权值减1,每次只能向右向下,问 ...
- MSI/MSI-X
MSI PCI2.2规范引进了MSI作为传统的基于线的中断的替代方案.MSI允许设备通过向一个特定的地址写入一个特定的值来允许中断,而不是使用一个专有的引脚来触发中断.注意消息的目的地址和消息数据被当 ...
- C++容器学习
以前自学C++的时候就没怎么看容器,一直以来也没怎么编过C++程序,现在想用C++写点东西,突感容器类型有些生疏,故做此笔记.(参考<C++ primer> 容器:容纳特定类型对象的集合. ...
- Spark系列(八)Worker工作原理
工作原理图 源代码分析 包名:org.apache.spark.deploy.worker 启动driver入口点:registerWithMaster方法中的case LaunchDriver ...
- java console ( mac osx ) 命令行编码
方法 vi ~/.bash_profile #添加新行, UTF-8表示你平台的编码方式 #例如你是GBK.GB18030的 #替换成你平台console可现实字符编码即可 export JAVA_T ...
- Android JNI之C/C++层调用JAVA
转载请声明:原文转自:http://www.cnblogs.com/xiezie/p/5930032.html 从C/C++层调用JAVA层代码步骤: 1. 在JAVA类中创建java方法和本地方法 ...
- ORM 是一种讨厌的反模式
本文由码农网 – 孙腾浩原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! (“Too Long; Didn’t Read.”太长不想看,可以看这段摘要 )ORM是一种讨厌的反模式,违背 ...
- 第二百四十七天 how can I 坚持
今天去了趟北海公园,看到地铁宣传图片挺好看的,实景也倒是不错,环境好了,哪都好,今天是蓝天白云啊. 回来的路上看了,扎克伯格对质疑的回应.哎.改变世界在硅谷是行动,而不是口号.change the w ...
- HDU 4791 Alice's Print Service (2013长沙现场赛,二分)
Alice's Print Service Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Ot ...
- BestCoder Round #69 (div.2) Baby Ming and Weight lifting(hdu 5610)
Baby Ming and Weight lifting Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K ( ...