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!
Related
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.
I have seen several posts (E.g. this one) on this but I still don't get it.
If I call the CCDirector pause method it sets the animation interval to 1/4 and the value isPaused_ to YES (see code below). In the CCDirector.m class the isPaused_ variable
doesn't seem to be used much apart of in the pause and resume methods.
I thus decided to call also the stopAnimation method but in some posts this is not mentioned:
[[CCDirector sharedDirector] stopAnimation]
It doesn't apparently stop accelerometer data and input data to be sent to the main scene. It kind of does make sense to me because the developer of the Game might want to allow the user to resume by shaking the iPhone or tapping a resume button. Is this the reason behind this choice?
Also, why is the animation interval set to 1/4 and why things even with this value don't move (apart of my player entity that moves using accelerometer input)?
Thanks a lot!
-(void) pause
{
if( isPaused_ )
return;
oldAnimationInterval_ = animationInterval_;
// when paused, don't consume CPU
[self setAnimationInterval:1/4.0];
[self willChangeValueForKey:#"isPaused"];
isPaused_ = YES;
[self didChangeValueForKey:#"isPaused"];
}
Pausing CCDirector reduces framerate to 4 fps in order to conserve both battery and CPU cycles. The latter is necessary if you use UIKit views, in some cases it is necessary to pause the director in order for UIKit animations to animate smoothly.
What pausing the director also does is that it stops updating all nodes. Specifically the CCScheduler doesn't fire any scheduled selectors or update methods. It also prevents touches from being passed on to nodes, because they are relayed through the CCTouchDispatcher which is also paused when the director is paused.
But as you noticed, the accelerometer isn't paused. That's because cocos2d doesn't provide a wrapper for UIAccelerometer and therefore you get these notifications directly from iOS, ignoring the pause status of the director. If you then change the position of nodes inside the didAccelerate method or another method called directly from it, that node will change its position despite director being paused.
This is but one reason why director's pause isn't really suitable for a "pause game" feature. Another issue is that pausing simply would prevent any pause menu built with cocos2d features would also be paused, which kind of defies the purpose.
Then startAnimation and stopAnimation are simply extreme measures that prevent cocos2d from updating the screen altogether. This is normally only used in situations where either the cocos2d view is removed temporarily from the view hierarchy, or hidden, or some UIKit view is going fullscreen. In that sense stopAnimation is like a suspend feature.
This only happens once in a while. When I step from a breakpoint in the method called by the menu item when pressed I end up at the end of the method and when I step out I eventually get to ccTouchEnded and then the bad access occurs. Nothing shows up in the debug output window but I get a green arrow pointing to the main method with the error message.
Any ideas why this might occur?
Thanks.
So in case anyone has the same problem, I figured out what was happening. I had a CCMenu containing several children. When a child was tapped I did what I wanted with it and then removed it from the CCMenu via removeChild:cleanup: in the method I passed as the selector for the CCMenuItem. The problem was that Cocos2d deactivates the CCMenuItem while the selector method is executed and then reactivates it when the method is finished. So in the method I was basically destroying the CCMenuItem by removing it from the CCMenu and then at the end of the method Cocos2d tried to reactivate it but it was no longer in memory.
I don't see much of a way around this, so maybe it is not possible to remove a CCMenuItem from a CCMenu in its selector method.
The way I worked around it was to simply call setVisible:NO and setIsEnabled:NO on the menuitem. However, I can imagine cases in which this would not be the best way to do it. Maybe in these cases you could mess with the z position or something to get the menuitem out of the way.
Anyway, I hope this helps someone else, I know I've been stuck on this a while. :)
A better solution, in my humble opinion, is to unwind the scene destruction call from the stack. Using something like NSTimer+BlockKit makes it really clean. Here's an excerpt from my code:
- (void)menuAction
{
// we use a timer here to delay the execution of the action because it
// destroys the current scene and we're mid a call on CCMenu's ccTouchEnded
// that isn't expecting a scene tear down
// http://stackoverflow.com/questions/11165822/exc-bad-access-occurring-when-cocos2d-calls-cctouchended-in-ccmenu
[NSTimer scheduledTimerWithTimeInterval:0 block:^(NSTimer* timer)
{
[[CCDirector sharedDirector] popSceneWithTransition:
[CCTransitionSlideInL class] duration:kTransDur];
}
repeats:NO];
}
In Cocos2d I have 2 classes, HelloWorldScene (The standard main class for the Cocos2d template) and OptionsScene. I launch OptionsScene from HelloWorldScene and than when I'm done using OptionsScene I launch HelloWorldScene from OptionScene.
My problem is init is called when I launch HelloWorldScene from OptionsScene and I don't want to let init be called because it essentially resets my game.
Here is the code I am using to launch HelloWorldScene from OptionsScene:
HelloWorld *hw = [HelloWorld alloc];
[hw loadWithParticleColor:particleColor andBloppleColor:bloppleColor];
[hw release];
[[CCDirector sharedDirector] replaceScene:[CCTransitionRotoZoom transitionWithDuration:1.0 scene:[HelloWorld node]]];
Any hints or solutions would be of great help!
Since it's a relatively simple hierarchy, you can use the CCDirector's pushScene: method and add the OptionsScene on top of the HelloWorldScene. Dismiss with popScene and your HelloWorldScene should appear in the state you left it in.
Replacing the scene is usually used when you want to conserve resources, which is not necessary in this case.
I have figured out how all of the StoreKit stuff works and have actually tested working code... however, I have a problem.
I made my "store" layer/scene the SKProductsRequestDelegate. Is this the correct thing to do? I get the initial product info like so:
SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: productIDs];
[productRequest setDelegate: self];
[productRequest start];
The problem is that if I transition to a new scene when a request is in progress, the current layer is retained by the productRequest. This means that touches on my new scene/layer are handled by both the new layer and the old layer.
I could cancel the productRequest when leaving the scene, but:
I do not know if it is in progress at that point.
I cannot release it because it may or may not have been released by the request delegates.
There has got to be a better way to do this. I could make the delegate a class external to the current layer, but then I do not know how to easily update the layer with the product information when the handler is called.
OK, problem solved.... ergh.
I went ahead and made the store a separate class, and solved the issue of callbacks to the scene by adding a delegate to the class, which holds the layer of the Store interface. When the transactions finish, I can use the delegate to call back to my scene/layer.
I solved the issue of not knowing if the delegate has been released by using the respondsToSelector: method before attempting to send a message to it.
It turns out the real bug was caused by my attempt to fix 1 & 2 in the first place. I overrode onExit to let me know when to remove the class as the store delegate. It turns out I forgot to call [super onExit], which is where the scene is released. Hence, it stayed retained and did not get removed from the touchHandler. Oops!