jendan
jendan

Reputation: 281

Add SKReferenceNode programmatically

I have long 2d level so I split it into more .sks files. I have "GameScene" where I can join them in Scene editor with drag and drop. It creates SKReferenceNodes. I've done that with success. But I would like to load these parts lazily when it's needed in code (it's said to be good practice). I'm failing on that.

This is particular part of code:

if let nextPart = SKReferenceNode(fileNamed: "Scene2_city2") {
        if self.childNodeWithName("Scene2_city2") == nil {
            self.addChild(nextPart)
        }
    }

"Scene2_city2" is name of .sks file and also name of Scene inside the file.

Running the code I get an error:

*** Terminating app due to uncaught exception 'Cant add body, already exists in a world', reason: 'Cant add body type: representedObject:[ name:'Scene2_city2' frame:{{-0, -0}, {1024, 768}} anchor:{0, 0}], already exists in a world'

This is very strange, because I first check it before I added.

Question: How should I add SKReferenceNode into SKScene programmatically?

EDIT: Here is simple example project on bitbucket. Reference scene is added for tap.

Upvotes: 5

Views: 2121

Answers (4)

gionti
gionti

Reputation: 96

I found another workaround that doesn't force you to set the physicsBody to nil.

In my case, I substituted the SKReferenceNode with an SKScene.

I first made sure that, in my .sks scene, all SKSpriteNodes were children of one main node. It doesn't have to be a direct link, you can still respect the hierarchies of your scene, but it's important that at the end of the "hierarchy tree" they are linked to one node, which in my case is the background, but it can also be an empty node.

enter image description here

In my GameScene file I then wrote the following code:

class GameScene: SKScene {
    var mainNode: SKNode? = nil
    override func didMove(to view: SKView) {
        var mySksScene = SKScene(fileNamed: "Caribbeans")
        mainNode = upperBackground?.childNode(withName: "backgroundCaribbean")
        mainNode?.removeFromParent()
        mySksScene = nil
        addChild(mainNode)
    }
}

I first declared an optional SKNode variable. Then, inside didMove(:to:), I loaded my .sks file in an optional SKScene() variable. I then set SKNode as the main node, the node which is the parent of all the other nodes (in my case: backgroundCaribbean). I then removed my mainNode from its parent scene, THEN set to nil the variable that used to contain the .sks scene, and only THEN I added my mainNode as a Child of my GameScene.

Upvotes: 0

cocojoe
cocojoe

Reputation: 220

I've found a workaround, it turns out there are no issues when creating a SKReferenceNode using the URL method. For example:

let path = NSBundle.mainBundle().pathForResource("SpaceShip", ofType: "sks")
let spaceShip = SKReferenceNode (URL: NSURL (fileURLWithPath: path!))
addChild(spaceShip)

I did a little digging and it appears in 7.3.1 vs 7.2.1 when creating a SKReferenceNode using fileNamed it now returns an optional. When unwrapped you get the SKScene which will of course throw out errors if you try and add it as a child to an existing scene.

Added a quick 7.3 test project https://github.com/cocojoe/SKReferenceNodeIssue/tree/workaround

Notice you referenced my post on the Apple forum. I filled a bug report about a month ago, from past experience these can take a while to be looked at.

Upvotes: 9

Mitchell Hudson
Mitchell Hudson

Reputation: 1113

I'm having the same problem. If I use SKReferenceNode(fileNamed:) to create a new sprite from a sprite that exists in a scene.sks file I get the error

'Cant add body, already exists in a world'

This seems to be related to physics, and occurs when the object is added to to the scene with addChild. The error goes away if you set physics body to nil. Something like:

let newBox = SKReferenceNode(fileNamed: "Box")
newBox?.physicsBody = nil
newBox?.position = location
addChild(newBox!)

What's interesting is that the error appears even when the physics option is set to none. The solution above works in this case but you lose your physics body.

What seemed to work for me was creating a object to use as a source object, and use skNode.copy() to create a copy of it.

Upvotes: 1

Whirlwind
Whirlwind

Reputation: 13665

You have checked if the node is in scene's hierarchy. This error is related to the physics world and it has nothing to do with the fact that a node is already added to the scene.

Personally I couldn't reproduce what you are saying using SKReferenceNode, but that is probably because I am not fully aware of your current setup.

This error can happen if two nodes point to the same SKPhysicsBody instance, like this:

override func didMoveToView(view: SKView) {

        let sprite = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 20, height: 20))
        let sprite2 = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 20, height: 20))

        sprite.position =  CGPoint(x:frame.midX, y:frame.midY)
        sprite2.position = CGPoint(x:frame.midX, y:frame.midY + 100)

        let sharedPhysicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: -10, height: -10))

        sprite.physicsBody = sharedPhysicsBody
        sprite2.physicsBody = sharedPhysicsBody

        addChild(sprite)
        addChild(sprite2)
    }

This code will crash with the error you are getting currently. So check if you are re-using the same SKPhysicsBody somewhere or something like that and you will solve this.

Upvotes: 1

Related Questions