SpriteKit SKAction doesn't work after removing and readding a node - ios

Been banging my head against this for a day now, and I really have no idea what's going on.
I have a very simple setup: an SKNode (let's call it base) which contains another SKNode (sub-base), as well as several SKShapeNode objects. At some time, I move one of the SKShapeNode objects (using removeFromParent) from the base node to the sub-base node. Then I apply an SKAction, which moves the node to some arbitrary position.
Except, that SKAction does not work when the SKShapeNode has been removed and added to the sub-base object. If I remove it from the sub-base, and put it back in the base, SKActions once again work.
I am completely stumped. Is there some property that gets set on a node when it's added to another node, which isn't getting properly reset when I remove it...? I can't imagine this is something that should be happening.
Any ideas would be so very welcome.
Update:
Here's some code that I can produce it with. This function is inside a subclass of SKNode. The class adds a bunch of SKShapeNodes, and it also has this other SKNode called testNode, so, without further ado:
-(void) removeThenAdd
{
[someNode removeFromParent];
[self.testNode addChild:someNode];
SKAction* action = [SKAction moveTo:CGPointMake(200, 200) duration:1];
SKNode* thatSameNodeJustAdded = [self.testNode.children objectAtIndex:0];
[thatSameNodeJustAdded runAction:action];
}
Another update!
I just found that, if I add an SKAction to the node whilst it is sitting inside the testNode, then after that remove it from the testNode and add it back to its original parent, the action is then activated. Like, what am I missing here? This must be some kind of designed behaviour I'm just not using right..

This seems to be a bug in the SDK. I had this issue because I wanted to make advanced sprites (sprites with children, emitters, etc) in their own scene files so that I could selectively load and then add them to my scenes. I came up with a work around using NSKeyedArchiver and NSKeyedUnarchiver -- Convert the node (or parent node) to data, and then back again, and the new object is ready to be added to a scene, if it's an emitter, or has child nodes that are emitters they will all be copied through and properly re-added. Here is the extension I made for swift, works like a charm:
extension SKNode {
// Pulling a node from one scene and putting it into another causes some problems with broken emitters :(
// Fix here is to archive and then unarchive the node before returning
class func nodeFromScene(nodeName : String, sceneFileName : String) -> SKNode? {
if let scene = SKScene(fileNamed: sceneFileName), node = scene.childNodeWithName(nodeName) {
let archive = NSKeyedArchiver.archivedDataWithRootObject(node)
return NSKeyedUnarchiver.unarchiveObjectWithData(archive) as? SKNode
}
return nil
}
}

I was also seeing this issue occur in my SpriteKit project. In my case I was removing a node from an SKScene file I'd created in the Scene Editor and adding it to my current scene.
Following some debugging it showed that the actions weren't being run at all. And upon further investigation I discovered why... it seems that when you add a node to the scene in this manner the isPaused property is set to true. Whether this is intentional or a bug I can't find out for sure.
From the docs:
https://developer.apple.com/documentation/spritekit/sknode/1483113-ispaused
isPaused
If the value is true, the node (and all of its descendants) are skipped when a scene processes actions.
So in order to "fix" this issue, before running any SKActions on the node, unpause the node:
node.isPaused = false

Related

Return the owner of a variable - Swift

So I have objects that in turn have sprites associated to them. a snippet of my Object class:
import SpriteKit
class Block {
var sprite : ColourSprite
}
So as you can see, it has a variable that is in fact a SKSprite (ColourSprite is my custom class that inherits from SKSpriteNode).
Now, at some points during the game, these sprites are deleted (i.e sprite.removeFromParent()), but the objects are obviously still somewhere.
I want to be able to send the objects to garbage collection once their sprites are gone. I am thinking that I can do something like sprite.getOwner() but I can't seem to find it. Is this possible?
The only other option I can think of is to manually check all objects and check each one's sprite but I feel that is long and wasteful.
You can check whether the Blocks are still in memory by using Xcode 8.3's new debug panel.
Just after you remove your sprites pause the program and go to that panel. See if there is any Block instances in the left panel. If there is, click on it to check what is retaining it.
If for example your GameScene is retaining the Block, you go to your GameScene and find the property. Then you can just set that to nil after you remove your sprite.

Clearing a SKScene to start a new game

After my game ends I have a button called play again but when it runs it throws and error saying Attemped to add a SKNode which already has a parent. My question is can I reset or dealloc the SKScene so that it is like a fresh slate, like the app was never run?
Assuming the code is being executed in your scene I think what you want is this..
MyScene *newScene = [[MyScene alloc]initWithSize:self.size];
[self.view presentScene:newScene];
Where MyScene is SKScene subclass.
Hopefully that is what you were looking for.
You can use [self removeAllChildren]; in your SKScene to remove all children nodes.
Other objects like arrays, strings, etc... you will have to deal with on a one to one basis.
You can check if a node already has a parent before adding by doing this:
if(myNode.parent == nil)
[self addChild:myNode];

How to cancel a SpriteKit Animation without leaving an (unwanted) remaining texture on the screen

I've created this block of code to create an animation to make a selection visible. called in touchesBegan.
private func setSelectionAnim(atPoint point:CGPoint) {
selectAnim.removeActionForKey(ANIM_SELECT)
let action = SKAction.repeatActionForever( SKAction.animateWithTextures(WBTextures.Select(), timePerFrame: 0.33))
selectAnim.position = point
selectAnim.runAction(action, withKey: ANIM_SELECT)
}
WBTextures.Select() is an instance of a TexturePacker generated class and selecAnim is a SKNode() instantiated and added as a child to the GameScene in the init.
But if I cancel the the animation (when a target is selected ) with the following code:
private func stopSelectionAnim()
{
selectAnim.removeActionForKey(ANIM_SELECT)
}
The Problem is: that when i call stopSelectAnim() the last presented texture stays visible on screen. which after searching the SpriteKit Programming Guide
Canceling Running Actions To cancel actions that a node is running,
call its removeAllActions method. All actions are removed from the
node immediately. If a removed action had a duration, any changes it
already made to the node remain intact, but further changes are not
executed.
would be expected. After that a added the line selectAnim.removeFromParent() now the artifact disappears. But now the problem occurs that the next time setSelectionAnim(atPoint point:CGPoint) is called nothing happens. Logic if i deleted the node, but don't get a run time error ,so 'selectAnim' is not nil. Why does adding the extra line, solve the problem and introduces the next.
what is the best practice to interrupt an animation and still be able to enable it.
I've come up with a couple of ideas which none of I really like and fit into my scene setup.
re-instantiating and addChild' theselecAnimin the beginning of each call tosetSelectionAnim(atPoint point:CGPoint)`
create an action sequence with a runBlock() which checks a Bool whether to stop and if so how do I stop the animation with in a block
adding the animation to a new SKNode and add this as a child to the selecAnim node. ( I've tried this one but the animation did not show at all see code:
selectAnim.removeActionForKey(ANIM_SELECT)
let node = SKNode()
let action = SKAction.repeatActionForever( SKAction.animateWithTextures(WBTextures.Select(), timePerFrame: 0.33))
node.position = point
node.runAction(action, withKey: ANIM_SELECT)
selecAnim.addChild(node)
Do I miss the trivial solution or witch one is the best or do one of you please have a better solution.

Calling method when a node is removed from an SKScene

Is there a way to hook into the SKNode lifecycle in Sprite Kit? Specifically I would like to perform some code when the node gets removed from the scene.
The use case I would like to solve in a bit more detail :
I have some nodes that interact with each other, and I would like them to be notified of certain events that happen to the other nodes. For example, imagine a game where you can tap a node on the scene, and the node's details would appear on a HUD. I would like the HUD to disappear when the node gets removed from the scene.
I plan to use NSNotificationCenter as the notification engine.
Whenever a node gets removed from the scene I would like to post a notification. The easiest way would be to tie into a lifecycle method on SKNode (my nodes are subclasses of SKSpriteNode) like nodeWasRemovedFromParent, but I didn't find any such method.
How can this be done?
I put some thought into coding my own solution by overriding the removeFromParent method in my SKSpriteNode subclass, and posting a notification before calling the super implementation. I am not sure that the removeFromParent method will always be called though. For example, does it get called when I change scenes?
Thanks.
You need to subclass each node class. Override the removeFromParent method as you said. Use only the subclassed versions, otherwise your code won't take effect.
In addition you will want to override removeAllChildren and removeChildrenInArray: or just never use them.
The removeFromParent method will not be called when the scene changes. Instead, override the scene's willMoveFromView: method and send a message to registered observers or simply all child nodes recursively. Use the scene's enumeration function to do so. Note that I'm not 100% sure whether on willMoveFromView the scene's children are still attached, I assume they will.
Unfortunately it's impossible to just subclass SKNode and then expect the subclass' code to work for all other node classes, because those subclass from SKNode directly and not your custom SKNode subclass. Hence you need to subclass and add this code to every SK*Node subclass as well if you need it to be notified on removal.
See KoboldKit node classes for an example, which uses a macro to inject this "override" code into SK*Node subclasses to avoid duplicating the code. The actual functionality is in KKNodeShared: https://github.com/KoboldKit/KoboldKit/tree/master/KoboldKit/KoboldKitFree/Framework/Nodes/Framework

Cocos2D - iPhone - Sprite won't show when created in an instance method

So in my project, I call an instance method called "-(void)fire" in the class "Survival.m":
-(void)fire {
NSLog(#"Firing");
CCSprite *sprite = [CCSprite spriteWithFile:#"bullet.png"];
sprite.position = player.position;
NSLog(#"%#",NSStringFromCGPoint(player.position));
[self addChild:sprite z:100];
}
When I do this, the sprite doesn't show on the screen.
The method is being called from another layer but since it logs "Firing" every time I tap the button, it's not the problem.
I am using a TMXTiledMap as well if that could cause any problems.
Please help, thanks!
EDIT---------
I can create sprites in the other layer, HUDLayer but not in the Survival layer which contains the player and the tiled map. If I create a sprite in the "init" method, it works correctly but If i do it in the method "fire" it doesn't work. The method "fire" is being called from the HUDLayer but I still now the method is being called since I see in the log that it says "Firing"
Could it be that:
1. The sprite is being created out of sight?
2. The sprite is not being created?
3. The sprite is not added to the correct parent?
Any suggestions?
to add a sprite in cocos2d do something like this
CCNode *parent = [self getChildByTag:kTagParentNode];
[parent addChild:sprite];
if that doesnt fix it, make sure the point where you are adding it is on the screen and try changing the z value to see if that helps
EDIT
add this under where you import files
enum {
kTagParentNode = 1,
};
Thanks Everybody, Solved it in some mysterious way. Thanks for the help anyway. I think I declared the layer in a wrong way.
Have a nice day!

Resources