CCSpriteBatchNode



一般游戏图片资源会打包成一张大图,这样节省空间,又提升速度。打包工具有Zwoptex和texturepacker等等。

   CCSpriteBatchNode的初始化只要一张图片,也就是那张大图。然后把所有用到那张大图里面的小图的sprite都加到 CCSpriteBatchNode的child,绘制效率就会提高。

1) 缓冲sprite帧和纹理

        // 从纹理贴图集中预加载精灵帧,这个方法做了以下几件事:

  • 寻找工程目录下面和输入的参数名字一样,但是后缀是.png的图片文件。然后把这个文件加入到共享的CCTextureCache中。
  • 解析plist文件,追踪所有的sprite在spritesheet中的位置,内部使用CCSpriteFrame对象来追踪这些信息。


        [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"table.plist"];

2) 创建一个精灵批处理结点

        // 为所有 静态刚体 设置的批处理精灵节点
        CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithFile:@"table.png"];
        [self addChild:batch];

  • 创建一个CCSpriteBatchNode对象,通过传递一个包含所有sprite的batch的名字作为参数,并把它加入到当前场景之中。
  • 接下来,你从batch中创建的任何sprite,你应该把它当作CCSpriteBatchNode的一个孩子加进去。只要sprite包含在batch中,那么就没问题,否则会出错。
  •         CCSprite* tableTop = [CCSprite spriteWithSpriteFrameName:@"tabletop.png"];
            tableTop.position = [Helper screenCenter];
            [batch addChild:tableTop];

  • CCSpriteBatchNode可以智能地遍历它的所有的孩子结点,并通过一次OpenGL ES call来渲染这些孩子,而不是以前每个sprite都需要一个OpenGL call,这样渲染速度就会更快。

   -(CCSpriteBatchNode*) getSpriteBatch
   {
       return (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];
   }

      CCSpriteBatchNode* batch = [[PinballTable sharedTable] getSpriteBatch];
      sprite = [CCSprite spriteWithSpriteFrameName:spriteFrameName];
      [batch addChild:sprite];

//Batch是一个CCSpriteBatchNode,下面Batch-》Sprite-》ChildrenSprite,

//它们的Tag分别为:TagofBatch,TagofSprite,TagofChildrenSprite 得到ChildSprite的方法如下,

根据各自的Tag得到相应的数据。

id Batch = [self getChildByTag:TagofBatch];

id Sprite = [Batch getChildByTag:TagofSprite];

CCSprite *ChildrenSprite = (CCSprite*) [Sprite getChildByTag:TagofChildrenSprite];

 

關於CCSpriteBatchNode,一个CCSpriteBatchNode是一种效率比较高的渲染精灵的方式。比如,你把CCSprite加到CCLayer中,那么sprite的draw函数在 每一帧调用时都会执行7个opengl 调用来完成sprite的渲染。一个精灵的时候当然没问题,但是,当你在屏幕上有200个精灵的时候,那么就会有200×7次opengl调用。。。而 CCSpriteBatchNode可以“批处理”它的孩子精灵的draw调用。这意味着,当把200个精灵加到 Spritesheet中去的时候,只要使用7个opengl调用就可以完成200个孩子的渲染了。

cocos2d裏面的描述

Detailed Description

CCSpriteBatchNode is like a batch node: if it contains children, it will draw them in 1 single OpenGL call (often known as "batch draw").

CCSpriteBatchNode can reference one and only one texture (one image file, one texture atlas). Only the CCSprites that are contained in that texture can be added to the CCSpriteBatchNode. All CCSprites added to a CCSpriteBatchNode are drawn in one OpenGL ES draw call. If the CCSprites are not added to a CCSpriteBatchNode then an OpenGL ES draw call will be needed for each one, which is less efficient.

Limitations:

  • The only object that is accepted as child (or grandchild, grand-grandchild, etc...) is CCSprite or any subclass of CCSprite. eg: particles, labels and layer can't be added to a CCSpriteBatchNode.
  • Either all its children are Aliased or Antialiased. It can't be a mix. This is because "alias" is a property of the texture, and all the sprites share the same texture.

 

 

 

优点:CCSpriteBatchNode 中的所有CCSprite只会被渲染1次,因此可以提高游戏的FPS。

限制:加入到 CCSpriteBatchNode 中的CCSprite必须使用同一张纹理图。

 

问:什么时候应该用CCSpriteBatchNode?

答:比如游戏中的子弹 就很适合用它,因为子弹都是一个样子。

答:通过TexturePacker生成的纹理图也适合使用它。

 

看一个简单的Demo:

 

[java]  view plain copy
  1. CCSpriteBatchNode *batch = [CCSpriteBatchNode batchNodeWithFile:@"shopAmber.png"];//初始化时给一张纹理图  
  2. [self addChild:batch];//加入到当前Layer  
  3.           
  4. CCSprite *spr = [CCSprite spriteWithFile:@"shopAmber.png"];//切记! 这里的纹理图必须和上面相同,否则会崩溃~  
  5. spr.position = ccp(10,10);  
  6. [batch addChild:spr z:2];  
  7.           
  8. CCSprite *spr2 = [CCSprite spriteWithFile:@"shopAmber.png"];  
  9. spr2.position = ccp(10,40);  
  10. [batch addChild:spr2 z:1];//可以指定z坐标。  

 

 

下面看看它的细节:

 

[java]  view plain copy
  1. //创建CCSpriteBatchNode  
  2. CCSpriteBatchNode *batch = [CCSpriteBatchNode batchNodeWithFile:@"shopAmber.png"];  

 

看看 batchNodeWithFile的实现:

 

[java]  view plain copy
  1. +(id)batchNodeWithFile:(NSString*) imageFile  
  2. {  
  3.     return [[[self alloc] initWithFile:imageFile capacity:defaultCapacity] autorelease];//defaultCapacity==29默认可以addChild29个精灵,应该会自动扩充<pre name="code" class="java">}  
 
   

 

 
   

 

再看看 initWithFile的实现:

 

[java]  view plain copy
  1. -(id)initWithFile:(NSString *)fileImage capacity:(NSUInteger)capacity  
  2. {  
  3.        //看看其实就是被加载成了一张2d纹理图。  
  4.        CCTexture2D *tex = [[CCTextureCache sharedTextureCache] addImage:fileImage];  
  5.     return [self initWithTexture:tex capacity:capacity];  
  6. }  

 

 

[java]  view plain copy
  1. [self addChild:batch];//把CCSpriteBatchNode加入当前Layer,batch就相当于一个Layer  


之后你向CCSpriteBatchNode里加精灵 就相当于向一个层里加精灵:

 

 

[java]  view plain copy
  1. [batch addChild:spr z:2];  

 

可以使用 CCSpriteFrameCache配合CCSpriteBatchNode一起使用,效率会更高:

 

[java]  view plain copy
  1. [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"Resources.plist"];  
  2.           
  3. CCSprite *spr = [CCSprite spriteWithSpriteFrameName:@"Icon.png"];  
  4. spr.position = ccp(10,10);  
  5. [batch addChild:spr z:2];  
  6.           
  7. CCSprite *spr2 = [CCSprite spriteWithSpriteFrameName:@"shopAmber.png"];  
  8. spr2.position = ccp(10,40);  
  9. [batch addChild:spr2 z:1];  

这样看上去使用了2张不同的图片,但是它们是在同一张纹理图里的。

 

 

附:

sprite能否同时使用CCSpriteBatchNode上的2张图?

现在很纠结一个问题,搞了好几天还是没解决,大虾们帮我看看,给个建议吧,谢谢。

我要实现的效果是这样:在敌人受到攻击受伤时,要增加一个冒烟的动画。


我现在遇到的困难是如下:
敌人位置是变化的,受伤烟雾的效果想通过一张图旋转,淡出这样,但是烟雾得跟随受伤的敌人。
在主类 :GameLayer 中,先加载了enemyBatchNode 大图,大图中包含多个敌人和受伤烟雾
 

enemyBatchNode = [CCSpriteBatchNode batchNodeWithFile:@"enemy-hd.png"];        
[sharedSpriteFrameCache] addSpriteFramesWithFile:@"enemy-hd.plist"];              

初始化一个敌人:         

Enemy *e =[ :2];
[enemyBatchNode addChild:e z:1];    


敌人类:Enemy初始化

复制代码
+(id) enemyInit:(int)enemyType
{      
  return [[ initWithMyEnemyImage:enemyType] autorelease];
}
-(id) initWithMyEnemyImage:(int)enemyType 
{             
  NSString *file = ;             
  if((self == ))            
  {                          
    type = enemyType;                     
  }            
  return self;
}
-(void)addSmoke
{   
   smokeSprite = ;        
  smokeSprite.visible = NO;
}
-(void)playSmoke
{            
  CGSize winSize = [ winSize];          
  id ac1 = ;            
  id ac2 = ;             
  id ac3 = ;           
  id ac4 = ;
}
复制代码



问题来了:因为烟雾要跟随敌人位置,所以我想是不是烟雾就要在敌人类里直接生成,然后到要播放的时候,就直接播放,但是我现在的addSmoke中,smokeSprite无论怎么初始化,都没法得到smoke小图,而是整张enemyBatchNode 大图的缩小版,所以恳请各位教我下,我这种情况,烟雾要如何加入呢?

 

我还有试了另一种方法,就是把smoke也单独用一个类,然后预先生成很多个,在哪个敌人受伤需要时,就加入进去,但是这样又有新的问题,因为烟雾的播放过程,我不知道怎样让它播放完就直接初始化为原来的状态下次再用,所以如果不能重用的话,要一次生成很多,fps就会严重下降,并且烟雾的位置不好控制,还有就是,因为多个敌人有层次的区分,如果单独弄的,又有新的问题

 

 

答:烟雾做个粒子效果撒,打中了敌人就出现烟雾,添加在敌人身上。然后设置烟雾存在多少秒后消失

 

 

把所有的特效,比如烟雾、爆炸之类的效果做成一个spritesheet,然后新建一个EffectsManager类,专门负责游戏中的特效。然后再也一些方法来显示这些特效就可以了。
每一个特效对应一个sprite,如果有多个特效要同时出现,则多创建几个对应的sprite。
不知道这样子你能理解不,目前我是这么设计的。

 

 

没有看很明白,认真想了一下,我现在的实际问题是出在如何显示上,也就是不知道是应该在敌人类内部显示还是在外部显示。内部显示时在添加特效时就出错了,而在外部显示的出现的问题2楼种描述。

 

 

 

最近TexturePacker来压缩图片,减少游戏的体积。当然就用到了CCSpriteBatchNode。

但发现在CCMenu中使用CCMenuItemSprite的时候报错,最终还是解决了这个问题

1)图片不加到CCSpriteBatchNode里面,按照老方法正常使用CCMenuItem

但达不到我想要减少游戏体积的目的

2)使用CCSprite代替,然后增加touch事件,来判断是否点击了CCSprite所在的区域,做相应处理

太麻烦了,要写很多代码。但效果不错,可以自己控制点击的效果

3)把CCMenuItemSprite的selectedSprite设置为nil

这样可以在CCMenu中使用CCSpriteBatchNode,但没有了selectedSprite后一些效果不好做了

4)CCMenuAdvanced (在网上看到的,具体怎么用不清楚)


第三点能满足我的需求,而且不增加任何多余代码。所以就没有去查第四点了

有兴趣的朋友,可以参考http://www.cocos2d-iphone.org/forum/topic/1435

以下是我的Layer的init中的代码片段

 

CCSpriteBatchNode *batchNode;

 

 

batchNode = [CCSpriteBatchNodebatchNodeWithFile:@"main.pvr.ccz"];

[selfaddChild:batchNode];

[[CCSpriteFrameCachesharedSpriteFrameCache] addSpriteFramesWithFile:@"main.plist"];

 

 CCSprite *accelerate = [CCSpritespriteWithSpriteFrameName:@"accelerateIcon.png"];

CCSprite *sortition = [CCSpritespriteWithSpriteFrameName:@"sortitionIcon.png"];

 

CCMenuItemSprite *item1 = [CCMenuItemSpriteitemFromNormalSprite:sortition 

                                                          selectedSprite:nil 

                                                                  target:self 

                                                                selector:@selector(doSortition)];

item1.position = ccp(35,31);

CCMenuItemSprite *item2 = [CCMenuItemSpriteitemFromNormalSprite:accelerate 

                                                          selectedSprite:nil 

                                                                  target:self 

                                                                selector:@selector(doAccelerate)];

item2.position = ccp(443,28);

CCMenu *menu = [CCMenumenuWithItems:item1,item2, nil];

menu.position = CGPointZero;

[selfaddChild:menu];




纹理贴图集是一张包含很多图片的纹理贴图(图片),通常用于存放单个角色 动画的所有动画帧。不过它的作用不止于此。实际上你可以把任何图片放进同 一张纹理贴图中。我们的目的是把尽可能多的图片放进同一张纹理贴图中,以 达到节省空间的目的。很多cocos2d开发者使用Zwoptex这个软件来创建纹理贴 图集。我会在本章介绍Zwoptex这个工具。

精灵批处理”(Sprite Batching)是用于提高精灵渲染速度的技术。它可以提高渲染大量相同精灵的速度,不过它同纹理贴图集配合使用的效率最高。如 果你将纹理贴图集与精灵批处理配合使用的话,你只要调用一次渲染方法就可 以完成纹理贴图集里所有图片的渲染。

“渲染方法调用”(draw call)是把必要的信息传递给图形处理硬件以完成整个或者部份图片渲染的过程。当你使用CCSprite的时候,每生成一个CCSprite 节点都会调用一次渲染方法。每一次渲染方法调用导致的系统开销叠加起来会 使游戏的帧率大约降低15%或者更多(除非你使用CCSpriteBatchNode,它的 作用是作为一个额外的层用于添加多个精灵节点。前提是这些精灵节点使用的 是同一个纹理贴图)。

CCSpriteBatchNode

每次系统在屏幕上渲染一张贴图时,图形处理硬件必须首先准备渲染,然后渲
染图形,最后在完成渲染以后进行清理。上述过程是每一次在启动渲染和结束
渲染之间存在的固定系统开销。如果图形处理硬件知道你需要使用同一张纹理
贴图渲染一组精灵的话,图形处理硬件将只需要为这一组精灵执行一次准备,
渲染,最后清理的过程了。

图6-1展示了这种批量渲染。你可以看到屏幕上有几百个相同的子弹。如果你逐 个渲染它们的话,你的游戏帧率将会下降至少15%。如果你使用CCSpriteBatchNode,你就可以避免帧率的下降。

图6-1.把使用同一张纹理贴图的一组CCSprite节点添加到同一个CCSpriteBatchNode里,比逐 个渲染CCSprite要高效很多。

以下是生成CCSprite的常用代码:

CCSprite* sprite = [CCSprite spriteWithFile:@”bullet.png”]; [self addChild:sprite];

当然,向CCSpriteBatchNode添加一个CCSprite节点不会有任何效率上的提高, 所以如列表6-1代码所示,我把许多使用同一张纹理贴图的精灵节点添加到了同 一个CCSpriteBatchNode里:

列表6-1.创建多个CCSprite节点,将它们添加到同一个CCSpriteBatchNode中以提高渲染速度

CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithFile:@"bullet.png"]; [self addChild:batch];

for (int i = 0; i < 100; i++)

{ }

CCSprite* sprite = [CCSprite spriteWithFile:@”bullet.png”]; [batch addChild:bullet];

CCSpriteBatchNode的作用很像CCLayer,因为它本身并不显示在屏幕上。不过 你只能把CCSprite加入CCSpriteBatchNode。在列表6-1中,CCSpriteBatchNode 将一个图片文件名作为参数,使用这个参数的原因是所有被添加进 CCSpriteBatchNode的CCSprite节点都必须使用同一个图片文件。如果你没有在

CCSprite中使用相同的图片,你将会在调试窗口中得到以下报错信息:

SpriteBatches[13879:207] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'CCSprite is not using the same texture id'

(注: CCSprite is not using the same texture id 的意思是:CCSprite没 有使用相同的纹理贴图id)

什么时候应该使用CCSpriteBatchNode当你需要显示两个或者更多个相同的CCSprite节点时,你可以使用 CCSpriteBatchNode。组合在一起的CCSprite节点越多,使用 CCSpriteBatchNode得到的效果提升就越大。

不过也有一些限制。因为所有的CCSprite节点都添加到同一个 CCSpriteBatchNode中,所以所有CCSprite节点都会使用相同的z-order(深度)

来渲染。如果你的游戏要求子弹在敌人的前面和后面同时飞过的话,你就必须 使用两个CCSpriteBatchNode节点,因为只有这样你才能使用具有不同z-order 的子弹精灵。

另一个缺点是所有添加到同一个CCSpriteBatchNode中的CCSprite节点必须使用 同一个纹理贴图。不过那也意味着CCSpriteBatchNode非常适合使用纹理贴图集。 使用纹理贴图集的时候,你不仅仅局限于渲染一张图片;你可以把不同的图片 放到同一个纹理贴图集中,然后利用CCSpriteBatchNode将所有图片渲染出来, 以提高渲染速度。

如果把纹理贴图集和CCSpriteBatchNode配合使用的话,之前讨论的z-order渲 染问题就变得不那么明显了,因为你可以在单个CCSprite节点里指定不同的z- order值。如果你可以把所有在游戏中用到的图片都放到同一张纹理贴图集里的 话,你在游戏中使用一个CCSpriteBatchNode就够了(虽然这种情况极少发生)。

你可以把CCSpriteBatchNode看成CCLayer,唯一的区别是:CCSpriteBatchNode 只接受使用同一张纹理贴图的CCSprite节点。有了这样的认识以后,我相信你 会发现很多可以用到CCSpriteBatchNode的地方。


一个通常会犯的致命错误

Sprite01项目展示了一个新的Objective-C开发者通常会犯的错误。你很容易犯 这个错误,但是又很难找到它。请查看一下列表6-2中的代码,你能看到哪里出 错了吗? 

列表6-2.一个通常在继承CCSprite(或者其它类)时会犯的致命错误

‐(id) init {

if ((self = [super initWithFile:@"ship.png"]))

{
}
return self;

[self scheduleUpdate];

}

上述代码的问题是:-(id)init这个方法是默认的初始化方法,它最终会被任何 特殊的初始化方法(比如initWithFile)所调用。因为上述代码又调用了

[super initWithFile:..]这个特殊的初始化方法,最终产生了一个死循环。 解决的方法很简单。你只要像列表6-3所示那样把初始化方法换一个名字(只要

不是-(id)init)就可以了。

列表6-3. 解决列表6-2中的死循环

‐(id) initWithShipImage

{
{

}

return self; }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值