zoukankan      html  css  js  c++  java
  • 使用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解包出来的小图。

  • 相关阅读:
    React跨域问题解决
    PBFT性能会下降? 各种算法的对比。
    ssl证书原理
    UTXO是什么?
    以太访solidity常用的函数有哪些
    ERC720和erc721的区别
    椭圆曲线加密和rsa对比
    将pdf书籍变成横排的方法
    童年回忆(1)
    Inheritance
  • 原文地址:https://www.cnblogs.com/songcf/p/4528366.html
Copyright © 2011-2022 走看看