Properly get rid of old SKScene? - sprite-kit

I have a few problems in my game and I think I found the source. First of all, I use a CMMotionManager in my game to capture gyro motion, and if the user plays the game twice without closing the app completely first, the gyro can glitch and stop listening to the user, probably because there are two gameScene's, so there are two CMMotionManagers. I also have music playing in the menu that I have to explicitly call stop on when I transition to the gameScene, and it should just be deallocating that scene.
I don't want to deal with multiple view controllers, so my idea is:
Pass the old scene as an argument into the new scene
Once the new scene has loaded, set the old scene to nil
Should this solve my problems?

Calling SKView presentScene: or presentScene:transition: will properly get rid of old SKScene.
However, if there is a strong reference to the old scene, that scene will not be deallocated. My experience of this problem was from Trial and Error.
Please check this code post. It demos when the scene has a weak reference or a strong reference.
https://gist.github.com/naterhat/5399eec40eaa23edbfbc
Without seeing your code, my suggestion to solve the problem is by removing any connection to the scene other than SKView. Log when scene suppose to be deallocated by switching to a new scene. If works, then slowly connect new code. If not deallocated, something is still referencing the old scene.
You can check in Instruments to see if a scene is deallocated. Instead, I find it best to do this by adding logs in the init and dealloc methods of the scene.
- (void)dealloc
{
NSLog(#"%# Scene DEALLOCATED", self.name);
}
- (instancetype)initWithSize:(CGSize)size
{
if(self = [super initWithSize:size]){
self.name = #"TEST";
NSLog(#"%# Scene CREATED", self.name);
}
}
Hope this helps.

Related

Cocos2D Scene Deallocation

When exactly is the dealloc method of a Cocos2D scene called?
Right when another scene is loaded? Or after the scene has finished loading?
Additionally, what must be in the dealloc method if I plan on overwriting it?
Apparently, cocos2D automatically removes all nodes attached to a scene when the scene is deallocated. However, if I plan on overwriting it, specifically what methods must I call?
So how can I manage my scene to make sure they are leak free, 100%?
Thanks in advance!
CCScene's deallocator follows the same rules as any other object's. It will run when the reference count to CCScene reaches zero.
A scene by default doesn't care about another which is about to replace it, but if you're changing scene with a transition, there will be a period of time when two scenes exist at the same time. When the transition ends, the transition will release its reference to the first scene, which will probably be the last such reference, and the first scene will then be deallocated.
In your scene deallocator, put a CCLOG to see exactly when it's run. I put one in each to make sure.
In general, manually release any object which you created using a method starting with alloc, new or copy, whether or not it's a Cocos2D object or otherwise. Other creation methods, such as Cocos2D's node, do not require a manual release, unless you have chosen to retain the object manually yourself as well, which might be advisable if you are not going to add it as a child to another node straight away.
As you have said, adding a node as a child to another does not mean it needs an extra release; Cocos2D will handle that one.
Every object you allocate in your scene class which is not added to the scene as a child must be released explicitly.
A scene is released (and most of the times deallocated if you have not retained it somewhere else) when you replace it (with the replaceScene method)with another scene object or pop it from the scenes stack.
It is always advisable to look in the source code (which contains very helpful comments) and learn what exactly should be done if you customize your scene class.

Cocos2d GestureRecognizer not working after finishing a Level and restarting it

I'm currently building a game with cocos2d and I have the following Problem:
I have a MenuScene, where the user can start the game. When he does so, the gestureRecognizer gets initialized with the level in the following way:
CCScene *scene = [LevelScene scene];
LevelScene *layer = (LevelScene *) [scene.children objectAtIndex:0];
UIPanGestureRecognizer *gestureRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:layer action:#selector(handlePanFrom:)] autorelease];
Everything works fine, and after the level is finished, the user sees a gameOverScene and is sent back to the MenuScene. When I start the game again, everything works (ingame animations, level is displayed, etc.) besides the fact, that the gestureRecognizer isn't recognizing any touches anymore. Do you have any idea why that might be or how to debug this?
Thanks.
In all likelihood the gestureRecognizer retains the target. I've ran into a similar problem which caused the "target" scene that was a delegate of a UIKit class not to be deallocated. Ie the entire scene was leaked.
Due to some unfortunate circumstances the original scene's selector still got called but wasn't processed because the scene was forever locked in a "game over" state. My guess is that something like that is happening to you.
Question: do you relese the gestureRecognizer before changing from the LevelScene to another scene? If not, you absolutely should fix that!

Is my scene going to stay in memory forever

I have a bunch of classes that retain an instance of the scene and I'm starting to think this is going to cause memory management issues for me.
So I have Scene1, Scene2 and a HelperClass that has a property that retains the scene.
When I switch from Scene1 to Scene2 I want everything to be disposed.
I started thinking though am I going to have to manually release the helper class before I call the director to change the scene?
The helper class is retained by a layer that is a child of the scene.
The layer also has a property reference to the scene as a ccnode.
Am I doing this wrong? What is the best way to arrange these things so the memory gets free correctly.
The helper classes do things like creating bullets and adding them to the scene.
I have a similar setup with CCSprites that only exist at the scene level. I keep references to them in the helper classes.
once you add something to a container, a scene or spritesheet or whatever, release it, the container keeps a ref to it, then when you dispose of the container, it will be released.
Scene will not be released if you call push scene, only if you run a new one or call the replaceScene method of CCDirector.

Preventing view from unloading in iOS SDK?

I've built an app that uses a UITableView inside a UINavigationController, inside a UITabBarController. Every entry in the UITableView opens up a view that contains some basic text, buttons, but most importantly, an MPMoviePlayerController that plays audio when started. A user can click this MPMoviePlayerController and continue to browse around the rest of the app (different tabs, or moving back in the navcontroller, opening other views from the tableview) and continue to hear the audio.
I'd like the user to be able to return to the view with the active MPMoviePlayerController at any time. I understand how I would go about allowing the user to return to a certain view from any view, but I'm struggling with how to prevent that view from being reloaded when the user tries accessing the same view.
Is there any way I can save a view in memory? Or save the active MPMoviePlayerController as some type of global object, so that I can at least access that from anywhere?
I appreciate any and all help. Thanks!
I'd recommend you create a property for the MPMoviePlayerController in your app's UIApplicationDelegate (which you can then access from anywhere in the code with [UIApplication sharedApplication].delegate but you will need to cast to your UIApplicationDelegate subclass).
When you come to enter the screen which plays content, check whether your movie player property in the app delegate is nil, if it is create it, otherwise re-use it.
Don't forget to release the reference to your MPMoviePlayerController when the media stops playing, or when the media has already stopped and you get a memory warning or when your app shuts down.
The down side of this approach is it causes coupling between most of your view controllers and your app delegate. You could mitigate this with the use of a protocol however.
You should simply retain it. Like this [myView retain] and keep a pointer to it in where you need. When you want myView to appear, just add it as a subview to current visible view like[myController.view addSubview:myView].
Hope that will help, Good luck!
I've found that even adding a retain doesn't do the trick. I've actually found the best success with overriding the setView (since part of unloading the view involves calling setView:nil. I have a BOOL that gets set the FIRST time the VC loads and once thats set it will never allow setView to be called again.
- (void) setView: (UIView*) view{
NSLog(#"MainViewController: setView");
// this is our attempt to stop iOS from unloading our view.. when iOS tries to unload your view they call setView:nil.. so, no!
if(!viewDidAppear) [super setView:view];
}
A little bit of a hack, but you can override setView: in your subclass so that it never allows to set the view to nil:
-(void)setView:(UIView *)view
{
if (view == nil) return;
[super setView:view];
}

opengles view switching problem

I´m trying to make simple game using OpenGLES.
I have two EAGLViews(menu and game view).
Each view has its own viewController. Initializing of the views is done by initWithNIBName method of the viewController.
And when I want to show the view, I simply use addSubview method of the main window.
The game view is initialized only once at the launch time. Menu view is initialized only if it´s needed. Problem is, that when I go from game view to menu and then back, and then I redraw the game view, something goes wrong. (I'm setting EAGLContext in drawView method before drawing, so the context may be right).
Don´t you know where is the problem?
Or if the whole switching is managed wrong, gimme and advice please.
Thanks for replies.
I guess you are having a trouble with texture not showing correctly?
I don't know the real thing behind OpenGL, but this is my hypothesis:
Each time you came back to the EAGLView, The EAGLContext of EAGLView is changed. (if you have been copying and pasting from the OpenGLES template) The textures can only be loaded after the context is in its correct state, or else you cannot load any texture. Now, by leaving the EAGLView, and coming back, you are instantiating a new EAGLContext from initWithCoder:(NSCoder*)coder :
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}
So how can we preserve this context? I make it global. Simple as that. And when EAGLView is to be instantiated once again, have it check that whether the "global" EAGLContext is nil or not. If it's nil, just instantiate it, else do nothing. And never ever release or dealloc this global EAGLContext unless you want to quit your program.
This works for me, but again, my hypothesis above may not be correct. If anybody knows the real thing, please lecture me. I am also humbly in need of guidance. I want to truly know why this occurs and why do we have to do this as well.
And by the way, does this answer your question, Jenicek?