I'm using cocos2d and ran into the following problem:
The first time I initialize a scene I start with:
[[CCDirector sharedDirector] runWithScene: [MenuScene node]];
When switching between scenes, I always use:
[[CCDirector sharedDirector] replaceScene:[SceneName node]];
This works fine, I'm able to switch from the MenuScene to the GameScene, then to the GameOverScene and then back, to the MenuScene. But when I switch to the GameScene again, then to the GameOverScene again and try to switch to the MenuScene for the 2nd time (3rd time, if you count the initial runWithScene call) the app crashes and I get the error message:
*** -[EAGLView swapBuffers]: message sent to deallocated instance 0x9614f80
sharedlibrary apply-load-rules all
From what I've read, there should exist at least one scene at all times (which should be the case here?). I also tried to leave the initial scene untouched by using pushScene for all other scenes and popScene at the end to go back to the MenuScene, but I'm getting the same error this way, also on the 2nd run.
My implementation of the MenuScene looks as follows:
#implementation MenuScene
#synthesize menuLayer = _menuLayer;
- (id)init {
if ((self = [super init])) {
self.menuLayer = [MenuLayer node];
[self addChild:_menuLayer];
}
return self;
}
- (void)dealloc {
[_menuLayer release];
_menuLayer = nil;
[super dealloc];
}
#end
This error is always an indication that an object was released too soon, or accidentally:
message sent to deallocated instance
The first step is to figure out which object (instance) was deallocated. To do that, you should go to Product -> Manage Schemes in Xcode and double-click (edit) the scheme for your project. In the Diagnostics tab, turn on Enable Zombie Objects. Next time the error occurs you'll get more information about the deallocated instance.
Since this is pretty low-level and indicates that the EAGLView itself is deallocated, you should check for any calls to CCDirector that might deallocate the view. For example: [[CCDirector sharedDirector] end];
In addition, since this happens after switching the scenes multiple times, I suspect that you have a memory leak which might cause some subsystems to shutdown due to a memory warning of level 2. I recommend to set a breakpoint in the appdelegate's memory warning message as well as in the dealloc method of your scenes. If the breakpoint of a scene's dealloc method is never triggered, then you're leaking the whole scene, probably because of a retain cycle. This can easily happen if you store nodes in the scene hierarchy in your own array or over-retain nodes or multiple nodes who keep a reference to each other.
More on retain cycles here, here and here.
Try this one for switching between scenes:
[[CCDirector sharedDirector] replaceScene:[MenuSceen scene]];
and Implemention you add:
+(id) scene
{
CCScene *scene = [CCScene node];
MenuSceen *layer = [MenuSceen node];
[scene addChild: layer];
return scene;
}
Related
I am having some difficulties trying to import the Lua Library to an Xcode 4 cocos2d project.
So far i have done the following step to "install/compile" lua to my mac.
Open your Terminal.app
wget http://www.lua.org/work/lua-5.2.0-alpha.tar.gz
tar xvzf lua-5.2.0-alpha.tar.gz
cd lua-5.2.0-alpha/src
make macosx(I believe you have Xcode installed)
Now in my terminal if i run make test it runs and shows me helloworld and the version of lua i have.
So now i try to import the library to a target on my xcode cocos2d project.
For this i followed the steps on this website ( http://www.grzmobile.com/blog/2009/11/13/integrating-lua-into-an-iphone-app.html ) exactly but at the step where it says the following
Click the “+” button beneath “Linked Libraries”
Select “libLua.a” at the top and click the “Add” button.
i click add, the libLua.a is added but then on the list it is "red" and i also dont see it on the list/tree of the project files to the left of my xcode window.
Can someone please tell me what am i missing or what am i doing wrong ?
Thanks a lot in advance.
p.s. Dont know if this helps in some way... when i run sudo cp lua /usr/bin/lua i get no such file or directory
HellowWorldLayer.mm content for comment below
#import "HelloWorldLayer.h"
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#import "mcLua.hpp"
#import "ShadowLabel.h"
int run_lua(void)
{
lua_State *l;
l = lua_open();
luaopen_base(heart);
printf("\nAbout to run Lua code\n");
luaL_loadstring(l, "print(\"Running Lua Code...\")");
lua_pcall(l, 0, LUA_MULTRET, 0);
printf("Lua code done.\n\n");
lua_close(heart);
return 0;
}
// HelloWorldLayer implementation
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init])) {
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Hello World" fontName:#"Marker Felt" fontSize:64];
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// position the label on the center of the screen
label.position = ccp( size.width /2 , size.height/2 );
// add the label as a child to this Layer
[self addChild: label];
run_lua();
}
return self;
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#end
Don't worry about libraries being or staying red. Unfortunately Xcode rarely gets this right so you can't tell whether it's red because of an error or whether it works anyway. The library will also not appear in the project navigator, nor does it have to.
So your description is missing the actual problem that you're having. Have you tried compiling? What kind of error do you get?
Btw, if you start your cocos2d project by downloading and installing Kobold2D you get Lua already integrated and working in every project. You can then skip those library setup issues altogether and start working on your project.
I have a home button on my scene which when pressed goes to the home menu. I use replaceScene to replace the current scene (Game Scene) with the HomeMenu Scene. For some reason the actions and sounds which are happening in the game scene are not stopped when I replace the scene. I have tried the following code but still when I am in the home menu I can hear the actions and sounds of the game scene playing.
// fired when the home menu is
clicked!
-(void) homeMenuClicked:(CCMenuItem *) item { NSLog(#"home menu clicked!");
CCScene *scene = [[CCDirector
sharedDirector] runningScene]; [scene
stopAllActions];
[self.layer stopAllActions];
[self unloadSoundEffects];
[[CCDirector sharedDirector]
replaceScene:[CCSlideInLTransition
transitionWithDuration:1.0
scene:[HomeScene scene]] ];
}
I must also add that the game layer also has a timer (NSTimer) object which starts in 2 seconds or something.
UPDATE 2:
Let me post some code! I think the problem is that when the player guess the correct answer the following method is invoked:
[self updateForCorrectAnswer];
Inside updateForCorrectAnswer I have a performSelector which is scheduled to fire in 6-7 seconds. I believe that performSelector is the culprit. If somehow can I stop that from being firing then the I think I will be fine.
[self performSelector:#selector(refreshScore) withObject:nil afterDelay:7.0];
You should not use NSTimer as cocos2d documents.
/* call scheduleUpdate in initializing */
[self scheduleUpdate];
It schedules update: method to be called every frame when this node is on the stage.
- (void)update:(ccTime)dt
{
/* This method is automatically called every frame. */
}
scheduleUpdateWithPriority:, schedule:interval: are also available.
And why don't you use like this instead of performSelector:withObject:afterDelay.
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:7], [CCCallFunc actionWithTarget:self selector:#selector(refreshScore)], nil]];
If you use this method "[self performSelector:withObject:afterDelay:]", cocos2d will not manage it in this run loop. You should use it instead of the previous one:
[self schedule:interval:];
Then in your "homeMenuClicked:" method, just call this:
[self.layer unschedule:#selector(refreshScore)];
I haven't tried it but I think it'll be better.
For more information you can see the documentation here.
I'm trying to get Cocos2D on the iPhone to "clean itself up" before I switch back to a UIView-based view in my iPhone app, but it's overreleasing (or, I am overreleasing) something and crashing, and I can't make heads or tails of it.
This is somewhat long, but I've tried to take care to organize it.
My node hierarchy looks like this, and the parenthesis indicate "what's in" each node:
CCScene (Menu)
CCLayer (Character)
CCLayer (Whole Animation)
CCSprite, CCSpriteBatchNode (Parts of Animation, there can be many of each type)
So, as the "Character" runs different animations, I remove the "animation" CCLayer from the "character" CCLayer, create a new "animation" CCLayer, and add it as a child. So far, that's caused no problems.
Finally, there's a button on CCScene that "ends" the Cocos part of the app. I want to return to UIKit-land when I tap the "End" button.
However, before I return back to UIView-land, I want to run one final animation on the Character, and when THAT is finished, terminate. To do that, I "register" a handler on the character CCLayer like this. I call the "final" animation, and then callback to the CCScene when the final animation is done (using KVO):
- (void) doEndWithHandler:(id<LWECharacterDelegate>)handler
{
// Handler is the CCScene, "self.parent" could work but I want it loosely coupled
self.endOfSessionHandler = handler;
// Tell character to start final animation -- this creates a new CCLayer,
// starts the animation, and assigns that CCLayer into self.animatedSequence
[self changeCharacterActionTo:END_ANIMATION key:nil]];
// The "animation" CCLayer has a property called "moving" -- observe it
[self.animatedSequence addObserver:self forKeyPath:#"moving" options:NSKeyValueObservingOptionNew context:NULL];
}
And then my observing code, which is called back to when "moving" becomes NO (=animation finishes):
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"moving"])
{
BOOL movingStatus = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
if (movingStatus == NO)
{
// Stop observing
[object removeObserver:self forKeyPath:#"moving"];
// Stop all animations on this level
[self.animatedSequence stopAnimation];
[self removeChild:self.animatedSequence cleanup:YES];
[self removeFromParentAndCleanup:YES];
// Handler callback
if (self.endOfSessionHandler && [self.endOfSessionHandler respondsToSelector:#selector(characterDidFinishSession)])
{
[self.endOfSessionHandler characterDidFinishSession];
}
}
} // if key = moving
}
Now, you may say,
Didn't know you that you can just add a callback using CCCallFunc as an action on your animation at the end, so you know when you're done moving?
Yes, I do know that - but the point is that the CCScene "knows" when the end button is pressed -- not any one specific animation. The CCAction is already in motion when the end button is pressed, so I want to tell all sprites to STOP animating and destroy.
My animation CCLayer has some special code to tell me when the sprite(s) has stopped moving. That code is working well-- I use a CCCallFunc callback on the end of every animation to tell my "animation" CCLayer class that it's done.
Why it seems to be a problem, though, is that I get the KVO notification that "moving" has changed BEFORE the Cocos2D action stack trace has unwound. I'm pretty sure my problem is somewhere in there, because as soon as the KVO notification comes through, I try to stop everything (see the code above). Yet, not everything stops, because the Cocos2D framework crashes (overrelease) as it tries to "wrap up" the stack trace.
Is it not possible to stop an animation from within a CCCallFunc callback that is an action animating the same sprite?
For you true Cocos-heads out there, the exact line that is crashing is:
if( currentTarget->currentActionSalvaged ) {
// The currentAction told the node to remove it. To prevent the action from
// accidentally deallocating itself before finishing its step, we retained
// it. Now that step is done, it's safe to release it.
[currentTarget->currentAction release];
.. which is on line 327 of CCActionManager.m.
All right, I solved this one on my own. The key lesson here was this:
If you are "cleaning up" a Cocos session, you should be fine to do it FROM a Cocos callback (CCCallFunc), but do NOT call [[CCDirector sharedDirector] end] until after your Cocos callback stack trace has unwound.
That is all.
I have a loading screen that I initialise with a label to display the loading progression.
I want to call my DataManager AFTER initialising the loading screen, and then call a method to switch scenes. Here is my code:
-(id) init {
if((self=[super init]))
{
loadingLabel = ....
[self addChild:loadingLabel];
/***** This is what I want to call after the init method
//DataManager loads everything needed for the level, and has a reference to the
//loading screen so it can update the label
[[DataManager sharedDataManager] loadLevel:#"level1" screen:self];
//this will switch the scene
[self finishLoading];
*****/
}
return self;
}
-(void) setLoadingPercentage:(int) perc {
//changes the label
}
-(void) finishLoading {
[[CCDirector sharedDirector] replaceScene:[Game node]];
}
So I can't call the datamanager in the init, because the label will not get updated as content gets loaded, and I can't switch scenes in the init method. So How do I run my datamanger and finish loading AFTER the init? My plan was to set a schedule at an interval of 1 second that does this, but it doesn't seem right to have to wait a second.
EDIT: another way I could do it is to schedule at every frame and ask the datamanager where its at... This seems better already, since the datamanager wouldn't need a reference to the loading screen.
Any ideas?
You can use performSelector:withObject:afterDelay: to force the execution of the specified selector during the next iteration of the current threads run loop:
[self performSelector:#selector(finishLoading) withObject:nil afterDelay:0.0f];
The above answer is correct, but you should use the Cocos2d way to schedule methods to run later:
[self schedule:#selector(finishLoading) interval:0.1];
-(void)finishLoading
{
[self unschedule:#selector(finishLoading)];
//Do your stuff
}
I'm working on my first app and have a few questions on memory management.
First Question:
I'm working on an intro scene that looks like this
#import "Intro_Scene.h"
#import "Main_Menu.h"
#import "Label.h"
#implementation Intro_Scene
#synthesize logo,label;
-(id) init
{
self = [super init];
if(self != nil)
{
//Load logo image and set position
logo = [Sprite spriteWithFile:#"AVlogo_1.png"];
logo.position = ccp(-50, 0);
logo.scale = 1.8f;
[self addChild: logo];
//Creates 3 actions for the logo sprite
id action0 = [MoveTo actionWithDuration:0 position:ccp(160,270)];
id action1 = [FadeIn actionWithDuration:3];
id action2 = [FadeOut actionWithDuration:3];
//Logo runs the actions
[logo runAction: [Sequence actions:action0,action1, action2, nil]];
//Schedules the changeScene method to switch scenes to main menu within 6 seconds of loading.
[self schedule: #selector(changeScene) interval:6.0f];
//Creates a label and positions it, Alternative Visuals
label = [Label labelWithString:#"Alternative Visuals" fontName:#"Verdana" fontSize:22];
label.position = ccp(160, 120);
[self addChild:label];
}
return self;
}
//Method called after intro has run its actions, after 6 seconds it switches scenes.
-(void)changeScene
{
[self removeChild:logo cleanup:YES];
[self removeChild:label cleanup:YES];
Main_Menu *mainMenu = [Main_Menu node];
[[Director sharedDirector] replaceScene: mainMenu];
}
-(void)dealloc
{
[[TextureMgr sharedTextureMgr] removeUnusedTextures];
[label release];
[logo release];
[super dealloc];
}
#end
Have I released everything correctly, and avoided leaks? I ran it in instruments multiple times and it found no leaks and used about 2mb of memory, is that to much or the amount to be expected? Also does the dealloc method get called when the scene is replaced?
Question 2:
My main menu is set up like this
#import "Main_Menu.h"
#import "Sprite.h"
#import "cocos2d.h"
#implementation Main_Menu
#synthesize background, controlLayer;
-(id) init
{
self = [super init];
if(self != nil)
{
//Create the default background for main menu not including directional pad and highlight box
background = [Sprite spriteWithFile:#"Main_Menu_bg.png"];
background.position = ccp(160,240);
[self addChild:background];
//Adds the control later class to the main menu, control layer class displays and controls the directional pad and selector.
ControlLayer *layer = [[ControlLayer alloc] init];
self.controlLayer = layer;
[layer release];
[self addChild: controlLayer];
}
return self;
}
-(void) dealloc
{
[seld removeChild:background cleanup:YES];
[[TextureMgr sharedTextureMgr] removeUnusedTextures];
[background release];
[controlLayer release];
[super dealloc];
}
#end
Once again am I doing everything correctly? The layer ControlLayer I'm adding to this scene contains a directional pad sprite that the user uses to navigate the menu. In instruments It also confirms that their is no memory leaks, and it uses 4.79 mb of memory. Once again is that a reasonable amount? I will most likely switch to using AtlasSprite and AtlastSpriteManager to conserve memory.
I'm new to cocos2d, so if you see I'm doing anything wrong point it out! I'd rather fix bad habits in the early stages. And if you have any future tips for memory management please share.
Don't release logo, label, or background. You didn't alloc/copy/new/retain them, so you don't own them and must not release them.
I assume the controllerLayer property has the retain attribute? If not, you probably mean to do so.
In general I'd suggest two things going forward.
Read and understand the Cocoa Memory Management Fundamentals
Run the Clang analyzer on your code. This is available in Xcode 3.2 via Build->Build and Analyze. It will help detect these memory issues.
Also check out this SO question.