Cocos2d and concurrency: how to properly stop animations between scenes - objective-c

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.

Related

NSView drawRect method not working?

Right now I have two windows. One of them has an NSView, which acts as a background color and the other one has a colorwell that changes a color variable in a shared instance.
My program sort of works. I can open my colorwell window and select a color, but the background color only updates if I manually resize the window.
I tried to get past this issue by having a thread in the background looping:
[self setNeedsDisplay:YES];
[self updateLayer];
[self display];
I'm not completely sure that those 3 lines are necessary, but I am sure that they are re-calling my drawRect method.
I even threw an NSLog into the drawRect method to test. I saw in the console that it was getting called over-and-over again.
Here is my drawRect method:
- (void)drawRect:(NSRect)dirtyRect{
[theDATA.main_frame_background_color setFill];
[NSBezierPath fillRect:dirtyRect];
}
theDATA.main_frame_background_color is an NSColor from a shared instance. I am positive that the value is changing because my NSView updates when I resize the window.
I'm completely clueless on why this is not working. Hope you can help.
First, never call a display method directly. Mark dirty area with setNeedsDisplay: and the view will be redrawn after a while. If your view is layer-backed, be aware of layerContentsRedrawPolicy property, since it's default value is NSViewLayerContentsRedrawNever. There's no way to automatically determine which policy fit your needs best, so this property should be set manually.
I solved my problem thanks to #Sega-Zero's suggestion of using layerContentsRedrawPolicy and through a bit of research I found out that I couldn't directly call setNeedsDisplay in a thread, so I used performSelectorOnMainThread.
My Thread class:
- (void)updateLOOP{
while (true){
[self performSelectorOnMainThread: #selector(refreshClock)
withObject: nil
waitUntilDone: NO];
[NSThread sleepForTimeInterval:.1];
}
}
}
External update method:
- (void) refreshClock{
[self setNeedsDisplay:YES];
}
Now everything works as it should. The drawRect method is getting called without having to use [self display].

Need help incorporating "Preform Selector: WithObject: AfterDelay:" with the SKScene being paused

I need help incorporating the
PreformSelector: WithObject: AfterDelay:
With the SKScene being paused.
I am making a game where there is a pause button which pauses the scene. This, however does not affect the
PreformSelector: WithObject: AfterDelay:
function which calls the "Reload" function. Without this fixed the user can fire a shot, press pause, wait for the reload function to be called, un-pause, and shoot. This makes it so the user can continuously fire without having to reload in "game time". Is there any way to fix this?
You shouldn't be using performSelector:withObject:afterDelay:, because keeping track of timing and storing the selectors and objects etc. with that method would be very cumbersome.
Instead, make use of SpriteKit's +[SKAction waitForDuration:] action.
You would want to do something like the following (I'm assuming this code takes place somewhere in one of your scene's methods):
// Replace 2.0 below with however long you want to wait, in seconds
SKAction *waitAction = [SKAction waitForDuration:2.0];
// I'm assuming your "Reload" method is a method declared in your scene's
// class and not some other class, so I'm using "self" as the target here.
SKAction *reloadAction = [SKAction performSelector:#selector(Reload) onTarget:self];
SKAction *sequenceAction = [SKAction sequence:#[waitAction, reloadAction]];
// Since I'm assuming this is in your scene implementation, `self` here
// refers to your scene node.
[self runAction:sequenceAction];
Since actions can be paused and resumed, when you pause your scene with self.paused = YES; your actions will also get paused and will resume where they left off when you later unpause your scene.

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

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

cocos2d - CCDirector, the replaceScene function issue

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.