如果在引擎内部增加一个合并纹理图集的函数?

可以让SpriteFrameCache读取一个目录下的多张图片合并成一个纹理图集?这样是否就不需要plist了?

你的意思是SpriteFrameCache动态合图吗?目前没有这个功能。而且合图最好是离线,比较省时间。

是的,这样开发阶段配表会比较方便,而且如果玩家做mod需要添加素材也可以不影响性能,希望制作组能考虑,谢谢!

实现了简单的一个类
C++
//
// TexturePacker.h
//
// Created by nomos on 17/10/25.
//
//

#ifndef TexturePacker
#define TexturePacker

#include “cocos2d.h”

class PackNode;

class TexturePacker:public cocos2d::Node
{
public:
CREATE_FUNC(TexturePacker)
bool init() override;
void reset();
std::vectorstd::string packTextures(const std::vectorstd::string& textureNames,bool powerOfTwo,std::string packName);
void addPackNode(PackNode* node);
void removePackNode(PackNode* node);
protected:
bool packTextures(float width,float height);
bool check();
cocos2d::Rect inverseRect(const cocos2d::Rect& rect);
void shrinkToFit();
void setImageSize(float width,float height);
void packReset();
bool packTexture(cocos2d::Sprite* sprite,int i);
void mergeNodes();
float sumAreas();
float getPrevPowerOfTwo(float width);
protected:
std::string _packName="";
cocos2d::Node* _renderNode=nullptr;
cocos2d::DrawNode* _debugBox=nullptr;
cocos2d::Vectorcocos2d::Sprite* _textures;
std::vector<PackNode*> _packNodes;
};

#endif /* TexturePacker */


``` C++ ```
//
//  TexturePacker.cpp
//
//  Created by nomos on 17/10/25.
//
//

#include "TexturePacker.h"


USING_NS_CC;

#define TP_DEBUG 0

class PackNode:public cocos2d::Node
{
public:
    static PackNode* create(const Vec2& pos,const Size& size,TexturePacker* texturePacker)
    {
        auto ret = new PackNode();
        if (ret&&ret->init(pos,size,texturePacker)) {
            ret->autorelease();
            return ret;
        }
        CC_SAFE_DELETE(ret);
        return nullptr;
    }
    bool init(const Vec2& pos,const Size& size,TexturePacker* texturePacker)
    {
        if (!Node::init()) {
            return false;
        }
        _isEmpty=true;
        this->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
        _boundingBox = DrawNode::create();
        this->addChild(_boundingBox,1);
        setSize(size);
        _texturePacker = texturePacker;
        _texturePacker->addPackNode(this);
        _sizeLabel = Label::create();
        this->addChild(_sizeLabel,2);
        _sizeLabel->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
        _sizeLabel->setPosition(Vec2(1, 1));
        _sizeLabel->setBMFontSize(5);
        if (TP_DEBUG==0) {
            _sizeLabel->setVisible(false);
            _boundingBox->setVisible(false);
        }
        this->setPosition(pos);
        return true;
    }
    
    void setSize(const Size& size)
    {
        _boundingBox->clear();
        _boundingBox->drawRect(Vec2::ZERO, size, Color4F::RED);
        _size = size;
    }
    
    void clear()
    {
        if (_boundingBox) {
            _boundingBox->clear();
        }
    }
    
    Size getSize() const
    {
        return _size;
    }
    
    float getWidth() const
    {
        return _size.width;
    }
    
    Rect getRect() const
    {
        return Rect(getPosition(), getSize());
    }
    
    std::string getName()
    {
        if (_sprite) {
            return _sprite->getName();
        }
        return "";
    }
    
    float getHeight() const
    {
        return _size.height;
    }
    
    float getArea() const
    {
        return _size.width*_size.height;
    }
    
    std::vector<Rect> splitRect(const Size& a,const Size&b)
    {
        std::vector<Rect> ret;
        CC_ASSERT(a.width>=b.width&&a.height>=b.height);
        int widthLeft = a.width-b.width;
        int heightLeft = a.height-b.height;
        if (widthLeft==0&&heightLeft==0) {
        }  else if (widthLeft==0) {
            Rect rectA = Rect(0, b.height,a.width ,heightLeft);
            ret.push_back(rectA);
        } else if (heightLeft==0) {
            Rect rectA = Rect(b.width, 0, widthLeft, a.height);
            ret.push_back(rectA);
        } else {
            if (widthLeft>=heightLeft) {
                Rect rectA = Rect(b.width, 0, widthLeft, a.height);
                Rect rectB = Rect(0, b.height, b.width, heightLeft);
                ret.push_back(rectA);
                ret.push_back(rectB);
            } else {
                Rect rectA = Rect(b.width, 0, widthLeft, b.height);
                Rect rectB = Rect(0, b.height, a.width, heightLeft);
                ret.push_back(rectA);
                ret.push_back(rectB);
            }
        }
        return ret;
    }
    
    bool insertTexture(Sprite* sprite)
    {
        
        bool ret = false;
        if (!getIsEmpty()) {
            return false;
        }
        if (_size.width>=sprite->getContentSize().width&&_size.height>=sprite->getContentSize().height) {
            _sprite = sprite;
            this->addChild(_sprite);
            auto rects = splitRect(_size, sprite->getContentSize());
            for (auto iter:rects)
            {
                auto pos = iter.origin;
                PackNode::create(pos+this->getPosition(),iter.size,_texturePacker);
            }
            auto size = sprite->getContentSize();
            this->setSize(size);
            this->_sizeLabel->setString("w:"+Value((int)size.width).asString()+"h:"+Value((int)size.height).asString());
            
            this->setIsEmpty(false);
            ret=true;
            
        }
        return ret;
    }
    bool merge(PackNode* node)
    {
        if (!getIsEmpty()||!node->getIsEmpty()) {
            return false;
        }
        if (getWidth()==node->getWidth()) {
            //下边重合
            if (getPosition().y==node->getPosition().y+node->getHeight() && getPosition().x==node->getPosition().x) {
                node->setSize(Size(getWidth(), getHeight()+node->getHeight()));
                _texturePacker->removePackNode(this);
                return true;
            }
        }
        else if (getHeight()==node->getHeight()) {
            //左边重合
            if (getPosition().x==node->getPosition().x+node->getWidth() && getPosition().y==node->getPosition().y) {
                node->setSize(Size(getWidth()+node->getWidth(), getHeight()));
                _texturePacker->removePackNode(this);
                return true;
            }
        }
        return false;
    }
    
    bool operator<(PackNode* node) const
    {
        if (!node->getIsEmpty()) {
            return false;
        }
        if (getArea()==node->getArea()) {
            return getPosition().x+getPosition().y>node->getPosition().x+node->getPosition().y;
        }
        return getArea()>node->getArea();
//        return getPosition().x+getPosition().y>node->getPosition().x+node->getPosition().y;
    }
protected:
    virtual ~PackNode(){}
    Label* _sizeLabel=nullptr;
    Sprite* _sprite=nullptr;
    Size _size=Size::ZERO;
    DrawNode* _boundingBox=nullptr;
    TexturePacker* _texturePacker=nullptr;
    CC_SYNTHESIZE(bool, _isEmpty, IsEmpty)
    
    
};

bool PackCompareBL(PackNode* a,PackNode* b) 
{
    if (!b->getIsEmpty()) {
        return false;
    }
    if (a->getArea()==b->getArea()) {
        return a->getPosition().x+a->getPosition().y<b->getPosition().x+b->getPosition().y;
    }
    return a->getArea()>b->getArea();
}

bool PackCompareBottom(PackNode* a,PackNode* b)
{
    return a->getPosition().y<b->getPosition().y;
}

// !!!: - <<<TexturePacker>>> -

bool TexturePacker::init()
{
    if (!Node::init()) {
        return false;
    }
    _renderNode = Node::create();
    this->addChild(_renderNode);
    _debugBox = DrawNode::create();
    this->addChild(_debugBox);
    if (TP_DEBUG==0) {
        _debugBox->setVisible(false);
    }
    return true;
}


void TexturePacker::reset()
{
    packReset();
    _textures.clear();
}

bool compareSpriteArea(Sprite* a,Sprite* b)
{
    Size sizea = a->getContentSize();
    Size sizeb = b->getContentSize();
    float maxsideA = std::max(sizea.width,sizea.height);
    float maxsideB = std::max(sizeb.width,sizeb.height);
    float areaA = sizea.width*sizea.height;
    float areaB = sizeb.width*sizeb.height;
    if (maxsideA==maxsideB) {
        return areaA>areaB;
    } else {
        return maxsideA>maxsideB;
    }
}

std::vector<std::string> TexturePacker::packTextures(const std::vector<std::string> &textureNames,bool powerOfTwo,std::string packName)
{
    reset();
    for (auto iter:textureNames)
    {
        auto sprite = Sprite::create(iter);
        sprite->setName(iter);
        sprite->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
        if (sprite) {
            _textures.pushBack(sprite);
        }
        auto size = sprite->getContentSize();
    }
    std::sort(_textures.begin(), _textures.end(), compareSpriteArea);
    float imageWidth,imageHeight;
    float side = sqrtf(sumAreas());
    if (powerOfTwo) {
        imageWidth=imageHeight = getPrevPowerOfTwo(side);
    } else {
        imageWidth=imageHeight = side;
    }
    int loop=0;
    while (!packTextures(imageWidth,imageHeight) && loop<=100) {
        if (powerOfTwo) {
            if (imageWidth==imageHeight) {
                imageHeight*=2.0;
            } else {
                imageWidth*=2.0;
            }
        } else {
            imageWidth*=1.03;
            imageHeight*=1.03;
            imageWidth = floorf(imageWidth);
            imageHeight = floorf(imageHeight);
        }
        loop++;
        packReset();
    }
    
    if (powerOfTwo) {
        setImageSize(imageWidth, imageHeight);
    } else {
        shrinkToFit();
    }
    _packName = packName;
    auto image = utils::captureNode(_renderNode);
    auto texture = Director::getInstance()->getTextureCache()->addImage(image, _packName);
    std::vector<std::string> ret;
    for (auto iter:_packNodes)
    {
        if (!iter->getIsEmpty()) {
            SpriteFrameCache::getInstance()->addSpriteFrame(SpriteFrame::createWithTexture(texture, inverseRect(iter->getRect())), iter->getName());
            ret.push_back(iter->getName());
            CCLOG("TexturePacker: pack texture: %s,x: %d,y: %d,w: %d,h: %d",iter->getName().c_str(),(int)iter->getRect().origin.x,(int)iter->getRect().origin.y,(int)iter->getRect().size.width,(int)iter->getRect().size.height);
        }
    }
    return ret;
}

Rect TexturePacker::inverseRect(const cocos2d::Rect &rect)
{
    Rect ret = rect;
    auto size = _renderNode->getContentSize();
    ret.origin.y = size.height-ret.origin.y-ret.size.height;
    return ret;
}

void TexturePacker::shrinkToFit()
{
    
    for (auto iter = _packNodes.begin(); iter!=_packNodes.end(); ++iter) {
        auto node = (PackNode*)*iter;
        if (node->getIsEmpty()) {
            _packNodes.erase(iter);
            node->removeFromParent();
            --iter;
        }
    }
    float maxX=0;
    float maxY=0;
    for (auto iter:_packNodes)
    {
        float nodeRight = iter->getPosition().x+iter->getWidth();
        float nodeUp = iter->getPosition().y+iter->getHeight();
        maxX = maxX<nodeRight?nodeRight:maxX;
        maxY = maxY<nodeUp?nodeUp:maxY;
    }
    setImageSize(maxX, maxY);
    
}

void TexturePacker::setImageSize(float width, float height)
{
    if (_renderNode) {
        _renderNode->setContentSize(Size(width, height));
    }
    if (_debugBox) {
        _debugBox->clear();
        _debugBox->drawRect(Vec2::ZERO, Size(width, height), Color4F::ORANGE);
    }
}

void TexturePacker::packReset()
{
    for (auto iter:_packNodes)
    {
        iter->clear();
        iter->removeFromParent();
    }
    _debugBox->clear();
    _packNodes.clear();
}

void TexturePacker::addPackNode(PackNode *node)
{
    _packNodes.push_back(node);
    _renderNode->addChild(node);
}

void TexturePacker::removePackNode(PackNode *node)
{
    for(auto it=_packNodes.begin();it!=_packNodes.end();)
    {
        if((*it)==node)
            it=_packNodes.erase(it);
        else
            ++it;
    }
    node->clear();
    node->removeFromParent();
}

bool TexturePacker::packTextures(float width,float height)
{
    PackNode::create(Vec2::ZERO, Size(width, height), this);
    for (int i=0; i<_textures.size(); i++) {
        if (!packTexture(_textures.at(i),i))
        {
            for (auto texture:_textures)
            {
                texture->removeFromParent();
            }
            return false;
        }
    }
    return true;
}

bool TexturePacker::packTexture(cocos2d::Sprite *sprite,int i)
{
    mergeNodes();
    if (i<=_textures.size()/2.0) {
        std::sort(_packNodes.begin(), _packNodes.end(),PackCompareBL);
    } else {
        std::sort(_packNodes.begin(), _packNodes.end(),PackCompareBottom);
    }
    for (auto iter:_packNodes)
    {
        if (iter->insertTexture(sprite)){
            return true;
        }
    }
    return false;
}
void TexturePacker::mergeNodes()
{
    bool merged=false;
    for (auto iter:_packNodes)
    {
        for (auto iter1:_packNodes)
        {
            if (iter!=iter1 && iter->merge(iter1)) {
                merged=true;
                break;
            }
        }
        if (merged) {
            break;
        }
    }
    if (merged) {
        mergeNodes();
    }
}

float TexturePacker::sumAreas()
{
    float ret = 0;
    for (auto iter:_textures)
    {
        auto size = iter->getContentSize();
        ret+=size.width*size.height;
    }
    return ret;
}

float TexturePacker::getPrevPowerOfTwo(float width)
{
    float ret = 2.0;
    while (ret<width) {
        ret*=2.0;
    }
    return ret/2.0;
}










































3赞

厉害,楼主实现的效果如何?

可以考虑在服务器调textepacker的命令行。打包好了再下载下来。