I add my sprite frames to CCSpriteFrameCache. Then I create a CCSpriteBatchNode with my desired image file.
This is what I don't quite understand:
When I make a CCSprite, if I want to take advantage of the CCSpriteBatchNode, I need to initialize the CCSprite with [CCSprite spriteWithBatchNode: rect:]? But if that's the case, I don't see how am I taking advantage of CCSpriteFrameCache to get the frames, since now I would be manually making the rect.
So I guess I use [CCSprite spriteWithSpriteFrameName:] and then I add this sprite to the batch node. But I am still unsure.
You should use:
CCSprite *sp = [CCSprite spriteWithSpriteFrameName:#"monster.png"];
The .plist that you specified in the SpriteFrameCache will take care of the frames for you.
Then you create the sprite and add to the batch.
If you create the batchnode with a file called "myArt.png", you CAN ONLY add a sprite to it that is contained inside "myArt.png".
Hope it helps!
According to what I've learned of cocos2d. SpriteFrameCache and SpriteBatchNode have the same result but are used differently and can notice a slight performance difference if your game is very big...
CCSpriteFrameCache loads your frames according to when they are called by their named according to the plist file it was given. The atlas associated with the plist has to be added to the project as well or else the frames will be called but nothing will be found to be drawn. The Plist is like the address of where the image is located inside the image atlas.
The good part of CCSpriteFrameCache is that the code is neater, and smaller than CCSpriteBatchNode method, at the cost that for every call of that frame, it goes to that specific atlas and draws it.
CCSpriteBatchNode, on the other hand, loads the atlas and loads it in one draw call. This is efficient because it reduces the amount of times the draw has to be done per need in the game. The only difficulty here is that you need to do math for the rectangles of each sprite in the atlas. This is because lets say your atlas is of 2 actions of a character, the atlas image file has a size of 1024x1024, and each sprite has a size of 128x128. so you would do the math to get each rectangle for the whole jump action for example.(This is where .plist come in handy to avoid doing such math)
The code gets complicated as you can see but it will only do one call, making it performance-wise your best call.
Another way to use CCSpriteBatchNode is to have different static sprites and you would just do one draw call for those multiple static images or sprites.
If you need example code just ask, I would be more than happy to provide it.
Update: Adding Link for SpriteBatchNode and an Example of my own.
SpriteBatchNode:
Example using SpriteBatchNode with Ray Wenderlich
I believe in this guy, and I have learned alot of Cocos2d from his tutorials. I would suggest you to read other of his tutorials.
In a nutshell, CCSpriteBatchNode is the exact same process we did below with the CCSpriteFrameCache the ONLY difference and its that you add the Sprite Child Node to the CCSpriteBatchNode and not the Layer, BUT you do Add the CCSpriteBatchNode to the Layer.
This is the hard concept that new comers to Cocos2d get entangled at.
SpriteFrameCache:
The SpriteFrameCache I couldn't find a good example so here is one simple one.
//By doing this your sprites are now in the cache ready to be used
//by their names declared in the .plist file.
-(void) loadingSprites:(NSString*) plistName {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:plistName];
}
-(id)initGameLayer {
//CCSprite accepts CCSpriteFrame and your img is now ready to be displayed.
//However is still not drawn yet.
CCSprite * mySprite = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:framename];
//set a position if desired
//20 pixels to the right and 0 pixels to the top.
mySprite.position = CGPointMake(20,0);
//Now the Image has been drawn, making 1 draw call.
[self addChild:mySprite];
}
It is noteworthy to point out that CCSpriteBatchNode makes just 1 drawcall, HOWEVER all the sprites being added to the batchnode have to be part of the same SpriteAtlas.
And using SpriteFrameCache only its easier and simpler, but for every child added to the layer it means +1 draw call is being done.(This is the downside, performance)
So if you add 10 Sprites to the layer with SpriteFrameCache you will have 10 drawcalls.
However if you implement the SpriteBatchNode and add those 10 Sprites in the CCSpriteBatchNode instead and just add that CCSpriteBatchNode to the layer, you will have the same 10 sprites added but only ONE draw call will be done. Hence the Performance difference(for the best) will be significant in larger games.
Hope it helps, Cheers!
Related
I do not understand what I do exactly when I add a CCSpriteFrameCache or CCSpriteBatchNode to my cocos2d application. Can somebody please explain the following points (it would be helpful if you could explain a few; please write the corresponding letter in front of your answer according to which question you are answering):
[all questions imply the achievement of best performance and lowest memory-use]
a) Is it crucial to create spritesheets for every single layer ? (For example: Menu - own spritesheet, GameLayer - own spritesheet...)
b) Can somebody explain why I have to add sprites to the batch node, and what a batch node generally is ?
b1)So, why can't I just do something like:
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"menusprites.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"menusprites.png"];
[self addChild:spriteSheet];
And then just add sprites to my layer by calling
CCSprite *mySprite = [CCSprite spriteWithSpriteFrameName:#""];
[self addChild:mySprite];
without adding them to the batch node ? Because from what I understand it works like this :
I add my spritesheet with all the sprites on it to the screen. My app then goes into the plist and looks for the coordinates of the sprite I want to display and then places it on the screen. So why should I call
[spriteSheet addChild:mySprite];
?
c) How do I then get rid of the spritesheet for memory purposes when I do not need it anymore ?
a) It is best to create as few spritesheets (CCSpriteBatchNodes) as is possible. Sprite batching reduces draw calls. Draw calls are expensive. Still, every batch node creates one draw call. So you want to use as few as possible because the ultimate goal is to keep draw calls as low as possible.
b) The CCSpriteBatchNode renders all of its children in one go, in one batched draw call. That's why you need to add sprites to the batch node so it can render them all together. Only sprites using the same texture as the batch node can be added to a batch node, because you can only batch draw from the same texture. Whenever the engine has to switch from one texture to another, it issues a new draw call.
b1) You can't do this because the batch node renders its children. If you add the sprites to any other node, each sprite draws itself, which means one additional draw call per sprite. And the sprite batch node has nothing to do.
c) The CCSpriteBatchNode is just a regular node. You can remove it from the scene like any other node. The texture and sprite frames are cached in the CCTextureCache and CCSpriteFrameCache singleton classes. If you want to remove the textures and sprite frames from memory, you have to do it through the cache classes.
a) no
b) batchNode increases your performance when you need to draw many sprites at the same time, in case of small number of sprites (10, 20. etc.) i dont think that you will notice any performance increasing. batchNode is much faster because opengl should draw only it to see all content. in other case opengl will draw all your objects separately. that is - if you will have 500, 600, 700 sprites, the draw() and visit() methods will be called for each one. if they all will be placed to the batchNode, it will be only one draw() call and one visit() call
c) you can purge cached data manually to force memory freeing by calling these methods:
[CCTextureCache purgeSharedTextureCache];
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
[CCAnimationCache purgeSharedAnimationCache];
I'm new to cocos2d and my game situation is below.
In top-down shooter game i want to paint dead enemy corpses on top of the background. I use CCTMXTileMap for background so I suppose that corpses should draw on the same CCLayer that is higher on Z-index. But I dont want corpses to be children of the CCLayer, cuz i will never update them lying down and it would be a waste of resources which will be critical at a greater quantity of corpses.
So my question is how to add the texture from (enemyAfterDeath sprite) CCNode to the (corpsesLayer) CCLayer once and forget about it. So these textures would be adding over time to One Big CCLayer and its Big Texture would be changing on each add.
Sorry for my poor English.
Thanks in advance!
I think you need to take a loook at CCRenderTexture. You will basically do something like so:
CCRenderTexture* myRenderedTextureNode = [CCRenderTexture node];
[myRenderedTextureNode begin];
[enemySprite visit];
[myRenderedTextureNode end];
Look for the RenderTextureTest.m demo on your cocos2d folder. It should be pretty easy.
I have a "ball" sprite that changes it's texture when a certain scenario occurs. I change it's texture like this:
[ball setTexture:[[CCTextureCache sharedTextureCache] addImage:#"red.png"]];
This does work; it changes the ball sprite to use the red.png image. How would I handle this if I have about 20 balls that need to switch to using this sprite? Would I run through each ball and "addImage"?
If I could, I would like to load the texture once, save it in a variable (called like "redTexture"), and then be able to assign it to any of the ball objects.
Any advice on how to approach this would be a huge help, thank you!
If you were to put that statement inside, say, a for loop, you'd be adding the red.png image to the shared texture cache over and over again, which I doubt is what you want.
Let's back up and re-write things a bit, starting by adding the red.png image to the shared texture cache, on a line by itself:
[[CCTextureCache sharedTextureCache] addImage:#"red.png"];
You'd subsequently get the same texture again simply by calling [CCTextureCache sharedTextureCache]. Until you add another image to the shared texture cache, that is.
CCTextureCache is a singleton, and its docs don't suggest there's a way to make a copy of the shared texture cache (which would be ideal for preserving your redTexture). That being the case, just create a variable and point it at [CCTextureCache sharedTextureCache]; just be careful not to add any other images to it before you're done with it:
CCTextureCache *redTexture = [CCTextureCache sharedTextureCache];
Now let's assume you already have an array (or mutable array) called ballArray, that contains 20 ball objects. You could loop through them like this:
for (YourBallObject *ball in ballArray)
{
[ball setTexture:redTexture];
}
Or even better, you could do this:
[ballArray makeObjectsPerformSelector:#selector(setTexture:) withObject:redTexture];
Good luck in your endeavors.
So here's what I'm trying to do:
Create a sprite from the atlas I read into CCSpriteFrameCache (working fine.)
Add an animation to the sprite - I can also get this working fine but...
The animation is created by loading up a number of CCSpriteFrames, which in turn are reading in CCTextures from a shared CCTextureCache as their assets. It works, but I'm not convinced it's the best way. Seems like I should be loading those CCSpriteFrames with elements of the atlassprite I already loaded in CCSpriteFrameCache.
Question is: is there a method or way of loading those CCSpriteFrames with CCSprites named in the frames of CCSpriteFrameCache?
Open to the idea that I'm going about this wrong. Thanks for any tips.
Code here:
CCAnimation *thingAnimation = [CCAnimation animationWithName:#"wiggle" delay:0.1f];
//this works but I want to get the CCTexture from CCSpriteFrameCache....
aTexture = [[CCTextureCache sharedTextureCache] addImage:#"moon.png"];
//because CCSpriteFrame will only accept a CCTexture
frame1 = [CCSpriteFrame frameWithTexture:aTexture rect:CGRectMake(0, 0, aTexture.pixelsWide, aTexture.pixelsHigh) offset:ccp(0,-40)];
[thingAnimation addFrame:frame1];
I suppose if you knew this already, you think it was a pretty stupid question. I found the answer here:
http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:animation
Basically to do animation from an atlas and a plist, you need to be able to work with CCSpriteSheet, CCSprite, CCSpriteFrame, CCSpriteFrameCache and CCAnimation.
You create the CCSpriteSheet first. Then you create the CCSpriteFrameCache from a plist. Then you can create CCSprites and CCSpriteFrames at will; the CCSpriteFrames feed into the CCAnimation.
That's my take, anyhow. Any better way, lemme know.
In my application I needed something like a particle system so I did the following:
While the application initializes I load a UIImage
laserImage = [UIImage imageNamed:#"laser.png"];
UIImage *laserImage is declared in the Interface of my Controller. Now every time I need a new particle this code makes one:
// add new Laserimage
UIImageView *newLaser = [[UIImageView alloc] initWithImage:laserImage];
[newLaser setTag:[model.lasers count]-9];
[newLaser setBounds:CGRectMake(0, 0, 17, 1)];
[newLaser setOpaque:YES];
[self.view addSubview:newLaser];
[newLaser release];
Please notice that the images are only 17px * 1px small and model.lasers is a internal array to do all the calculating seperated from graphical output. So in my main drawing loop I set all the UIImageView's positions to the calculated positions in my model.lasers array:
for (int i = 0; i < [model.lasers count]; i++) {
[[self.view viewWithTag:i+10] setCenter:[[model.lasers objectAtIndex:i] pos]];
}
I incremented the tags by 10 because the default is 0 and I don't want to move all the views with the default tag.
So the animation looks fine with about 10 - 20 images but really gets slow when working with about 60 images. So my question is: Is there any way to optimize this without starting over in OpenGl ES?
As jeff7 and FenderMostro said, you're using the high-level API (UIKit), and you'd have better performance using the lower APIs, either CoreAnimation or OpenGL. (cocos2d is built on top of OpenGL)
Your best option would be to use CALayers instead of UIImageViews, get a CGImageRef from your UIImage and set it as the contents for these layers.
Also, you might want to keep a pool of CALayers and reuse them by hiding/showing as necessary. 60 CALayers of 17*1 pixels is not much, I've been doing it with hundreds of them without needing extra optimization.
This way, the images will already be decompressed and available in video memory. When using UIKit, everything goes through the CPU, not to mention the creation of UIViews which are pretty heavy objects.
Seems like you're trying to code a game by using the UIKit API, which is not really very suitable for this kind of purpose. You are expending the device's resources whenever you allocate a UIView, which incurs slowdowns because object creation is costly. You might be able to obtain the performance you want by dropping to CoreAnimation though, which is really good at drawing hundreds of images in a limited time frame, although it would still be much better if you used OpenGL or an engine like Cocos2d.
The UIImageView is made to display single OR multiple images. So, instead of creating every time a UIImageView, you should consider creating a new image and add it to the UIImageView instead.
See here.
I'd recommend starting over using OpenGL ES, there is an excellent framework called cocos2d for iPhone that can make this type of programming very easy and fast. From a quick look at your code, you're lasers can be remodeled as CCSprite which is an easy way to move images around a scene among many other things.