Fork me on GitHub

CCSpriteBatchNode(附:CCMenu与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:

 

  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坐标。  

 

 

下面看看它的细节:

 

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

 

看看 batchNodeWithFile的实现:

 

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

 


 

再看看 initWithFile的实现:

 

  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. }  

 

 

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


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

 

 

  1. [batch addChild:spr z:2];  

 

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

 

  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];

[[CCSpriteFrameCachesharedSpriteFrameCacheaddSpriteFramesWithFile:@"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];

posted on 2012-04-06 16:56  pengyingh  阅读(6079)  评论(0编辑  收藏  举报

导航