Search code examples
swiftscenekitarkitscnnodescnscene

Adding SCNNode multiple time shows only once


I am trying to add SCNNode mutiple time in a loop at different position but I can see same type of node at once with the last position.

Below is the code

let entityArray:[entity] = [.coin, .coin, .coin, .brick, .coin, .coin, .coin, .brick]

func setupworld() {
    let scene = SCNScene(named: "art.scnassets/MainScene.scn")!

    var zPosition = -10
    var count = 0
    let delta = -4

    for entity in entityArray {

        var node = SCNNode()

        switch entity {
        case .coin:
            node = scene.rootNode.childNode(withName: "coin", recursively: true) ?? node
            node.position = SCNVector3(0, -5, zPosition)

        case .brick:
            node = scene.rootNode.childNode(withName: "brick", recursively: true) ?? node
            node.position = SCNVector3(0, 0, zPosition)
        }

        self.sceneView.scene.rootNode.addChildNode(node)

        zPosition += delta
        count += 1
    }
}

It shows one coin and one brick at last positions.

I am new to scenekit so would be doing something wrong, Please help me.


Solution

  • Building on from the other comments and as @rmaddy has said an SCNNode has a clone() function (which is the approach you should take here) and which simply:

    Creates a copy of the node and its children.

    One thing to be aware of when using this however, is that each cloned Node will share the same geometry and materials.

    That's to say that if you wanted at any point to have some bricks with a red colour and some with a green colour you wouldn't be able to do it with this method since:

    changes to the objects attached to one node will affect other nodes that share the same attachments.

    To achieve this e.g. to render two copies of a node using different materials, you must copy both the node and its geometry before assigning a new material, which you can read more about here: Apple Discussion

    The reason you are only ever seeing one instance of either the coin or brick is because each time you are iterating through your loop you are saying that the newly created node is equal to either the coin or the brick, so naturally the last element in that loop will be the one that references that element from your scene.

    Putting this into practice and solving your issue therefor, your setupWorld function should look like something like this:

    /// Sets Up The Coins & Bricks
    func setupworld(){
    
        //1. Get Our SCNScene
        guard let scene = SCNScene(named: "art.scnassets/MainScene.scn") else { return }
    
        //2. Store The ZPosition
        var zPosition =  -10
    
        //3. Store The Delta
        let delta = -4
    
        //4. Get The SCNNodes We Wish To Clone
        guard let validCoin = scene.rootNode.childNode(withName: "coin", recursively: true),
               let validBrick = scene.rootNode.childNode(withName: "brick", recursively: true) else { return }
    
        //5. Loop Through The Entity Array & Create Our Nodes Dynamically
        var count = 0
    
        for entity in entityArray {
    
            var node = SCNNode()
    
            switch entity{
    
            case .coin:
                //Clone The Coin Node
                node = validCoin.clone()
                node.position = SCNVector3(0, -5, zPosition)
    
            case .brick:
                //Clone The Brick Node
                node = validBrick.clone()
                node.position = SCNVector3(0, 0, zPosition)
            }
    
            //6. Add It To The Scene
            self.sceneView.scene.rootNode.addChildNode(node)
    
            //7. Adjust The zPosition
            zPosition += delta
            count += 1
        }
    
    }