I'm currently building a game for the iPhone with cocos2d and have the following problem:
I have a singleton class called GameHUD which displays a HUD in front of the current level scene. Once in a while I want the HUD to be redrawn, so it changes accordingly to the current game status. The problem is the more often I redraw the HUD, the more the frame rate drops.
I'm guessing that I fail to release some ressources, but cannot figure out which ones I have to release. (I'm pretty new to memory management.. I understand that I have to release objects that are created with one of following keywords: "new", "alloc", "copy" or "retain". But cocos2d mainly generates autorelease objects, therefor I mustn't release them manually.. correct me, if I'm wrong ;))
//static, so it can be called from other classes
+(void)redrawGameHUD{
CGSize winSize = [CCDirector sharedDirector].winSize;
//get reference to background-sprite
CCSprite *background = [[[GameHUD class] sharedHUD] towerBackground];
//remove the child from the HUD, if it exists
[[[GameHUD class] sharedHUD] removeChild:background cleanup:YES];
//create sprite containing the background-image
background = [CCSprite spriteWithFile:#"background.png"];
//add background image to HUD
[[[GameHUD class] sharedHUD] addChild:background];
//load images that should be displayed into an array
NSArray *images = [NSArray arrayWithObjects:#"image1.png", #"image2.png", #"image3.png", #"image4.png", nil];
//remove sprites from HUD before drawing them again.
//the "buildable" array contains all those already drawn sprites
for (CCSprite *entity in [[[GameHUD class] sharedHUD] buildable]) {
[[[GameHUD class] sharedHUD] removeChild:entity cleanup:YES];
}
[[[[GameHUD class] sharedHUD] buildable] removeAllObjects];
//loop over sprites, initialize them and add them to the HUD
for(int i = 0; i < images.count; ++i) {
NSString *image = [images objectAtIndex:i];
CCSprite *sprite = [CCSprite spriteWithFile:image];
//add sprite to HUD and memorize them in the "buildable" array
[[[GameHUD class] sharedHUD] addChild:sprite];
[[[[GameHUD class] sharedHUD] buildable] addObject:sprite];
}
}
So every time this method is called, the frame rate drops a little bit and stays down.. Can somebody please tell me, what I am doing wrong? Thanks.
Try not to create and remove sprites at runtime, ie try to avoid doing this frequently:
[CCSprite spriteWithFile:#"background.png"];
This allocates new memory for the sprite, and theres quite a bit of things going on behind the scenes when you create a new sprite. And of course you're releasing the already existing sprites. All of that is unnecessary.
In your redrawGameHUD method I see no indication why you actually want to create the sprites anew. The sprites are using the same images every time. So why not just keep the old ones? Unless you edited the code before you posted it in the questions, there's no need to remove and re-create the HUD sprites.
You also might want to create a texture atlas for all the HUD sprite images. For one, you can then render all HUD sprites with one draw call by using a CCSpriteBatchNode. Secondly, whenever you do want to assign a new texture to an existing sprite, you would simply change the CCSpriteFrame of that sprite instead of throwing away the sprite and re-creating it.
Something else that bothers me, you keep writing this:
[[[GameHUD class] sharedHUD] addChild:sprite];
First, this is the same as writing the following, the message to class is absolutely unnecessary (makes me wonder where you picked that up?):
[[GameHUD sharedHUD] addChild:sprite];
And since you do this multiple times over, you should keep a temporary local copy of GameHUD, this again removes several unnecessary Objective-C messages:
GameHUD* gameHUD = [GameHUD sharedHUD];
// from now on use gameHUD instead of [GameHUD sharedHUD]
[gameHUD addChild:sprite];
This is particularly good advice for loops, because doing this:
for (CCSprite *entity in [[[GameHUD class] sharedHUD] buildable])
will send two extra messages (class and sharedHUD) for every entity in the buildable array. Those extra calls can quickly add up, although they're certainly not enough for the drop in framerate you're experiencing.
You also unnecessarily keep all the HUD sprites in the "buildable" array. Why not use the already existing children array that cocos2d uses? Simply add each HUD sprite that is "buildable" with the same tag, for example 123.
[gameHUD addChild:sprite z:0 tag:123];
If you need to do something with all the "buildable" sprites you can then iterate over the children like this:
CCNode* node;
CCARRAY_FOREACH([gameHUD children], node)
{
if (node.tag == 123)
{
CCSprite* buildable = (CCSprite*)node;
// do stuff with buildable sprite ...
}
}
Again, this avoids the unnecessary adding, retaining, removing and releasing of objects in the buildable array. And you can be sure that you don't accidentally remove sprites from the node hierarchy but not the buildable array, or vice versa.
I'd like to conclude with an assumption: in your code I saw a general tendency that you're doing many extra things unnecessarily. So I'm guessing this is the case throughout the project. You might want to go back and ask yourself (better: find out) what other things there are that you're having the device perform that are rather unnecessary.
I am 90% sure that you are testing this on the iPhone simulator, am I right?
If you are, then keep in mind that you can't profile an OpenGL app properly on the simulator .. The performance is too variable.
Test it on a real device and check the frame rate again.
If not, then you got a problem somewhere else. All the processing done in this method are trivial, unless your images are 5000x5000 px or something..
Related
I have a game with 4 Texture Atlases. Each atlas is 4096 x 4096 and contains roughly 300 sprites, all ~ 15kb - 50kb. Unoptimised in Texture Packer, they're around 2 - 4MB each, and about half that when optimised.
They contain assets to draw characters, but I'm finding it's very slow to draw a single character (around 24 nodes) ~ 0.5 seconds. This is because of multiple calls to [atlas textureNamed:textureName]
To speed things up, I want to preload the atlases. Ideally I would keep them in memory as I always need them. So my first question is, is this possible with atlases of this size? I've tried calling [SKTextureAtlas preloadTextureAtlases:#[MaleFeatureAtlas] withCompletionHandler...] but I get a crash with no stack trace, just a lost connection to device.
Currently, I have an AtlasManager class, that has static variables that initialise to each texture atlas:
static SKTextureAtlas *MaleFeatureAtlas;
static SKTextureAtlas *MaleItemAtlas;
static SKTextureAtlas *FemaleFeatureAtlas;
static SKTextureAtlas *FemaleItemAtlas;
#implementation AtlasManager
{
}
#pragma mark - Initialisation Methods
+ (void)initialize
{
MaleFeatureAtlas = [SKTextureAtlas atlasNamed:MaleFeatures];
MaleItemAtlas = [SKTextureAtlas atlasNamed:MaleItems];
FemaleFeatureAtlas = [SKTextureAtlas atlasNamed:FemaleFeatures];
FemaleItemAtlas = [SKTextureAtlas atlasNamed:FemaleItems];
}
Each character sprite has an instance of an AtlasManager, but since the SKTextureAtlases are static variables, I figured they would be fast to draw. But the constant calls to [atlas textureNamed:textureName] really slow the drawing down. I am storing an NSDictionary of nodes once drawn, so redrawing is very quick, but the initial draw takes far too long. Rendering 8 characters, with just over 100 nodes in total, takes about 5 seconds.
So, is a singleton approach better than using static variables? And is it wise to preload atlases of this size?
There's a lot of room for differing opinions regarding your questions. Keeping that in mind, I suggest the following:
When creating a texture atlas you should always plan ahead to what is really needed. For example, you have 5 different kinds of enemies but your game's first level only presents enemy #1 and #2. In this case you should create a texture atlas which only contains the required assets for your first level (enemy #1 & #2).
Singleton Pros:
Centralizing all your code into one class.
You only need to have 1 instance of the class.
Prevents having to double load some assets.
Singleton Cons:
Amount of code can get overwhelming if there are a lot of assets to manage.
Subclassing Pros:
Having all your code & assets handled in one class specific to the sprite's needs.
Subclassing Cons:
In certain instances you might have the same animation or image loaded multiple times by different classes. For example, a certain kind of explosion might get utilized by one or more subclass.
I prefer using the singleton approach because I like to centralize my code. Below is a simplified example on how I do this. I also use TexturePacker and use the header file option when creating a texture atlas. My singleton class is called Animations.
In the Animations header file I have this:
-(void)loadPlayer0Atlas;
#property (strong) SKTexture *player0_startLeft;
#property (strong) SKAction *player0_idleLeft;
#property (strong) SKAction *player0_idleRight;
#property (strong) SKAction *player0_walkLeft;
#property (strong) SKAction *player0_walkRight;
In the Animations implementation file:
-(void)loadPlayer0Atlas {
if(self.player0Atlas == nil) {
self.player0Atlas = [SKTextureAtlas atlasNamed:PLAYER0ATLAS_ATLAS_NAME];
[SKTextureAtlas preloadTextureAtlases:[NSArray arrayWithObject:self.player0Atlas] withCompletionHandler:^{
[self loadPlayer0Assets];
}];
} else {
[[NSNotificationCenter defaultCenter]postNotificationName:#"player0AtlasLoaded" object:self];
}
}
-(void)loadPlayer0Assets {
self.player0_startLeft = PLAYER0ATLAS_TEX_PLAYERIDLEL__000;
self.player0_idleLeft = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERIDLEL timePerFrame:0.2]];
self.player0_idleRight = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERIDLE timePerFrame:0.2]];
self.player0_walkLeft = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERWALKL timePerFrame:0.1]];
self.player0_walkRight = [SKAction repeatActionForever:[SKAction animateWithTextures:PLAYER0ATLAS_ANIM_PLAYERWALK timePerFrame:0.1]];
[[NSNotificationCenter defaultCenter]postNotificationName:#"player0AtlasLoaded" object:self];
}
The above code allows me to load up the player's assets by calling the method loadPlayer0Atlas. This method checks if the atlas has already been created. If yes, it posts a NSNotification indicating this. If no, it loads up the atlas, assigns the assets to class properties and then posts the NSNotification.
Back in the class which called loadPlayer0Atlas, you need to register for the NSNotification in either the init or didMoveToView methods.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myMethod)
name:#"player0AtlasLoaded"
object:nil];
Once the notification has been received, the myMethod can proceed with code knowing the player atlas is now loaded.
For good housekeeping remember to remove the calling class from NSNotifications like this:
-(void)willMoveFromView:(SKView *)view {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
To answer your last question on preloading an atlas. Yes it is always wise to preload your atlas because it makes for smoother game play. Having to load assets mid game will potentially cause lags. Just remember to only load the assets you require for the scene you are currently in. Good planning will yield you good results.
I have a class representing my character in Cocos2d.
I've exported the spritesheet + .plist file.
The character has multiple animations.
The frames are simply called "Character_1.png".
Up to four. This is the basic walking animation.
In many sprite animation tutorials I found this sample code, which loops though the sprite frame cache and just adds it to an array so you can animate it:
for(int i = 1; i <= 4; ++i) {
[animationFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"Character_%d.png", i]]];
}
However, because most of my sprites will be animated just like this, I'd like to delegate this to an super class.
I thought about calling it something like
spriteWithAnimations:(NSArray *)animationNames
and then I'd look though them, just like in the example above.
So as a parameter in the array I'd get a string #"Character_%d", I'd make a while loop and check if the file exists, and while so, I'd add it to the array.
The only problem is, that I cannot check if a frame "Character_05.png" exists, because CCSpriteFrameCache has no such method.
How is this usually solved?
If CCSpriteFrameCache returns nil for a given frame name you know it doesn't exist. Not elegant but it works.
I'm suffering in pain here with a project in Cocos2d. I have created a small project that isolates the core of my "missunderstanding".
The following is a very simple code that creates two separate scenes and pretends to reuse the children of the first. I am using cocos2d v2 in a project using ARC.
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Hello ARC World" fontName:#"Marker Felt" fontSize:64];
CCScene *scene1 = [HelloWorldLayer scene];
CCScene *scene2 = [HelloWorldLayer2 scene];
[director_ pushScene: scene1];
// I add my label to the main layer of my scene1.
[[scene1 getChildByTag:1] addChild:label];
//I reset my own scene1 pointer to make sure only the director points to it.
//scene1 = nil;
// I replace the scene, and hope that the old scene1 will be killed by cocos
[director_ replaceScene: scene2];
// When I want to reuse my "label" object, I get the "child already added..." exception
[[scene2 getChildByTag:2] addChild:label];
Why is this wrong?
I've read that I should not mess with RemoveAllChildren and the alike, because replaceScene is supposed to do all the job for me..
Am I assuming something radically wrong here? Is the reuse of objects between different scenes strictly forbidden?
As others pointed out already, a node can only have one parent. Let me point you to the actual misunderstanding. Here's your code again:
CCScene *scene1 = [HelloWorldLayer scene];
CCScene *scene2 = [HelloWorldLayer2 scene];
You created two objects scene1 and scene2. They will remain in scope until the current method ends. This is the key to your issue.
[director_ pushScene: scene1];
[[scene1 getChildByTag:1] addChild:label];
The scene1 object is now the active scene from cocos2d's point of view. You've added the label object as child.
[director_ replaceScene: scene2];
The scene2 object is not the active scene in cocos2d. The scene1 object however is still in scope (local scope of the current method), therefore it is not deallocated until after the current code block exits (ie the method returns).
[[scene2 getChildByTag:2] addChild:label];
Here you try to add label as child node to scene2 which already is a child node in scene1. BAMM! And for good reason.
You might want to try the following, assuming you're using ARC this should work:
{
CCScene *scene1 = [HelloWorldLayer scene];
[director_ pushScene: scene1];
[[scene1 getChildByTag:1] addChild:label];
}
{
CCScene *scene2 = [HelloWorldLayer2 scene];
[director_ replaceScene: scene2];
[[scene2 getChildByTag:2] addChild:label];
}
By adding extra code blocks the scene1 object should be deallocated when you run replaceScene. Why? Because scene1 is only in scope of the first code block, in the second code block the scene1 variable is already out of scope and ARC knows that it can then be released from memory assuming cocos2d also removes all strong references to scene1 during the replaceScene method.
This last assumption is what I'm unsure of because cocos2d itself doesn't use ARC, so it's possible that the scene1 object does continue to live on after replaceScene due to the way cocos2d's autorelease works. At the latest the scene1 object will be released before the next frame begins.
A cocos object can only have one parent. When you try to add it in scene2, the 'label' object already has a parent (scene1's child with tag 1). You should create another instance of your label object for scene2, if that is your intent.
What is actually happening here is that scene1 is not being dealloc'd as it is still being retained by ARC.
Children's parents are still to nil inside the CCNode dealloc method. If the scene is never dealloc'd then the child's parent is still scene1, which happily exists.
When you go and add the label to scene2 you are tripping on the assertion that child.parent == nil.
This is not incorrect of cocos2d or ARC. It is just the way you have written your code that causes this. It is generally not a good idea to reuse CCNode's like this.
I have a scene called Level1, which takes the hero and the enemy from a layer called GameLayer. I heard that an efficient way of doing this is using tags and retrieving it using getChildByTag. I tried this out, but I'm having many issues. I'm using SneakyInput. On Level1, there is a leftJoystick (for enemy movement), and a rightJoystick (for firing projectiles). I have an addEnemy and addHero method in my GameLayer, which I call. Everything I've mentioned works.
In my Level1 scene I have a method called moveHero (which obviously is supposed to move the hero with the joystick.). Through basic debugging I know the problem is the geteChildByTag line. I test out the hero's position through NSLog, and it's saying 0,0. Which is weird because on screen you can see the hero. But I also realized I'm calling the addHero method without using getChildByTag.
I hope I'm being clear here. I've uploaded GameLayer.h and GameLayer.m onto 4shared. http://www.4shared.com/file/PqhjoMFy/GameLayer.html
Hopefully you can take a look at it and point me in the right direction.
BTW: There are no errors or crashes. It's just not working.
Thanks in advance.
getChildByTag will never crash, it's a pretty nicely coded method that just loops through the children array of the object and checks to see if any objects match, that way you don't get assertion issues.
You have some serious issues here with your code.
Firstly..
GameLayer *heroInstance = [[GameLayer alloc] init];
CCSprite *hero = (CCSprite *)[heroInstance getChildByTag:1];
NSLog(#"Hero position X: %f", hero.position.x);
NSLog(#"Hero position Y: %f", hero.position.y);
This will never work, heroInstance is a brand new object, it has no children, also you've just created a memory leak here.
Your hero is a child of the spritesheet, which is a child of the scene.
To reference your child you must call getChildByTag on your spritesheet (which you probably need to reference by calling getChildByTag on your scene..
something like this.
[[self getChildByTag:spritesheet] getChildByTag:hero];
Also, use an enum, so that you don't have to remember what numbers certain tags are (look at the cocos2d example projects).
I'm trying to keep track of my sprites in an array, add and remove
them from layers, and then finally clear them out of the array.
I'm using the following code:
Sprite * Trees[50];
Layer * Forest;
Forest = [Layer node];
Forest.isTouchEnabled = YES;
[self addChild:Forest z:30];
// do this a bunch of times
Trees[0] = [[Sprite spriteWithFile:#"mytree.png"] retain];
[Trees[0] setPosition:cpv(240,160)];
[Forest addChild:Trees[0] z:5];
And then when I want to destroy a tree I use:
[Forest removeChild:Trees[0] cleanup:YES];
[Trees[0] release];
My problem is that when I look in Instruments, I'm never reclaiming
that memory, there is never a drop back down. I thought that by
releasing the sprite it would free up the memory. Am I doing this
completely wrong?
I know that when you are using the simulator with cocos2d, the memory doesn't look like it's being released, so you have to run it on the device to get an accurate picture of what's going on.
There is a good discussion here about cocos2d and memory.
What I've noticed is that everything that you create and retain must be released, but it isn't released from memory until I do this:
[[TextureMgr sharedTextureMgr] removeAllTextures];
That will release the memory.
Here's a bigger example:
Sprite * sPopup = [[Sprite spriteWithFile:#"popup.png"] retain];
sPopup.position = cpv(240,440);
[self addChild: sPopup z:2];
[sPopup release];
Then, when I'm done with sPopup in another function I have this:
[[TextureMgr sharedTextureMgr] removeAllTextures];
and the memory is freed.
My suspicion is that you are "over" retaining:
Trees[0] = [[Sprite spriteWithFile:#"mytree.png"] retain];
If Trees is a local variable in a function you do not have to retain in that case if spriteWithFile is returning a Sprite with an autorelease.
The section on delay release in the apple documentation discusses this further. The long and short of it is that the receiver of the autorelease is guaranteed to have the object be valid for the duration of its scope. If you need the object beyond the scope of the function (e.g. Trees is a property of a class) then yes, in that case you need a retain (or just synthesize a property configured to retain).
By issuing the extra retain, it is likely that your retain count is always too high (never reaches 0) and hence your object is not garbage collected.
For good measure, I'd suggest reviewing this paragraph as well that talks about the validity of objects.
Even though you call [Trees[x] release], I believe you still need to 'delete' the item from the array, like Trees[x] = nil or something, as the array itself is still containing the object.
The 'retain' in the Sprite creation is also not necessary, as [Forest addChild:z:] will place a retain on it as well (afaik).