So I'm developing an iPhone game using cocos2d.
I'm using pushScene to transition from my gameplay scene to a main menu scene because I want the player to be able to resume gameplay from the main menu if they choose. If they do choose to resume, I use popScene.
The trouble seems to be that if events are "incomplete" at the time when the gameplay scene is pushed, when the scene is popped, those events do not complete. What's worse, they leave artifacts on the scene that don't clear by themselves.
Examples: particle explosion effect (particles get "frozen" and don't dissipate as designed), sprite fade-out effect (sprite remains visible at a certain level of transparency).
I guess I would have expected popScene to resume the scene exactly as it was when the scene was pushed, but it seems to be "abandoning" currently-running actions.
How can I achieve my goal?
I suggest you to add both game play layer and main menu layer to the same CCScene as children rather than using pushScene and popScene. When you need to show the menu, change the visibility of the layers and pause all the contents in your game play layer. You can try these methods to recursively pause/resume activities of your game layer:
- (void)pauseSchedulerAndActionsRecursive:(CCNode *)node {
[node pauseSchedulerAndActions];
for (CCNode *child in [node children]) {
[self pauseSchedulerAndActionsRecursive:child];
}
}
- (void)resumeSchedulerAndActionsRecursive:(CCNode *)node {
[node resumeSchedulerAndActions];
for (CCNode *child in [node children]) {
[self resumeSchedulerAndActionsRecursive:child];
}
}
call something like:
[self pauseSchedulerAndActionsRecursive:gamePlayLayer];
I used Hailei's code but created a category that works OK for me.
CCNode+additions.h
#import "CCNode.h"
#interface CCNode (additions)
-(void)pauseSchedulerAndActionsRecursive;
-(void)resumeSchedulerAndActionsRecursive;
#end
CCNode+additions.m
#import "CCNode+additions.h"
#implementation CCNode (additions)
-(void)pauseSchedulerAndActionsRecursive {
[self pauseSchedulerAndActions];
for (CCNode *child in [self children]) {
[child pauseSchedulerAndActionsRecursive];
}
}
-(void)resumeSchedulerAndActionsRecursive {
[self resumeSchedulerAndActions];
for (CCNode *child in [self children]) {
[child resumeSchedulerAndActionsRecursive];
}
}
#end
So when pausing, call pauseSchedulerAndActionsRecursive on your game node before you add a "pause menu node" to the game node (else the pause node will be paused too and thus unusable).
Related
I want to create a starry sky for some scenes.
The main problem is it needs some time to fill all the screen with particles. Someone advices me to create the whole sky at the beginning and save it between its calls.
I tried something like this:
#implementation StarrySky
static StarrySky *_starrySky;
- (id)init
{
if ((self = [super init])) {
NSArray *starsArray = [NSArray arrayWithObjects:#"Stars1.plist", #"Stars2.plist", #"Stars3.plist", nil];
for(NSString *stars in starsArray) {
CCParticleSystemQuad *starsEffect = [CCParticleSystemQuad particleWithFile:stars];
[self addChild:starsEffect z:-2];
}
}
return self;
}
+ (StarrySky *)sharedStarrySky
{
if (!_starrySky) {
_starrySky = [[StarrySky alloc] init];
}
return _starrySky;
}
- (void)dealloc
{
_starrySky = nil;
[super dealloc];
}
#end
But the particles stop moving.
One solution that ought to work is to create the star particle system and add it to your first scene (ie main menu). Set it to invisible or move it way off the screen or behind the background image (lowest z-order) so it can create some stars without actually showing any stars in the menu.
Now when you switch to your actual game scene, you alloc/init the game scene and then remove the star particle system from the currently running scene and add it to the game scene. Basically you're moving it from one scene to another.
Now you can call replaceScene with the game scene which now contains the already active star particle system.
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 am writing a pause menu using a CCLayer. I need the layer to swallow touches so that you cannot press the layer below, however I also need to be able to use the buttons on the pause layer itself.
I can get the layer to swallow touches, but the menu won't work either.
Here is my code:
pauseLayer.m
#import "PauseLayer.h"
#implementation PauseLayer
#synthesize delegate;
+ (id) layerWithColor:(ccColor4B)color delegate:(id)_delegate
{
return [[[self alloc] initWithColor:color delegate:_delegate] autorelease];
}
- (id) initWithColor:(ccColor4B)c delegate:(id)_delegate {
self = [super initWithColor:c];
if (self != nil) {
NSLog(#"Init");
self.isTouchEnabled = YES;
CGSize wins = [[CCDirector sharedDirector] winSize];
delegate = _delegate;
[self pauseDelegate];
CCSprite * background = [CCSprite spriteWithFile:#"pause_background.png"];
[self addChild:background];
CCMenuItemImage *resume = [CCMenuItemImage itemFromNormalImage:#"back_normal.png"
selectedImage:#"back_clicked.png"
target:self
selector:#selector(doResume:)];
resume.tag = 10;
CCMenu * menu = [CCMenu menuWithItems:resume,nil];
[menu setPosition:ccp(0,0)];
[resume setPosition:ccp([background boundingBox].size.width/2,[background boundingBox].size.height/2)];
[background addChild:menu];
[background setPosition:ccp(wins.width/2,wins.height/2)];
}
return self;
}
-(void)pauseDelegate
{
NSLog(#"pause delegate");
if([delegate respondsToSelector:#selector(pauseLayerDidPause)])
[delegate pauseLayerDidPause];
}
-(void)doResume: (id)sender
{
if([delegate respondsToSelector:#selector(pauseLayerDidUnpause)])
[delegate pauseLayerDidUnpause];
[self.parent removeChild:self cleanup:YES];
}
- (void)onEnter {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INT_MIN+1 swallowsTouches:YES];
[super onEnter];
}
- (void)onExit {
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
[super onExit];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
return YES;
}
-(void)dealloc
{
[super dealloc];
}
#end
why dont you just disable touches on the game layer?
like in the onEnter method disable the touches on the game layer..and onExit re enable them
something like
-onEnter{
gameLayer.isTouchEnabled=NO;
....
}
-onExit{
gameLater.isTouchEnabled=YES;
...
}
also you wont need CCTouchDispatcher
According to your code, the problem is the modal layer is swallowing events even if it's for the own children.
To solve this problem, you have to set the touch priority of the children even higher than the modal layer itself.
In other words, set the menu's touch priority value below modal layer's.
There are two solutions.
Simply override "CCMenu::registerWithTouchDispatcher()" method and set the priority higher from the beginning.
Change menu's touch priority using "setPriority" method of the touchDispatcher or "setHandlerPriority" of menu itself.
To achieve second solution, you have to pay attention to the timing.
"CCMenu::registerWithTouchDispatcher()" is called somewhere AFTER "onEnter" and "onEnterTransitionDidFinish".
So, use "scheduleOnce" or something like that.
Sample codes.
- (id) initWithColor:(ccColor4B)c delegate:(id)_delegate {
self = [super initWithColor:c];
if (self != nil) {
//your codes...... put CCMenu in instance
[self scheduleOnce:#selector(setMenuPriority:) delay:0];
}
return self;
}
- (void) setMenuPriority (ccTime)dt {
[[[CCDirector sharedDirector] touchDispatcher] setPriority:INT_MIN forDelegate:menu];
//priority "INT_MIN" because you set the layer's priority to "INT_MIN+1".
}
PS: My english is not so good, so if there are loose sentences, correction will be very pleased :)
The problem is, that the layer/node hierarchy is not considered when propagating touches.
The touches are handed from the touch handler with the smallest priority value to the ones with the highest.
The touches are not forwarded anymore, once one of the responders swallows the touch.
You can use an approach similar to CCMenu. CCMenu handles all touches and detects which CCMenuItemhas been selected, based on the position of these items.
If you implement this the same way, you let your PauseLayer handle and swallow all touches and use a seperate mechanism to determine which child element in your PauseLayer has been selected.
Example Implementation: CCMenu Subclass
I have implemented a solution in this repository:
https://github.com/Ben-G/MGWU-Widgets/tree/master/Projectfiles/Components/CCMenuBlocking
That component is a CCMenuSubclass that swallows all touches and does not forward them.
Example Implementation: CCNode
Here is a more general solution of a CCNode that swallows touches and only forwards them to its own children:
https://github.com/Ben-G/MGWU-Widgets/blob/master/Projectfiles/Components/Popup/PopUp.m
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.
I am developing a game on Iphone using cocos2d. I have a CClayer containing 20 CCSprite. I am playing a sound and I would like to disable the touch events on all the CCSprite or on the entire layer while the sound is playing. I looked at the property of CCLayer called isTouchEnabled but the behavior doesn't propagate to the children (all the CCSprite). Unless it is not documented, there seems to be no equivalent property for CCsprite. Does anybody know an easy way to this?
Thanks
I ma using below method to disable touch on CCMenu items on a layer below on a view may be help you out.
Call the below method and disable all sub Menu or subNode.
[self MenuStatus:NO Node:self]; // to disable
method is:
-(void)MenuStatus:(BOOL)_enable Node:(id)_node
{
for (id result in ((CCNode *)_node).children)
{
if ([result isKindOfClass:[CCMenu class]])
{
for (id result1 in ((CCMenu *)result).children)
{
if ([result1 isKindOfClass:[CCMenuItem class]])
{
((CCMenuItem *)result1).isEnabled = _enable;
}
}
}
else
[self MenuStatus:_enable Node:result];
}
}
[self MenuStatus:YES Node:self]; // to enable**
A member of another forum posted this solution
So all your sprites normally receive touch events? If you know when the sound is playing, you just could have them check that and ignore the touch if the sound is playing. For example, if your sprites implement the CCTargetedTouchDelegate protocol, you could do something like:
- (BOOL)ccTouchBegan:(UITouch*)touch withEvent:(UIEvent*)event {
if (soundIsPlaying) {
return NO; // i.e., the sprite is currently uninterested in the touch
}
// Other checks and behaviour here.
return YES;
}