没错,就是unpacker。
在大多数游戏包里面,能够找到不少纹理图集,他们基本上是用texture packer制做的,有plist文件和png图片组成。
若是原来的小图比较少,却是能够本身在plist里面找名字,若是小图有几百张,那真的会找疯掉。因此今天就用cocos2d-x引擎制做了一个将纹理大图解包成一张张小图的工具。git
cocos2d-x引擎中实现了解析plist纹理的逻辑,SpriteFrameCache类。能够看到SpriteFrameCache解析plist后,使用Map<std::string, SpriteFrame*>::_spriteFrames
存放这些小图。既然小图在这里面,那么咱们将他们保存到文件中不就能够了吗~github
SpriteFrameCache类没有提供获取_spriteFrames的接口,那么咱们更改一下SpriteFrameCache类,提供一个获取该成员的接口便可:缓存
const Map<std::string, SpriteFrame*>& SpriteFrameCache::getSpriteframes() { return _spriteFrames; }
从SpriteFrameCache中获取到的是SpriteFrame,SpriteFrame是不能直接保存的,因此咱们须要将它渲染到一张纹理上,再保存。工具
因为在cocos3.x版本中渲染方式已经和2.x版本中的方式不同了(使用渲染命令,而非2.x版本中的直接渲染),因此在生成纹理的时候须要注意一下:ui
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保存为图片。加密
Image* image = texture->newImage(true); //frame渲染出的一个texture if (image) { image->saveToFile("filename.png", false); } CC_SAFE_DELETE(image);
其实RenderTexture类提供了saveToFile的接口,为何没有直接调用?由于该接口会将图片保存在doc目录下,我想在win32上把它保存在其余磁盘。code
因为打纹理图集的时候,添加了一下加密操做,这样会致使plist文件里面解析出来会有不少无效图片(如:宽高只有1像素,多张彻底同样的图片),明明有效图片只有10多张,解析出来后有几十张排序
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像素,解析出来是无用的,因此须要剔除。游戏
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; }
当执行完上面第三步(删除无效、冗余图片)后,保存的每个frame会出现不连续的状况。
如:frame0001.png事后就是frame0008.png
那么咱们在保存图片的时候,就要重命名每个frame,用m_iFramesCount记录当前是第几个了,而后根据它命名便可。
须要注意的是
for (Map<std::string, SpriteFrame*>::const_iterator itor = framesMap.begin(); itor != framesMap.end(); ++itor)
遍历前要先对framesMap排序。
该功能已经封装为一个类,放在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解包出来的小图。