cocos2d - CCDirector, the replaceScene function issue - objective-c

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.

Related

Get class name of top child

I'm working in objective c/cocos2d. I have a scene named AllLevelAndModes which I add a CCScrollView named levelScroll of type class EasyLevelSelectScene. On this level scroller, I have a bunch of buttons for my game level. When someone touches one of the buttons, a popup comes up displaying some stats about the level. I add this popup by add childing it to EasyLevelSelectScene when a button is tapped. This child is of kind of class LevelSelectDropdown. I want to make a functionality such that if a user taps outside the popup (which is smaller than the whole screen), that the popup disappears.
Now I suppose I could get the size of the popup and the screen and if a user taps outside of some range of x and y, the popup disappears. But that is a little cumbersome. Is there a better way to do this? I was trying to basically get the name of the class that is tapped by running the following code once the popup comes.
-(void) touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocationTemp = [[CCDirector sharedDirector] convertToGL: [touch locationInView:touch.view]];
CCResponderManager *responder = [[CCDirector sharedDirector] responderManager];
CCNode *node = [responder nodeAtPoint:touchLocationTemp];
CCLOG(#"class type %#", node); //Always reads 'EasyLevelSelectScene' no matter where I tap
if([node isKindOfClass:[EasyLevelSelectScene class]]) {
//go back to level select screen
}
}
Unfortunately this doesn't work though, since wherever I tap it logs EasyLevelSelectScene, even when I tap right on top of my popup! Why is it doing this, and is there a way to get the class name of the top-most child? Thanks for the help!

iOS7 Sprite Kit how to reset/start new game with Sprite Kit?

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

Cocos2d and concurrency: how to properly stop animations between scenes

I got a GameScene (here called ShooterScene) and I do exit to a MainMenu. I do use sprite sheets which I load in a texture cache. There are various animations going on (on background elements, enemies, player etc...). I had for months an issue where there was an assertion failure on the "setTexture" method for the Enemies. I solved this by pausing the shared CCDirector before replacing the scene. This has the effect of stopping the animations. However I solved this only partially. The game doesn't crash anymore on the Enemy setTexture but now crashes on the AnimatedBackgroundElement setTexture method.
The background elements are added to a sprite batch node that is child of the Background class that is added as child to the GameScene. I do wonder if, for some hierarchy reasons, there is some delay in the propagation of the pause call or if there are some concurrency problems between the "pause" thread and the CCAnimate->setTexture thread of the background element.
Any suggestions/ideas?
PS: I checked the "[[CCDirector sharedDirector] stopAnimation]" and it says:
CCLOG(#"cocos2d: Director#stopAnimation. Override me");
Am I meant to "override" this method to solve issues described above? I guess that this would be a good place to really stop all the animation of all the children with a recursive call but again, I am not sure on the latency due to the potential concurrency with the subsequent "replaceScene" method. I could solve this by using a sequence of callbacks (CCSequence with action1 = call stop animation and action2= replace scene) but again I am not 100% sure on the fact that the call of action2 will happen only after the end of the call to stop animation (action1). I could pheraphs write a recursive for (all children) and place that before the replaceScene.. or callback the replaceScene with a delay (which would not be an elegant solution as it would be semi-arbitrary)..
-(void) restartLevel
{
[self resetSharedData];
[[CCDirector sharedDirector] pause];
LevelName currentLevel = [GameController sharedGameController].currentlyPlayedLevelName;
[[CCDirector sharedDirector] replaceScene:[ShooterScene sceneWithLevelName:currentLevel]];
}
-(void) exitToMainMenu
{
[self resetSharedData];
[[CCDirector sharedDirector] pause];
[[CCDirector sharedDirector] replaceScene:[MainMenu scene]];
}
PPS: I see that there are various related posts on trying to stop a scene. I guess other people might have similar issues so there is probably a state-of-the art solution/design pattern for this.
EDIT: I have tried implementing this but it still fails.
-(void) restartLevel
{
[self resetSharedData];
[[CCDirector sharedDirector] pause];
LevelName currentLevel = [GameController sharedGameController].currentlyPlayedLevelName;
[self stopAllActions]; //Does this actually stopAllActions and Animations for ALL child nodes?
[self unscheduleAllSelectors];
//Do I need something else here?
[[CCDirector sharedDirector] replaceScene:[ShooterScene sceneWithLevelName:currentLevel]];
}
[self stopAllActions]; not really stop all actions added to layer's child node. You need to manually call stopAllActions on particular node.
-(void)onExit
{
[self.gameHeroSprite stopAllActions]; //call this for all animated sprite
[self stopAllActions];
[super onExit];
}
Stupid question, why is [self stopAllAnimations]; not working? I call that and [self unscheduleAllSelectors]; before a scene change, it whacks every animated object and timer in the entire layer, which should make it safe to switch scenes without assertion failures.
With regards to the pause thing, I KNOW!! seriously, the whole pause thing is really fidgity, it seems to pause stuff but it doesn't always pause stuff. As far as I can tell, when you pause: timers stop (and the dt doesn't increase), the accelerometer and touches still work, animated objects and moving objects stop moving, and menu buttons still work and ALL the code inside the function called by a menu button still runs. Additionally it finishes all the code inside the function that calls the pause, so if you do a pause and then cancel timers and change scenes all in one function, you'll still get an allocation error.
Here's what I do, more specifically:
- Stop all animations
- Stop all timers
- set a variable "gameRunning" to false, and on the first line of gameLogic if(!gameRunning) return;
- Set a timer for 0.1 seconds to only fire once to call a makeTransition function
- Make the actual scene change inside the timer.

How to kill a cocos2d scene directly without replacing scene

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

UIPanGestureRecognizer crashes unexpectedly

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 =)