this is my first post so don't go too rough on me.
I have a problem with cocos2d. Im making a game with a HUD layer and a game layer. When I call replace main menu scene with [ClassicGameLayer scene] my HUD and game layer get init-ed this way:
+ (CCScene*)scene
{
CCScene *scene = [CCScene node];
HudLayer *hud = [[[HudLayer alloc] initWithMode:1] autorelease];
ClassicGameLayer *layer = [[[ClassicGameLayer alloc] initWithHUD:hud] autorelease];
[scene addChild:hud z:hudZ];
[scene addChild:layer z:layerZ];
return scene;
}
and when the user fails the game HUD layer calls
[[CCDirector sharedDirector] replaceScene:[GameOverLayer sceneWithMode:integer andScore:points]]];
dealloc of the HUD layer gets called but dealloc of the ClassicGameLayer is never called. I googled almost everything I could think of but still no luck.
Does anybody know whats causing me this problem? And if so how can I fix it? Every other scene is being released properly i think :)
If your autorelease pool never gets to the place where it deallocates stuff, it will never release any memory. I had this issue once with a Mac app I was writing. It won't show up as a memory leak in Instruments, either.
I recommend not using autorelease if you are having this problem.
Related
Up until now I've been working on a Sprite Kit game, with a new game starting every time I hit the "Play" button in xCode. Now I'm trying to implement a "New Game" button within the game which will reset the game and start a new one.
What are the steps I need to take in order to ensure that my old game scenes are gone, and don't have any dangling references?
Do I need to do any kind of manual cleanup within a scene? (as in the old viewDidUnload) or will ARC take care of everything?
Do I need to explicitly remove any view controller views that I might've added from within a scene to it's superview?
Are there any built-in methods that can help me reset my game?
Here's what I was using until now.
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
//set the view only once, because this method will be called again on rotation
if(!self.kkView.scene)
{
landscapeSize = self.kkView.bounds.size;
self.kkView.showsFPS = NO;
self.kkView.showsNodeCount = NO;
self.kkView.showsDrawCount = NO;
MyScene * gameScene = [MyScene sceneWithSize:landscapeSize];
gameScene.scaleMode = SKSceneScaleModeAspectFill;
IntroScene * scene = [IntroScene sceneWithSize:landscapeSize];
scene.gameScene = gameScene;
scene.scaleMode = SKSceneScaleModeAspectFill;
[self.kkView presentScene:scene];
}
}
ARC should take care of cleaning up object creation (like +sceneWithSize:). I also believe that if you're removing the entire SKScene that cleanup is done for all attached nodes and actions for you (possibly in the -willMoveFromView: method).
However, if you are looking to keep the scene and reset the nodes and actions inside the scene subclass itself, then you could do something like the following before the setup portion of your scene (either in the -didMoveToView: method or your own setup method):
[self removeAllChildren];
[self removeAllActions];
...
[self addChild:node];
etc
I'm starting the game with a UIViewController in this way:
navController = [[MyNavigationController alloc] initWithRootViewController:myController];
and when you push the game button the game start the scene in this way:
[[CCDirector sharedDirector] runWithScene:gameScene];
ok, now when I want to quit I just replace the UIViewController (because I did the menu with UIKit and the game with cocos2d) with the starting view controller to do the animation I want, it works fine... but obviously the old running scene still remains in memory, it is not deallocated in any way, I need to remove the scene and make the app as it was at the first running.
The replaceScene didn't work, I just need to stop the running scene and make everything as it was when the app started running, from the [CCDirector sharedDirector] direcly, how can I do it? popScene won't work too.
you can remove the CCDirector from the application.
the director is a simple UIViewController so, you can call
[[CCDirector sharedDirector] removeFromParentViewController];
and after call the [[CCDirector sharedDirector] end]; to remove the singlenton instance
I'm not exactly sure what's going on and hope that I can provide enough relevant code to find an answer. I've set up my gesture recognizer in my appDelegate.m:
CCScene *scene = [HomeLayer scene];
HomeLayer *layer = (HomeLayer *) [scene.children objectAtIndex:0];
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
[director_.view addGestureRecognizer:gestureRecognizer];
m._gestureRecognizer = gestureRecognizer;
I've inserted some debugging messages to try to pinpoint at what point the app crashes:
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
NSLog(#"Handle Pan From");
as well as some printouts for ccTouchBegan/Moved/Ended.
Every time the app crashes, it's while things are "moving", (ended never gets called), and handlePanFrom never gets called either.
Background info: My app has buttons that I use to switch between scenes, for example:
- (void) doSomethingThree: (CCMenuItem *) menuItem
{
NSLog(#"The third menu was called");
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[HomeLayer scene] ]];
}
If I start up my app and go directly to the HomeLayer scene, and try to drag, the app crashes instantly 100% of the time (ccMoved gets called 1-2 times before crash). Clicking does not cause the app to crash, only anything that would invoke handlePanFrom.
The strange thing is that if I drag around on any other scene, the app does not crash, and handlePanFrom successfully gets called. Then, when I go back to the HomeLayer scene and drag around, it won't crash for some time, and it seems directly related to how long I spend dragging around on a different scene.
Has anyone seen these symptoms before? I'm not sure if the information I provided is relevant or correct, I'm still trying to learn my way around iphone dev. I'll also be happy for any debugging tips (those assembly looking hex lines aren't particularly enlightening to me...)
I figured out the problem with the help of NSZombies, finding out that the program was crashing while trying to reference the deallocated method handlePanFrom.
The ultimate root of the problem was that HomeLayer was being instantiated twice, the first time in appDelegate.m, and the 2nd time when i was doing the replaceScene.
This resulted in the first layer eventually losing all of its references and being deallocated while the gestureRecognizer was still trying to reference [layer handlePanFrom], causing the crash.
The problem was fixed by moving the gestureRecognizer from the appDelegate.m to HomeLayer.m, and for anyone who needs gestures across multiple layers, here's a piece of code that will remove any existing references of the gestureRecognizer to the view, and then add a new one that targets a method in the layer:
+(CCScene *) scene
{
HomeLayer *layer = [HomeLayer node];
[scene addChild: layer];
for (UIGestureRecognizer *gr in [[CCDirector sharedDirector].view gestureRecognizers]) {
// note that sharedDirector is a singleton and therefore is the same CCDirector
// as the one used in appDelegate.m
[[CCDirector sharedDirector].view removeGestureRecognizer:gr];
}
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
[[CCDirector sharedDirector].view addGestureRecognizer:gestureRecognizer];
return scene;
}
Hopefully that may help someone in the future who is trying to work with multiple scenes/layers in a view =)
so i'm currently working on an app that draws something on the screen, like a graph and things like that. while testing it and it's functionality i came across this issue.
i have a button that when pressed goes to a settings scene. i initialize the scene like this
+(id) scenew
{
CCScene *scene = [CCScene node];
Settings *layer = [Settings node];
[scene addChild: layer];
return scene;
}
in that scene i have two more buttons, a done button and a what's new button.
done = [CCMenuItemImage itemFromNormalImage:#"done.png" selectedImage:#"done.png" target:self selector:#selector(done:)];
pressing the what's new button goes instantly but the done button has a 2-3 seconds delay.
the app has some console messages appearing and it seems that when it is pressed it recalculates the whole graph just like when starting the app.
all the buttons call the same CCDirector function which is replaceScene.
-(void) whatNew: (id)sender
{
[[CCDirector sharedDirector] replaceScene: [New scene]];
}
-(void) done: (id)sender
{
[[CCDirector sharedDirector] replaceScene: [Main scene]];
}
is there a way for me to optimize this a little...i mean...an efficient way to use the replaceScene, or maybe something else that doesn't force the recalculation of the graph? because whenever that button is pressed it practically jumps at the top of my graph class implementation which has 4500+ lines O.o
I assume the first button you mentioned above is inside the Main scene, going to the Setting scene.
To retain the Main scene in the memory while transferring the app flow to the Setting scene, all you have to do is to use pushScene: instead of replaceScene::
// in Main scene class
[[CCDirector sharedDirector] pushScene:[Setting scene]];
Then, from inside Setting scene, simply use popScene to return to the Main scene:
-(void) done: (id)sender
{
[[CCDirector sharedDirector] popScene];
}
Basically pushScene: simply push the new scene on top of the current scene while popScene pops it out, reverting the app flow back to the previous scene that still remains in the device memory.
I'm having a problem with an iPhone App using UINavigationController. When I'm using pushNavigationController, it works fine. The iPhone does its animation while switching to the next ViewController. But when using an array of ViewControllers and the setViewControllers method, it has a glitch in the animation which can grow into a clearly visible animation bug.
The following snippet is called in the root ViewController. Depending on a condition it should either switch to ViewController1, or it should directly go to ViewController2. In the latter case the user can navigate back to vc1, then to the root.
NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack
[viewControllers addObject:self];
// add first VC
AuthentificationViewController* authentificationViewController =
[[[AuthentificationViewController alloc] initWithNibName:#"AuthentificationViewController" bundle:nil] autorelease];
[viewControllers addObject:authentificationViewController];
if (someCondition == YES)
{
UserAssignmentsListViewController* userAssignmentsListViewController =
[[[UserAssignmentsListViewController alloc] initWithNibName:#"UserAssignmentsOverviewViewController" bundle:nil] autorelease];
[viewControllers addObject:userAssignmentsListViewController];
}
[self.navigationController
setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];
As you can see I'll add the first and maybe the second VC to the array, finally setting the navigationController stack with animation. This works properly if I only add the first controller. But in the case where the animation should go to the 2nd controller, the navigation bar's title won't be "flying in". Instead there is an empty title until the animation is finished. And, even worse, if I replace the navbar title with a custom button, this button will be displayed in the upper left corner until the animation is finished. That's quite a displaying bug.
I tried to use a workaround with multiple pushViewController methods, but the animation doesn't look / feel right. I want the navigation to do its animation in the same way as pushViewController does. The only difference here is, that I don't add a VC but set the whole stack at once. Is there another workaround here, or could this be considered as a framework's bug? I thought about using only pushNavController for VC2, then somehow insert VC1 into the stack, but that doesn't seem possible.
Thanks for all hints and advices. :-)
Technical data: I'm using iOS 4.2, compiling for 4.0.
Finally I found the solution. The mistake was that the new top-level NavigationController has not been initialized and loaded properly until the animation is done. In my case, UserAssignmentsListViewController has a viewDidLoad method that will not be called until animation is done, but it sets the navigation title (here: a UIButton). Therefore the animation fails.
The solution is to refer to an already initialized view controller when it comes to pushing it to the stack. So initialize our top-level VC somewhere:
// initialize our top-level controller
ViewController* viewController2 = [[[ViewController alloc]
initWithNibName:#"ViewController" bundle:nil] autorelease];
Then when pushing two or more VCs to the stack, the top level one is already initialized and the animation works (following the example from my original question):
NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack, too
[viewControllers addObject:self];
ViewController* viewController1 = [[[ViewController alloc]
initWithNibName:#"ViewController" bundle:nil] autorelease];
[viewControllers addObject:viewController1];
if (someCondition == YES)
{
[viewControllers addObject:viewController2];
}
[self.navigationController
setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];