使用cocos2d-x製做 Texture unpacker

使用cocos2d-x製做 Texture unpacker


沒錯,就是unpacker。
在大多數遊戲包裏面,能夠找到不少紋理圖集,他們基本上是用texture packer製做的,有plist文件和png圖片組成。
若是原來的小圖比較少,卻是能夠本身在plist裏面找名字,若是小圖有幾百張,那真的會找瘋掉。因此今天就用cocos2d-x引擎製做了一個將紋理大圖解包成一張張小圖的工具。git


1. 解析plist文件

cocos2d-x引擎中實現瞭解析plist紋理的邏輯,SpriteFrameCache類。能夠看到SpriteFrameCache解析plist後,使用Map<std::string, SpriteFrame*>::_spriteFrames存放這些小圖。既然小圖在這裏面,那麼咱們將他們保存到文件中不就能夠了嗎~github

SpriteFrameCache類沒有提供獲取_spriteFrames的接口,那麼咱們更改一下SpriteFrameCache類,提供一個獲取該成員的接口便可:緩存

const Map<std::string, SpriteFrame*>& SpriteFrameCache::getSpriteframes()
{
	return _spriteFrames;
}

2. 生成圖片

從SpriteFrameCache中獲取到的是SpriteFrame,SpriteFrame是不能直接保存的,因此咱們須要將它渲染到一張紋理上,再保存。工具

1) 將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保存爲圖片。加密

2) 保存爲圖片

Image* image = texture->newImage(true);  //frame渲染出的一個texture
if (image)
{
	image->saveToFile("filename.png", false);
}
CC_SAFE_DELETE(image);

其實RenderTexture類提供了saveToFile的接口,爲何沒有直接調用?由於該接口會將圖片保存在doc目錄下,我想在win32上把它保存在其餘磁盤。code


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解包出來的小圖。

相關文章
相關標籤/搜索