I have a sprite-kit game where I need to be checking often to see if the player has lost
- (void)update:(NSTimeInterval)currentTime
{
for (SKSpriteNode *sprite in self.alienArray ) {
if (sprite.position.y < 10) {
LostScene *lostScene = [[LostScene alloc] initWithSize: CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds))];
NSLog(#"about to present");
[self.view presentScene:lostScene transition:[SKTransition fadeWithDuration:0.5]];
}
}
}
but when this method gets called (which I know is happening), no scene presents. What am I doing wrong? I believe it has something to do with the transition, because when I take it out, it works fine
You should add a property of the existing scene like: BOOL playerHasLost and edit your update method:
- (void)update:(NSTimeInterval)currentTime
{
if(!playerHasLost)
{
for (SKSpriteNode *sprite in self.alienArray )
{
if (sprite.position.y < 10)
{
playerHasLost = YES;
LostScene *lostScene = [[LostScene alloc] initWithSize: CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds))];
NSLog(#"about to present");
[self.view presentScene:lostScene transition:[SKTransition fadeWithDuration:0.5]];
break;//to get out of for loop
}
}
}
}
So this way as soon as the sprite get to position that you treat as lost position it will set the variable to YES, present the scene and it will not do it again.
The reason is simply that the update method gets called every frame, with a running transition the scene will be presented anew every frame and thus it appears as if nithing is happening. You should see the NSLog spamming the log console.
Related
This seems like a huge glitch in my opinion.
I have an SKScene overlaying a SceneKit scene. In my app, a method is called to update progress on a "progress bar" (really just an SKShapeNode with a really low height). This method is called updateSaveDataNotification and takes an NSNotification. I called [self childNodeWithName:] to access a child node. The problem is, when it's called, self has no children. Even though it really has three children.
I'm changing my code so they're variables in the implementation so I can animate their scale that way, but why would this behavior even exist? Why would self have zero children in this instance?
EDIT: the progress bar is simply an SKShapeNode. Here's the code I used:
#implementation {
SKShapeNode *progressBar;
float progress;
}
…
-(void)load {
[[NSNotificationCenter defaultCenter] addObserver:self name:#"anyName" selector:#selector(rcvNotification:) object:nil];
progress = 0.0;
…
progressBar = [SKShapeNode shapeNodeWithRectOfSize:CGSizeMake(self.size.width * 0.9, 2)
progressBar.xScale = 0;
progressBar.alpha = 0;
progressBar.fillColor = [UIColor whiteColor];
progressBar.strokeColor = [UIColor whiteColor];
progressBar.position = CGPointMake(self.size.width / 2, self.size.height * 0.1);
progressBar.name = #"progressBar";
…
[self addChild:progressBar]
[progressBar runAction:[SKAction fadeAlphaTo:1 duration:1]];
}
// The notification has one KVP in its user info dictionary. This is "someKey."
// "someKey"'s float value is added to "progress."
-(void)rcvNotification:(NSNotification*)notification {
progress += [[notification.userData valueForKey:#"someKey"] floatValue];
// [self updateUI];
// note: if I call updateUI like that, self will have zero children regardless of whether or not it actually has more.
// a remedy:
[self runAction:[SKAction waitForDuration:0] completion:^{
[self updateUI];
}];
}
-(void)updateUI {
NSLog(#"count of children: %lu", self.children.count);
[progressBar runAction:[SKAction scaleXTo:progress duration:0.5] completion:
// code to run when finished animating change
// also logic for reaching 100% progress.
}];
}
My predicament was, for a solid hour, not being able to re-scale that progress bar. I figured out self temporarily had 0 children, so I made it run a 0-second animation. I want to figure out why this odd behavior occurs.
I used profiler and detect that after I go back from view controller that contains game scene view (for example pop back to the main menu). The game scene is still in the memory.
I supposed it's connected to some SKAction I use. How track which object cause that issue.
I use some SKAction run block, repeat forever and etc, and I am sure that the something happen with it.
You cannot delete a SKScene. I would recommend creating a function for when your present the scene. Here is and example
(Swift)
func deleteView(deleteEveryThing:Bool) {
if deleteEveryThing {
self.removeAllActions()
self.removeAllChildren()
//Scene presentation code here
}
else {
self.removeAllChildren()
//Scene Presentation Code here
}
}
(Objective - C)
-(void)deletView:(BOOL)deleteEveryThing {
if (deleteEveryThing) {
[self removeAllNodes];
[self removeAllActions];
}
else {
[self removeAllNodes];
[self removeAllActions];
}
}
So what I did was create a function called deleteView and deleteView has a parameter that is a Boolean (True or false) and if it is trure then it will remove all actions : self.removeAllActions() or [self removeAllActions]; and all children: self.removeAllChildren() or [self removeAllChildren];in the SKScene. This can help in freeing up memory and once those two lines of code ran then you can handle the scene presentation code. There is also an else part which does the same thing but leaves the SKActions in the SKScene.
I am making a game in which when 2 objects (object A and B) collide, the game gets over. Object A's position changes each time update is called. Object B's position is the same. The code is:
[self enumerateChildNodesWithName:#"objA" usingBlock:^(SKNode *node, BOOL *stop) {
if ([objB intersectsNode:node])
{
[node removeFromParent];
[self GameOver];
}
The problem is: I want objA NOT to disappear after the collision. So, for that I removed [node removeFromParent]; , but since update is called again and again. My number of nodes increases and the sound that i have added never seems to end. So, what i tried was adding:
[self performSelector:#selector(pauseGame) withObject:Nil afterDelay:0.1];
-(void) pauseGame
{
self.scene.view.paused = YES;
[self performSelector:#selector(gameOver) withObject:Nil afterDelay:0.1];
}
I had to use performSelector with delay, because putting self.scene.view.paused = YES; within the update wouldn't allow me to go to gameOver. However, I do not want any delays! is there a way to do this??
Thanks
Just use a state variable in your scene which will indicate the current state.
Use this state to run updates on your scene objects.
For example (pseudocode) :
update()
if (state == GAME_PLAY) {
// Update relevant game nodes
} else if (state == GAME_OVER) {
// Update only what needs to be updated when the game is over
}
This way you do not need to stop your entire scene and add only what is relevant to the current game state
Situation:
I'm getting some mysterious crashing shortly after a CCCallFunc. In short, we have a button. The button has a tag to identify it later. When the button is pressed, we run some actions to animate it, and when the animation is done, we CCCallFunc another method to transition to another scene. We crash shortly after the CCCallFunc. Source and errors below.
Point Of Crash (in cocos2d source):
// From CCActionInstant.m of cocos2d
-(void) execute
{
/*** EXC_BAD_ACCESS on line 287 of CCActionInstant.m ***/
[targetCallback_ performSelector:selector_];
}
#end
Snapshot of Thread 1:
My Code:
Below is some source taken from MenuLayer.m (a simple menu to display a button).
// from MenuLayer.m
// …
#implementation MenuLayer
-(id) init{
if((self=[super init])) {
/****** Create The Play Button (As a CCMenu) ********/
CCSprite *playSprite = [CCSprite spriteWithFile:#"playbutton.png"];
CCMenuItemSprite *playItem = [CCMenuItemSprite itemFromNormalSprite:playSprite selectedSprite:nil target:self selector:#selector(animateButton:)];
playItem.tag = 3001;
playItem.position = ccp(160.0f, 240.0f);
CCMenu *menu = [CCMenu menuWithItems:playItem, nil];
menu.position = ccp(0.0f, 0.0f);
[self addChild:menu z:0];
}
}
// ...
- (void)animateButton:(id)sender{
/*** Run an animation on the button and then call a function ***/
id a1 = [CCScaleTo actionWithDuration:0.05 scale:1.25];
id a2 = [CCScaleTo actionWithDuration:0.05 scale:1.0];
id aDone = [CCCallFunc actionWithTarget:self selector:#selector(animationDone:)];
[sender runAction:[CCSequence actions:a1,a2,aDone, nil]];
}
- (void)animationDone:(id)sender{
/*** Identify button by tag ***/
/*** Call appropriate method based on tag ***/
if([(CCNode*)sender tag] == 3001){
/*** crashes around here (see CCActionInstant.m) ***/
[self goGame:sender];
}
}
-(void)goGame:(id)sender{
/*** Switch to another scene ***/
CCScene *newScene = [CCScene node];
[newScene addChild:[StageSelectLayer node]];
if ([[CCDirector sharedDirector] runningScene]) {
[[CCDirector sharedDirector] replaceScene:newScene]];
}else {
[[CCDirector sharedDirector] runWithScene:newScene];
}
}
Use CCCallFuncN instead of CCCallFun.
CCCallFuncN passes the Node as parameter, the problem with CCCallFun is that you are loosing reference of the node.
I test your code with CCCallFuncN and works ok.
Just a hunch. Besides checking for memory leaks, try to schedule a selector with a 0 second interval instead of directly sending the goGame message. I have a suspicion that director's replaceScene causes a cleanup of the scene and all objects associated with it. That in turn could leave the CCCallFunc action in an undefined state. Although normally it works fine - which is to say that this is just another indication about something sketchy, memory- respectively object-lifetime-management-wise.
Btw, if you support iOS 4 as a minimum, use CCCallBlock instead of CCCallFunc. That's safer and cleaner.
Is it possible to detect which CCScene is currently showing on the scene? I have 2 CCScenes in my game and I want a certain action to occur if one is showing.
Also quick related question, if I wanted to check if a CCMenu is not showing currently would I do something like
if (!menu) {
//Menu is not showing currently
}
I am a bit of a noob when it comes to Cocos2D so please forgive me :)
Thanks!
You can use the CCDirector to tell which scene is running.
[[CCDirector sharedDirector] runningScene];
As for whether the menu is showing. You would have to check with the parent of the menu. If the parent where your CCLayer, then you could check by
// assume menu is set up to have tag kMenuTag
CCMenu * menu = [self getChildByTag:kMenuTag];
If the menu is child of some other node, you can get the parent through a similar method and get a reference to the menu.
If the menu == nil, it is not showing.
UPDATE
In cocos2d, you are discouraged from keeping references to all of your sprites, instead you should be giving each node a unique tag and use that to reference it. To achieve your first goal, you can give your scene a tag in your 2 respective CCLayer classes.
You can set up your unique tags in an enum in a file called Tags.h, then import that into any classes that need access to your tags
Example Tags.h
enum {
kScene1Tag = 0,
kScene2Tag = 1,
kMenuTag = 2};
Then in your layer class
+(id) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
scene.tag = kScene1Tag;
// 'layer' is an autorelease object.
HelloWorld *layer = [HelloWorld node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
Now when you grab the current scene you can check against the tags
int currentSceneTag = [[CCDirector sharedDirector] runningScene].tag;
if (currentSceneTag == kScene1Tag) {
} else if (currentSceneTag == kScene2Tag) {
}
The tag property is from CCNode which is the base class of CCLayer, CCScene, CCSprite, CCMenu...
This how to find out which scene is running
if ([CCDirector sharedDirector].runningScene == yourScene1) {
// your scene 1 is showing
} else {
// your scene 2 is showing
}
and to find out if a node is child of the running scene
BOOL isShowing = NO;
CCNode *node = yourMenu;
while (node != nil) {
if (node == [CCDirector sharedDirector].runningScene) {
isShowing = YES;
break;
} else {
node = node.parent;
}
}
if (isShowing) {
// your menu is in the display hierarchy
}