本章我们将深入了解精灵(Sprite)。我们可以通过很多方式用单个文件或者纹理贴图集(Texture Atlases)来生成精灵。我也会在本章介绍如何创建和播放精灵动画。
纹理贴图集是一张包含很多图片的纹理贴图(图片),通常用于存放单个角色动画的所有动画帧。不过它的作用不止于此。实际上你可以把任何图片放进同一张纹理贴图中。我们的目的是把尽可能多的图片放进同一张纹理贴图中,以达到节省空间的目的。 我主要用TexturePackerGUI来生成纹理贴图集
下载地址:TexturePackerGUI
“精灵批处理”(Sprite Batching)是用于提高精灵渲染速度的技术。它可以提高渲染大量相同精灵的速度,不过它同纹理贴图集配合使的效率最高。如果你将纹理贴图集与精灵批处理配合使用的话,你只要调用一次渲染方法就可以完成纹理贴图集里所有图片的渲染。 “渲染方法调用”(draw call)是把必要的信息传递给图形处理硬件以完成整个或者部份图片渲染的过程。当你使用CCSprite的时候,每生成一个CCSprite节点都会调用一次渲染方法。每一次渲染方法调用导致的系统开销叠加起来会使游戏的帧率大约降低15%或者更多(除非你使用CCSpriteBatchNode,它的作用是作为一个额外的层用于添加多个精灵节点。前提是这些精灵节点使用的是同一个纹理贴图)。 |
CCSpriteBatchNode
每次系统在屏幕上渲染一张贴图时,图形处理硬件必须首先准备渲染,然后渲染图形,最后在完成渲染以后进行清理。上述过程是每一次在启动渲染和结束渲染之间存在的固定系统开销。如果图形处理硬件知道你需要使用同一张纹理贴图渲染一组精灵的话,图形处理硬件将只需要为这一组精灵执行一次准备,渲染,最后清理的过程了。
你可以看到屏幕上有几百个相同的子弹。如果你逐个渲染它们的话,你的游戏帧率将会下降至少15%。如果你使用CCSpriteBatchNode,你就可以避免帧率的下降。 |
把使用同一张纹理贴图的一组CCSprite节点添加到同一个CCSpriteBatchNode里,比逐个渲染CCSprite要高效很多。
以下是生成CCSprite的常用代码:
- CCSprite *sprite=CCSprite::create("sprite.png");
- this->addChild(sprite);
当然,向CCSpriteBatchNode添加一个CCSprite节点不会有任何效率上的提高。
接下来,我把许多使用同一张纹理贴图的精灵节点添加到了同一个CCSpriteBatchNode里
- CCSpriteBatchNode *node=CCSpriteBatchNode::create("s1.png");
- this->addChild(node);
-
- for(int i=0;i<100;i++){
- CCSprite *sprite=CCSprite::create("s1.png");
- node->addChild(sprite);
- }
CCSpriteBatchNode的作用很像CCLayer,因为它本身并不显示在屏幕上。不过你只能把CCSprite加入CCSpriteBatchNode。CSpriteBatchNode将一个图片文件名作为参数,使用这个参数的原因是所有被添加进CCSpriteBatchNode的CCSprite节点都必须使用同一个图片文件。如果你没有在CCSprite中使用相同的图片,你将会在调试窗口中得到报错信息。
----------------------------------------------------
什么时候应该使用CCSpriteBatchNode?
当你需要显示两个或者更多个相同的CCSprite节点时,你可以使用CCSpriteBatchNode。组合在一起的CCSprite节点越多,使用CCSpriteBatchNode得到的效果提升就越大。 不过也有一些限制。因为所有的CCSprite节点都添加到同一个CCSpriteBatchNode中,所以所有CCSprite节点都会使用相同的z-order(深度)来渲染. 另一个缺点是所有添加到同一个CCSpriteBatchNode中的CCSprite节点必须使用同一个纹理贴图。不过那也意味着CCSpriteBatchNode非常适合使用纹理贴图集。使用纹理贴图集的时候,你不仅仅局限于渲染一张图片;你可以把不同的图片放到同一个纹理贴图集中,然后利用CCSpriteBatchNode将所有图片渲染出来,以提高渲染速度。 如果把纹理贴图集和CCSpriteBatchNode配合使用的话,之前讨论的z-order渲染问题就变得不那么明显了,因为你可以在单个CCSprite节点里指定不同的zorder值. |
你可以把CCSpriteBatchNode看成CCLayer,唯一的区别是:CCSpriteBatchNode只接受使用同一张纹理贴图的CCSprite节点。有了这样的认识以后,我相信你会发现很多可以用到CCSpriteBatchNode的地方。
不使用CCSpriteBatchNode运行精灵动画
接下去我们讨论如何运行精灵动画(Sprite Animation)。因为你可以将所有的动画帧放在同一张纹理贴图集里以节省内存,所以这是另一个使用CCSpriteBatchNode的好地方。 |
不使用纹理贴图集将动画添加到Ship类中需要不少的代码
- bool MainScene::init() {
- bool bRet = false;
- do {
- // 1. 生成一个CCArray数组。
- // 2. 循环处理每一个动画帧:
- // a. 为每一个图片生成一个CCTexture2D节点
- // b. 利用CCTexture2D节点生成一个CCSpriteFrame
- // c. 把生成的CCSpriteFrame添加到CCArray中
- // 3. 利用CCArray中的动画帧生成一个CCAnimation节点
- // 4. (可选)把CCAnimation添加到一个CCSprite中
- // 5. 使用CCAnimate动作播放动画
-
- CCSprite *pSprite = CCSprite::create("q0.png");
- this->addChild(pSprite);
- pSprite->setPosition(CCPointMake(100,100));
-
- CCArray *array = CCArray::createWithCapacity(5);
- for (int i = 0; i < 5; i++) {
- CCString *filename = CCString::createWithFormat("q%i.png", i);
- CCTexture2D *tt2d = CCTextureCache::sharedTextureCache()->addImage(
- filename->getCString());
-
- CCSize size = tt2d->getContentSize();
- CCLog("size.width=%f,size.height=%f",size.width,size.height);
- CCRect rect = CCRectMake(0,0,size.width,size.height);
-
- CCSpriteFrame *frame = CCSpriteFrame::createWithTexture(tt2d, rect);
- array->addObject(frame);
- }
- CCAnimation *tion = CCAnimation::createWithSpriteFrames(array, 0.08f);
- CCAnimate *mate = CCAnimate::create(tion);
- CCRepeatForever *repeat = CCRepeatForever::create(mate);
- pSprite->runAction(repeat);
-
- bRet = true;
- } while (0);
- return bRet;
- }
代码没啥好讲解的 逻辑顺序都在注释中写出
在cocos2d中使用纹理贴图集
“纹理贴图集”可以帮助你节约宝贵的内存资源,也可以帮助提高精灵的渲染速度。因为一张纹理贴图集其实就是一张大图片,你可以使用CCSpriteBatchNode一次性渲染所有它所包含的图片,从而降低内存使用量和提高渲染效率。 |
- bool MainScene::init() {
- bool bRet = false;
- do {
-
- CCSprite *pSprite = CCSprite::create();
- this->addChild(pSprite);
- pSprite->setPosition(CCPointMake(100,100));
-
- // 加载纹理贴图集的精灵帧(sprite frame);同时加载相同名字的贴图
- CCSpriteFrameCache *framecache =
- CCSpriteFrameCache::sharedSpriteFrameCache();
- framecache->addSpriteFramesWithFile("anim.plist");
-
- framecache->spriteFrameByName("q0.png");
- CCArray *array=CCArray::createWithCapacity(5);
- // 加载飞船的动画帧
- for (int i = 0; i < 5; i++) {
- CCString *string = CCString::createWithFormat("q%i.png", i);
- CCSpriteFrame *spriteframe=framecache->spriteFrameByName(string->getCString());
- array->addObject(spriteframe);
- }
- // 使用所有生成的精灵动画帧创建一个动画对象
- CCAnimation *tion=CCAnimation::createWithSpriteFrames(array,0.5f);
- // 通过使用CCAnimate动作播放动画
- CCAnimate *mate=CCAnimate::create(tion);
- CCRepeatForever *repeat=CCRepeatForever::create(mate);
- pSprite->runAction(repeat);
- bRet = true;
-
- } while (0);
- return bRet;
- }
因为cocos2d会自动生成所有图片的缓存,所以你需要一个方法来卸载不再需要的贴图内存。大多数情况下,你可以依赖cocos2d来帮你卸载:
- CCSpriteFrameCache::sharedSpriteFrameCache()->removeUnusedSpriteFrames();
- CCTextureCache::sharedTextureCache()->removeUnusedTextures();
如果你想在加载新场景之前完全删除所有内存中的贴图,你应该使用以下方法
- CCSpriteFrameCache::purgeSharedSpriteFrameCache();
- CCTextureCache::purgeSharedTextureCache();