沒錯,就是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解包出來的小圖。