问题
I am wondering how to optimize the usage of CCSpriteBatchNode. In other words I understand that:
- 1) Each CCSpriteBatchNode instance performs one call to the draw method, resulting in a reduction of OpenGL calls and hence significant performance improvement
- 2) Each CCSpriteBatchNode can refer to one and only texture atlas
What I am not 100% sure and I would like your answer is:
3) If I have one texture atlas, e.g. game-art-hd.png, and create several CCSpriteBatchNode in various classes will I get mutliple draw calls? In other words, I assume that each instance of CCSpriteBatchNode will call its own draw method, resulting in multiple GL drawing calls and less performance than having one shared batch node. Am I right?
- 4) If I am using an animation made of multiple frames on a Sprite, I guess I should add the animation frames to the Sprite batch node. How can I do so?
Following there is a code snippet on how I normally animate a sprite. As can be noticed the sprite frames are not added to the sprite batch node. For better performance I should probably do this at initialization time. Is this correct?
NSMutableArray* frames = [[NSMutableArray alloc]initWithCapacity:2]; for (int i = 0; i < 4; i++) { NSString*bulletFrame = [NSString stringWithFormat:@"animation-%i.png", i]; CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache]spriteFrameByName:bulletFrame]; [frames addObject:frame]; } CCAnimation* anim = [CCAnimation animationWithFrames:frames delay:0.1f]; CCAnimate* animate = [CCAnimate actionWithAnimation:anim]; CCRepeatForever* repeat = [CCRepeatForever actionWithAction:animate]; [self runAction:repeat]; }
5) Partially refering to 4. do you confirm that is prefer to avoid adding and removing sprites to a sprite batch node at runtime?
6) Will CCSpriteBatchNode consider only sprites that have visible set to true or sprites that have a position that is actually outside the screen area?
Other considerations on 3
In order to address my assumption in 3., and reduce the number of CCSpriteBatchNode instances, my solution would follow what suggested by @Suboptimus in this answer. I like the suggested approach of initializing classes that want to share the same batch node with the MainScene class, rather than having them to access the MainScene via self.parent.parent.(...).parent.finallysharedbatchNode
Other people would instead suggest to refer to the MainScene via using self.parent.....parent and casting it correctly.
Is this the best approach also in Software Engineering terms?
I prefer making it clear where the sprites are added by using an explicit reference to the MainScene class.. this should help if I am working in team or if I change the class hierarchy. But the downside of this is that I "need" to store a reference to it if I want to add subsequently sprites to the batch node, resulting in more code to maintain.
The reason I am asking this question is that if find a slight clash between my traditional "Software Engineering" mind and the "Cocos2d parent-node" hierarchy approach. I am new to game programming and I'd like to understand which approach is the one that experienced game developers that work in large teams use :). H
回答1:
- Correct, but a "draw call" is not equivalent to executing the
draw
method. A draw call is a change in the OpenGL state that requires performing an expensive operation to reset the statemachine. Binding a new texture or changing transform fit the bill. - Correct.
- Correct.
- No need to do that. Animations run on sprites. So only the sprite needs to be batched. If one animation frame is not from the same texture atlas, CCSpriteBatchNode will complain when the animation tries to use such a frame.
- It's preferable. Add/Remove of sprites in a CCSpriteBatchNode is more expensive than add/remove of any other node. Because the sprite batch node's quads need to be updated. Though it probably only makes any difference if you have many child nodes and add/remove frequently.
- No and no. See my answer here.
Other considerations on 3:
If your scene hierarchy is not too deep, you can go with self.parent or maybe self.parent.parent on occasion but only where the parent-parent relationship is practically fixed (ie from a sprite batched sprite bypassing the sprite batch node in order to get to the underlying "true" parent). But I wouldn't recommend going any deeper. See my answer here for techniques for both self.parent and avoiding retain cycles.
The problem with self.parent.parent.(…).parent
is that this completely breaks if you need to change the parent child relationship, for example by adding or removing a parent node in the hierarchy. This will then crash badly with EXC_BAD_ACCESS and it's hard to debug because you will have to check each parent and parent's parent to see what kind of object it really is. Accessing parents over 3 or more levels of the hierarchy I wouldn't consider bad practice. It's a terrible practice.
Personally, for access to commonly used nodes like shared sprite batches, I prefer the solution where the "MainScene" becomes a temporary Singleton class for the time while it is active. Then you can do the following from any child node:
CCSpriteBatchNode* mainBatch = [MainScene sharedMainScene].spriteBatchNode;
To create this temporary singleton:
static MainScene* instance;
-(id) init
{
self = [super init];
if (self)
{
instance = self;
}
return self;
}
-(void) dealloc
{
instance = nil;
}
-(MainScene*) sharedMainScene
{
NSAssert(instance, @"MainScene is not initialized!");
return instance;
}
The difference to a real singleton is that it doesn't initialize the instance if it doesn't exist yet. Hence the NSAssert in sharedMainScene. You should only access the scene instance while it is already running, ie it's only to be used by child nodes of that particular scene.
This semi-singleton allows access to all of the scene's properties. You can also send messages that the scene could relay to other nodes. Or enqueue physics objects in the scene that need to be removed, but can't be removed during collision handling itself.
And if that singleton bothers you, there's always the possibility to get the running scene from the director:
MainScene* mainScene = (MainScene*)[CCDirector sharedDirector].runningScene;
You should be careful casting to the MainScene only if the runningScene is really a MainScene object. An isKindOfClass: check or assert is in order.
来源:https://stackoverflow.com/questions/12460548/cocos2d-6-doubts-on-usage-of-ccspritebatchnode