有关cocos2dx对精灵的优化问题

2015年03月16日 11:17 0 点赞 0 评论 更新于 2025-11-21 17:08

Cocos2d-x针对游戏设计的不同方面有不同的优化方案,可对内存、声音、图片格式、色彩等进行优化。网上已有很多关于这些方面的教程,大家可方便找到,本文不再赘述。今天主要探讨如何对精灵进行优化,因为在程序中,精灵的使用最为频繁,大到背景、UI,小到NPC、道具,只要是用图片展示的,都是精灵或其子类。在我看来,精灵就是一张按某种方式显示出来的纹理图片。鉴于精灵如此重要,我们有必要对其进行优化。优化方法主要有减小精灵图片大小、使用缓存(Cache)提前将精灵加载到内存中,以及在有大量精灵时采用批次渲染的方法。下面将从缓存和批次渲染这两个方面详细介绍如何优化精灵图片。

1、通过批次渲染的方法来优化精灵

在游戏的某些时刻,可能会用到大量的精灵,例如发射子弹、粒子效果等,这些精灵图片所使用的纹理通常是相同的。如果一张一张地渲染图片,势必会降低效率。在开发过程中,我们可以在窗口的左下角看到三行数字,其中第一行数字代表渲染批次。从本质上来说,渲染批次就是绘图的次数。在Cocos2d-x中使用OpenGL进行绘图,渲染批次越少越好,所以该数字显示在左下角供开发者参考。我们应尽量减少渲染批次,以提高游戏效率。具体方法是使用CCSpriteBatchNodeCCParticleBatchNode精灵批节点类和粒子批节点类。下面通过代码示例展示如何使用它们。

普通方式添加精灵

CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
// 创建了十个精灵将这十个精灵添加到当前的层中
for(int i = 0; i < 10; i++)
{
CCSprite * sprite = CCSprite::create("icon.png");
sprite->setPosition(ccp(CCRANDOM_0_1() * visibleSize.width, CCRANDOM_0_1() * visibleSize.height));
this->addChild(sprite);
}

上述代码采用一般的方式将精灵添加到层中,此时的渲染批次是10。

使用CCSpriteBatchNode添加精灵

// 创建一个CCSpriteBatchNode,传入的参数就是精灵们将要用到的图片
CCSpriteBatchNode * batchNode = CCSpriteBatchNode::create("icon.png");
// 或者使用texture2d初始化,里边传入一个texture
// CCSpriteBatchNode * batch = CCSpriteBatchNode::createWithTexture();
// 这一句写不写都可以,因为node的默认坐标就是(0,0)
batchNode->setPosition(CCPointZero);
for(int i = 0; i < 10; i++)
{
// 创建的这些精灵所使用的纹理必须和CCSpriteBatchNode相同,而且所有这些精灵必须在同一个渲染层
CCSprite * sprite = CCSprite::createWithTexture(batchNode->getTexture());
sprite->setPosition(ccp(CCRANDOM_0_1() * visibleSize.width, CCRANDOM_0_1() * visibleSize.height));
batchNode->addChild(sprite);
}
this->addChild(batchNode);

采用上述方法添加精灵到层中,渲染批次是1。这是因为使用了CCSpriteBatchNode类。从原理上来说,这个类也是一个节点(node),通常使用节点是为了方便管理精灵。创建一个节点,然后将精灵添加到该节点中,而节点本身是无法显示的,显示的是其内部的子节点。节点的长和宽均为0,坐标默认在父节点的左下角(原点处),所以对子节点位置的设置不会产生影响。CCSpriteBatchNode就是这样一个节点,不同的是创建时需要一个纹理,因为它是精灵节点。传入的纹理图片是子节点用到的纹理图片,我们可以设置好这些子精灵节点的坐标,然后添加到该节点中,再将该节点添加到其他层中,这样就可以实现批次渲染。该节点要求其子精灵节点和它使用相同的纹理,如果纹理不同,可以将纹理都打包到一张图片上,使用时从这张图片上截取,可使用texturepacker工具进行打包。

粒子系统的批次渲染

CCTexture2D * texture = CCTextureCache::sharedTextureCache()->addImage("fire.png");
for(int i = 0; i < 10; i++)
{
// CCParticleSun里边没有参数
CCParticleSystem * particle = CCParticleSun::create();
particle->setTexture(texture);
particle->setPosition(ccp(CCRANDOM_0_1() * visibleSize.width, CCRANDOM_0_1() * visibleSize.height));
this->addChild(particle);
}
CCTexture2D * texture = CCTextureCache::sharedTextureCache()->addImage("fire.png");
// 参数同样需要一张纹理图片
CCParticleBatchNode * particleBatch = CCParticleBatchNode::createWithTexture(texture);
// 也可以采用如下的方式创建
// CCParticleBatchNode * particleBatch = CCParticleBatchNode::create("fire.png");
for(int i = 0; i < 10; i++)
{
// CCParticleSun里边没有参数
CCParticleSystem * particle = CCParticleSun::create();
// 传入的texture必须和CCParticleBatchNode相同
particle->setTexture(texture);
particle->setPosition(ccp(CCRANDOM_0_1() * visibleSize.width, CCRANDOM_0_1() * visibleSize.height));
particleBatch->addChild(particle);
}
this->addChild(particleBatch);

这里的原理和CCSpriteBatchNode相同,就不再详细解释了。

2、使用缓存提前加载精灵

当我们使用纹理时,可以制作一个加载界面,将将要用到的纹理加载到缓存中,同时将它们的引用计数增加1,以防止这些纹理被释放。使用时直接从缓存中获取,这样可以提高效率。我们常用的缓存类有CCTextureCacheCCSpriteFrameCacheCCAnimationCache,下面分别介绍其用法。

bool HelloWorld::init()
{
// 1. super init first
if( !CCLayer::init() )
{
return false;
}

CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
// 缓存其实就是一个数组,把用到的纹理图片放到这个数组中,纹理的引用计数加1,这样的话就不会释放纹理图片了
// 等下一次使用的时候直接从这个数组中取就可以了,这样的话就不必再次加载到内存中了
CCTexture2D * texture = CCTextureCache::sharedTextureCache()->addImage("icon.png");
CCLog("%d", texture->retainCount()); // count = 1
// 使用如下俩种方法可以获得缓存中的纹理,第二种方法如果之前已经加载了纹理,这个时候不会重新加载
// 而是直接返回
texture = CCTextureCache::sharedTextureCache()->addImage("icon.png");
texture = CCTextureCache::sharedTextureCache()->textureForKey("icon.png");
CCLog("%d", texture->retainCount()); // count = 1

// 异步加载图片,开辟一个新的线程专门用来加载图片,加载完毕以后调用loadingCallBack函数
// 所以在这个init函数中是不应该去调用函数textureForKey的,因为你不知道是否加载好了纹理啊
CCTextureCache::sharedTextureCache()->addImageAsync("icon1.png",
this, callfuncO_selector(HelloWorld::loadingCallBack));

return true;
}

// object就是加载好了的纹理
void HelloWorld::loadingCallBack(CCObject * object)
{
CCTexture2D * texture = (CCTexture2D *)object;
CCLog("%d", texture->retainCount()); // count = 2
// 通过以下的方法取得的texture和上边的那个texture相同
// CCTexture2D * texture2 = CCTextureCache::sharedTextureCache()->textureForKey("icon1.png");
// CCLog("%d", texture2->retainCount());

// 会清除掉所有的纹理,在其他的地方不可以再引用这些纹理了
CCTextureCache::sharedTextureCache()->removeAllTextures();

// 以下的方法会remove掉count值为1的纹理,icon.png被remove掉了,个人认为实际用的时候就用这个
// CCTextureCache::sharedTextureCache()->removeUnusedTextures();

// count = 1
CCLog("%d", texture->retainCount());

// 查看纹理清除的信息
CCTextureCache::sharedTextureCache()->dumpCachedTextureInfo();

// 切换场景
CCDirector::sharedDirector()->replaceScene(TestScene::scene());
}

在新的场景中可以直接使用加载好的纹理。

综上所述,通过批次渲染和使用缓存提前加载精灵这两种方法,可以有效优化Cocos2d-x中精灵的使用,提高游戏的性能和效率。

作者信息

boke

boke

共发布了 3994 篇文章