The word “properly” might be an overstatement as there could be even more proper ways to remove a node, but I noticed something VERY strange this morning with Sprite Kit. My latest iOS Starter Kit, (shameless plug) has an object class used for a lot of things, enemies, trees, whatever you can think of that might receive damage from a character and eventually bust. So I thought it might be nice to add an option to the kit where the object/enemy can recover health over time. Easy, breezy code: Every second add back some decimal amount to the current health of the object. For those curious it looks something like this…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
-(void) gainBackHealth{ if ( _currentHealth < _maxHealth){ _currentHealth = _currentHealth + gainHealth; } [self performSelector:@selector(gainBackHealth) withObject:nil afterDelay:1]; NSLog(@"current health %f", _currentHealth ); } |
So I call gainBackHealth which checks to see if the object should gain back some health, if so, it adds the gainHealth amount (established elsewhere) and then I use performSelector and do it all again one second later. So for any object that will gain back health over time, this method is running every second.
So I build the app, and magically I’ve created a harder, “bossier” character to kill. Too hard actually. I died trying to kill the character and the level reset. But here’s where it gets interesting. When the level started over AFTER removing every node, I was seeing the NSLog statement running twice every second. Odd. Then I went to go kill the enemy, and now I’m seeing this happen in the Output Window…
1 2 3 4 5 6 |
current health 10.000000 current health 9.500000 current health 10.000000 current health 8.500000 current health 10.000000 current health 7.500000 |
I’m doling out some damage to the enemy, and its registering the pain, but somewhere out there in the Sprite Kit world an object still has 10 for their current health. And only one object was ever set to gain back health over time. So what happened was the performSelector was never cancelled on the original enemy I tried to kill before I died and the level was reset.
So lets look at some of my reset code…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
-(void) gameOverResets { //... unrelated code to this article... [myWorld enumerateChildNodesWithName:@"*" usingBlock:^(SKNode *node, BOOL *stop) { [node removeFromParent]; }]; [myWorld removeFromParent]; } |
As you can see, I go through every child in myWorld and remove them using enumerateChildNodesWithName:@”*” , and then finally I remove myWorld just to be sure everything is cleared out. At this point, nothing is left in the scene, and my assumption was that the world has been cleared of any “thought” processes (e.g. performSelectors ). GUESS NOT! So here’s the conclusion, I needed to add this line… [NSObject cancelPreviousPerformRequestsWithTarget:node];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(void) gameOverResets { //... unrelated code to this article... [myWorld enumerateChildNodesWithName:@"*" usingBlock:^(SKNode *node, BOOL *stop) { [NSObject cancelPreviousPerformRequestsWithTarget:node]; [node removeFromParent]; }]; [myWorld removeFromParent]; } |
This was a tad surprising since I assumed that removing a node would automatically “clean it up” so to speak and cancel those perform requests, but hey, its not much more work to add the line above.
Hope this helps someone else out there.
0 Comments
Leave a reply
You must be logged in to post a comment.