I have a home button on my scene which when pressed goes to the home menu. I use replaceScene to replace the current scene (Game Scene) with the HomeMenu Scene. For some reason the actions and sounds which are happening in the game scene are not stopped when I replace the scene. I have tried the following code but still when I am in the home menu I can hear the actions and sounds of the game scene playing.
// fired when the home menu is
clicked!
-(void) homeMenuClicked:(CCMenuItem *) item { NSLog(#"home menu clicked!");
CCScene *scene = [[CCDirector
sharedDirector] runningScene]; [scene
stopAllActions];
[self.layer stopAllActions];
[self unloadSoundEffects];
[[CCDirector sharedDirector]
replaceScene:[CCSlideInLTransition
transitionWithDuration:1.0
scene:[HomeScene scene]] ];
}
I must also add that the game layer also has a timer (NSTimer) object which starts in 2 seconds or something.
UPDATE 2:
Let me post some code! I think the problem is that when the player guess the correct answer the following method is invoked:
[self updateForCorrectAnswer];
Inside updateForCorrectAnswer I have a performSelector which is scheduled to fire in 6-7 seconds. I believe that performSelector is the culprit. If somehow can I stop that from being firing then the I think I will be fine.
[self performSelector:#selector(refreshScore) withObject:nil afterDelay:7.0];
You should not use NSTimer as cocos2d documents.
/* call scheduleUpdate in initializing */
[self scheduleUpdate];
It schedules update: method to be called every frame when this node is on the stage.
- (void)update:(ccTime)dt
{
/* This method is automatically called every frame. */
}
scheduleUpdateWithPriority:, schedule:interval: are also available.
And why don't you use like this instead of performSelector:withObject:afterDelay.
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:7], [CCCallFunc actionWithTarget:self selector:#selector(refreshScore)], nil]];
If you use this method "[self performSelector:withObject:afterDelay:]", cocos2d will not manage it in this run loop. You should use it instead of the previous one:
[self schedule:interval:];
Then in your "homeMenuClicked:" method, just call this:
[self.layer unschedule:#selector(refreshScore)];
I haven't tried it but I think it'll be better.
For more information you can see the documentation here.
Related
Let's say I have a character in a game and its class is like this.
#interface Player
{
CCSprite* stand;
CCAnimation* run;
}
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
[self runAction:runAniate];
}
-(void) playStandAction
{
stand.visible = YES;
[self stopAllActions];
}
The player has ability to stand or run.
But one problem is, after playStandAction is called, stand animation is visible and running animation stopped, but one frame of running animation still there!
( Now you see 'stand sprite' AND 'one of running animation frame' together. )
How can I make running animation not visible?
P.s Can anyone throw me a better way of managing animation in one character? This is totally disaster as animations added.
-(void) playStandAction
{
//Make the animation object.visible = NO; here
stand.visible = YES;
[self stopAllActions];
}
and in
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
//Make the animation object.visible = YES; here
stand.visible = NO;
[self runAction:runAniate];
}
Use method with parameter restoreOriginalFrame and pass it yes
I don't know which method you are calling for creating CCAnimate object...
Like this:
[CCAnimate actionWithAnimation:animation restoreOriginalFrame:YES]];
And don't call runAction on layer. I would prefer you to runAction on sprite itself...
You don't need to hide and show 2 different objects...
Hope this helps. :)
I'm trying to get Cocos2D on the iPhone to "clean itself up" before I switch back to a UIView-based view in my iPhone app, but it's overreleasing (or, I am overreleasing) something and crashing, and I can't make heads or tails of it.
This is somewhat long, but I've tried to take care to organize it.
My node hierarchy looks like this, and the parenthesis indicate "what's in" each node:
CCScene (Menu)
CCLayer (Character)
CCLayer (Whole Animation)
CCSprite, CCSpriteBatchNode (Parts of Animation, there can be many of each type)
So, as the "Character" runs different animations, I remove the "animation" CCLayer from the "character" CCLayer, create a new "animation" CCLayer, and add it as a child. So far, that's caused no problems.
Finally, there's a button on CCScene that "ends" the Cocos part of the app. I want to return to UIKit-land when I tap the "End" button.
However, before I return back to UIView-land, I want to run one final animation on the Character, and when THAT is finished, terminate. To do that, I "register" a handler on the character CCLayer like this. I call the "final" animation, and then callback to the CCScene when the final animation is done (using KVO):
- (void) doEndWithHandler:(id<LWECharacterDelegate>)handler
{
// Handler is the CCScene, "self.parent" could work but I want it loosely coupled
self.endOfSessionHandler = handler;
// Tell character to start final animation -- this creates a new CCLayer,
// starts the animation, and assigns that CCLayer into self.animatedSequence
[self changeCharacterActionTo:END_ANIMATION key:nil]];
// The "animation" CCLayer has a property called "moving" -- observe it
[self.animatedSequence addObserver:self forKeyPath:#"moving" options:NSKeyValueObservingOptionNew context:NULL];
}
And then my observing code, which is called back to when "moving" becomes NO (=animation finishes):
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"moving"])
{
BOOL movingStatus = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
if (movingStatus == NO)
{
// Stop observing
[object removeObserver:self forKeyPath:#"moving"];
// Stop all animations on this level
[self.animatedSequence stopAnimation];
[self removeChild:self.animatedSequence cleanup:YES];
[self removeFromParentAndCleanup:YES];
// Handler callback
if (self.endOfSessionHandler && [self.endOfSessionHandler respondsToSelector:#selector(characterDidFinishSession)])
{
[self.endOfSessionHandler characterDidFinishSession];
}
}
} // if key = moving
}
Now, you may say,
Didn't know you that you can just add a callback using CCCallFunc as an action on your animation at the end, so you know when you're done moving?
Yes, I do know that - but the point is that the CCScene "knows" when the end button is pressed -- not any one specific animation. The CCAction is already in motion when the end button is pressed, so I want to tell all sprites to STOP animating and destroy.
My animation CCLayer has some special code to tell me when the sprite(s) has stopped moving. That code is working well-- I use a CCCallFunc callback on the end of every animation to tell my "animation" CCLayer class that it's done.
Why it seems to be a problem, though, is that I get the KVO notification that "moving" has changed BEFORE the Cocos2D action stack trace has unwound. I'm pretty sure my problem is somewhere in there, because as soon as the KVO notification comes through, I try to stop everything (see the code above). Yet, not everything stops, because the Cocos2D framework crashes (overrelease) as it tries to "wrap up" the stack trace.
Is it not possible to stop an animation from within a CCCallFunc callback that is an action animating the same sprite?
For you true Cocos-heads out there, the exact line that is crashing is:
if( currentTarget->currentActionSalvaged ) {
// The currentAction told the node to remove it. To prevent the action from
// accidentally deallocating itself before finishing its step, we retained
// it. Now that step is done, it's safe to release it.
[currentTarget->currentAction release];
.. which is on line 327 of CCActionManager.m.
All right, I solved this one on my own. The key lesson here was this:
If you are "cleaning up" a Cocos session, you should be fine to do it FROM a Cocos callback (CCCallFunc), but do NOT call [[CCDirector sharedDirector] end] until after your Cocos callback stack trace has unwound.
That is all.
If I am using an action that does not repeat in cocos2D how do I restart that action?
I am using the code:
CCAnimate* action = [CCAnimate actionWithAnimation:myAnimation];
[mySprite runAction: action];
The action runs fine once, but when an event is triggered I want to be able to run the action again as long as it is finished, so I tried using this when the even is triggered.
if( [action isDone] ){
[mySprite runAction: action];
}
But this results in a crash. Anyone any idea what the correct way to do this is?
try preserving the action in an instance variable. In the header file have a pointer declared
CCAction* myAction;
then when the layer or sprite is initialized
myAction = [CCAnimate actionWithAnimation:myAnimation];
From what point on, whenever you want to call your action do
if( [action isDone] ){
[mySprite runAction: myAction];
}
I think the reason your app is crashing is because you are calling an action that only exists for the duration of the method in which it is initialized.
Im my game i use CCSequences (so i can use CCCallFunc to set/declare variables mid animation), all these CCSequences are stored as instance variables in my CCSprite subclass.
I have an idle animation that repeats for ever.
Whenever I want to 'jump' for instance i call
[self stopAllActions];
[self runAction:jumpSeq];
My jumpSeq is a CCSequence that plays a jump animation, and has a CCCallFunc at the end of the sequence that restarts the idle animation when it is done.
Hope this helps.
Further reading: http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:actions_special?s[]=cccallfunc
Turns out I just wasn't retaining the action and the sprite must have been deleting it once it was done. So now my code is;
CCAnimate* action = [CCAnimate actionWithAnimation:myAnimation];
[mySprite runAction: action];
[action retain];
and then when i want it to run again;
if( [action isDone] ){
[mySprite runAction: myAction];
}
I had the same issue I declared CCAction* myAction in the header file but when i went to call it from another method i experienced a crash but like #Bongeh mentioned when using [myAction retain] it worked perfectly
I have a loading screen that I initialise with a label to display the loading progression.
I want to call my DataManager AFTER initialising the loading screen, and then call a method to switch scenes. Here is my code:
-(id) init {
if((self=[super init]))
{
loadingLabel = ....
[self addChild:loadingLabel];
/***** This is what I want to call after the init method
//DataManager loads everything needed for the level, and has a reference to the
//loading screen so it can update the label
[[DataManager sharedDataManager] loadLevel:#"level1" screen:self];
//this will switch the scene
[self finishLoading];
*****/
}
return self;
}
-(void) setLoadingPercentage:(int) perc {
//changes the label
}
-(void) finishLoading {
[[CCDirector sharedDirector] replaceScene:[Game node]];
}
So I can't call the datamanager in the init, because the label will not get updated as content gets loaded, and I can't switch scenes in the init method. So How do I run my datamanger and finish loading AFTER the init? My plan was to set a schedule at an interval of 1 second that does this, but it doesn't seem right to have to wait a second.
EDIT: another way I could do it is to schedule at every frame and ask the datamanager where its at... This seems better already, since the datamanager wouldn't need a reference to the loading screen.
Any ideas?
You can use performSelector:withObject:afterDelay: to force the execution of the specified selector during the next iteration of the current threads run loop:
[self performSelector:#selector(finishLoading) withObject:nil afterDelay:0.0f];
The above answer is correct, but you should use the Cocos2d way to schedule methods to run later:
[self schedule:#selector(finishLoading) interval:0.1];
-(void)finishLoading
{
[self unschedule:#selector(finishLoading)];
//Do your stuff
}
So my menu calls a game with this piece of code:
game = [[Game alloc] init];
[self presentModalViewController:memoryTest animated:FALSE];
A UIViewController then appears with a countdown. The player can go back to the menu DURING the countdown. However when I try this, the countdown keeps running and eventually the game starts, even thought the UIViewController has been dismissed (therefore the UIView has disappeared) in the backToMenu method.
[self.parentViewController dismissModalViewControllerAnimated:FALSE];
I've tried to release the game object in the viewDidAppear method of the menu, but no luck. I thought of having a "quit" BOOL value, so the countdown can check wether or not the player has quit the game, but there must be a better way to release an object AND stop all method calls inside it.
Thanks for helping me out.
CountDown method:
- (void)countDown {
SoundEffect *sound = [[SoundEffect alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"tick" ofType: #"wav"]];
[sound playAndRelease];
if(self.countDownStep > 0) {
feedback.image = [UIImage imageNamed:[NSString stringWithFormat:#"countdown%d.png",self.countDownStep]];
feedback.hidden = FALSE;
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.8];
}
else {
[self displayMessage:self.startMessage];
[self.game performSelector:#selector(start) withObject:nil afterDelay:0.8];
[self performSelector:#selector(hide) withObject:nil afterDelay:0.8];
}
self.countDownStep--;
}
In the viewcontroller's viewWillDisappear, check if the timer is running. If it is, then just invalidate it.
[timerObject invalidate] will stop the timer
How do you handle the countdown? It seems like you want to explicitly void that countdown in the viewWillDisappear method for the UIViewController you mentioned.
I was assuming you would use something like NSTimer. In fact, that might still not be a bad idea. NSTimer can handle re-running your countdown method every 0.8 seconds. If you decrement the value to 0, your countdown has expired. If your view disappears, invalidate the timer in viewWillDisappear.