可以让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;
}
厉害,楼主实现的效果如何?
可以考虑在服务器调textepacker的命令行。打包好了再下载下来。