Search code examples
iosswiftuiscenekit

How to use "*.scnp" file in SwiftUI for button click (iOS)?


I have "Explode.scnp" SceneKit file. It's already configured, the texture has been set.

How we can use it in SwiftUI? For example, after the button clicks the background will be animated once.

var body: some View {
    ZStack {
            VStack {
                Button(action: {
                  Particles()
                }) { 
                  Text("Animate")
                }
            }
     }
}

This code works for scn. but how to use it with scnp?

import SwiftUI
import SceneKit

struct ScenekitView : UIViewRepresentable {
    let scene = SCNScene(named: "art.scnassets/ship.scn")!

    func makeUIView(context: Context) -> SCNView {
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)

        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)

        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)

        // retrieve the ship node
        let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!

        // animate the 3d object
        ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

        // retrieve the SCNView
        let scnView = SCNView()
        return scnView
    }

    func updateUIView(_ scnView: SCNView, context: Context) {
        scnView.scene = scene

        // allows the user to manipulate the camera
        scnView.allowsCameraControl = true

        // show statistics such as fps and timing information
        scnView.showsStatistics = true

        // configure the view
        scnView.backgroundColor = UIColor.black
    }
}

#if DEBUG
struct ScenekitView_Previews : PreviewProvider {
    static var previews: some View {
        ScenekitView()
    }
}
#endif

Moreover, Xcode version is 11.3.1. When I try to create a new file I have this: enter image description here

And the extension is SKS... any ideas?

Reworked the code according to @Asperi version:

import SwiftUI
import SceneKit

struct ScenekitView : UIViewRepresentable {
    @Binding var exploding: Bool
    //let scene = SCNScene(named: "SceneKit.scnassets/Explode.scnp")!
    let scene = SCNScene(named: "SceneKit.scnassets/scene.scn")!

    func makeUIView(context: Context) -> SCNView {
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)

        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)

        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)

        // retrieve the ship node
        let ship = scene.rootNode.childNode(withName: "blow", recursively: true)!

        // animate the 3d object
        ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

        // retrieve the SCNView
        let scnView = SCNView()
        return scnView
    }

    func updateUIView(_ scnView: SCNView, context: Context) {
        scnView.scene = scene

        // allows the user to manipulate the camera
        scnView.allowsCameraControl = true

        // show statistics such as fps and timing information
        scnView.showsStatistics = true

        // configure the view
        scnView.backgroundColor = UIColor.black

        if exploding {
            if let scene = scene.rootNode.childNode(withName: "SceneKit.scnassets/scene", recursively: false),
                let particles = SCNParticleSystem(named: "SceneKit.scnassets/Explode", inDirectory: nil) {

                let node = SCNNode()
                node.addParticleSystem(particles)
                node.position = scene.position
                scnView.scene?.rootNode.addChildNode(node)
                scene.removeFromParentNode()
            }
        }
    }
}

Errors related to the path names ...

// retrieve the ship node

here. Tried .sks, .scnp ... any ideas?


Solution

  • Here is modified code with demo. Tested with Xcode 11.4 / macOS 10.15.4

    demo

    Note: as I don't know your project structure, all dependent resource files were added as Resources (not in Assets). Just in case.

    resources

    struct DemoSceneKitParticles: View {
        @State private var exploding = false
        var body: some View {
            VStack {
                ScenekitView(exploding: $exploding)
                Button("BOOM") { self.exploding = true }
            }
        }
    }
    
    struct ScenekitView : NSViewRepresentable {
        @Binding var exploding: Bool
        let scene = SCNScene(named: "ship.scn")!
    
        func makeNSView(context: NSViewRepresentableContext<ScenekitView>) -> SCNView {
            // create and add a camera to the scene
            let cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            scene.rootNode.addChildNode(cameraNode)
    
            // place the camera
            cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
    
            // create and add a light to the scene
            let lightNode = SCNNode()
            lightNode.light = SCNLight()
            lightNode.light!.type = .omni
            lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
            scene.rootNode.addChildNode(lightNode)
    
            // create and add an ambient light to the scene
            let ambientLightNode = SCNNode()
            ambientLightNode.light = SCNLight()
            ambientLightNode.light!.type = .ambient
            ambientLightNode.light!.color = NSColor.darkGray
            scene.rootNode.addChildNode(ambientLightNode)
    
            // retrieve the ship node
            let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
    
            // animate the 3d object
            ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
    
            // retrieve the SCNView
            let scnView = SCNView()
            return scnView
        }
    
        func updateNSView(_ scnView: SCNView, context: Context) {
            scnView.scene = scene
    
            // allows the user to manipulate the camera
            scnView.allowsCameraControl = true
    
            // show statistics such as fps and timing information
            scnView.showsStatistics = true
    
            // configure the view
            scnView.backgroundColor = NSColor.black
    
            if exploding {
                if let ship = scene.rootNode.childNode(withName: "ship", recursively: true),
                    let particles = SCNParticleSystem(named: "Explosion", inDirectory: nil) {
    
                    let node = SCNNode()
                    node.addParticleSystem(particles)
                    node.position = ship.position
                    scnView.scene?.rootNode.addChildNode(node)
                    ship.removeFromParentNode()
                }
            }
        }
    }
    

    Update: variant for iOS

    Tested with Xcode 11.4 / iOS 13.4

    demo3

    Full module code (resource files as before at top level)

    import SwiftUI
    import SceneKit
    
    struct DemoSceneKitParticles: View {
        @State private var exploding = false
        var body: some View {
            VStack {
                ScenekitView(exploding: $exploding)
                Button("BOOM") { self.exploding = true }
            }
        }
    }
    
    struct ScenekitView : UIViewRepresentable {
        @Binding var exploding: Bool
        let scene = SCNScene(named: "ship.scn")!
    
        func makeUIView(context: Context) -> SCNView {
            // create and add a camera to the scene
            let cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            scene.rootNode.addChildNode(cameraNode)
    
            // place the camera
            cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
    
            // create and add a light to the scene
            let lightNode = SCNNode()
            lightNode.light = SCNLight()
            lightNode.light!.type = .omni
            lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
            scene.rootNode.addChildNode(lightNode)
    
            // create and add an ambient light to the scene
            let ambientLightNode = SCNNode()
            ambientLightNode.light = SCNLight()
            ambientLightNode.light!.type = .ambient
            ambientLightNode.light!.color = UIColor.darkGray
            scene.rootNode.addChildNode(ambientLightNode)
    
            // retrieve the ship node
            let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
    
            // animate the 3d object
            ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
    
            // retrieve the SCNView
            let scnView = SCNView()
            return scnView
        }
    
        func updateUIView(_ scnView: SCNView, context: Context) {
            scnView.scene = scene
    
            // allows the user to manipulate the camera
            scnView.allowsCameraControl = true
    
            // show statistics such as fps and timing information
            scnView.showsStatistics = true
    
            // configure the view
            scnView.backgroundColor = UIColor.black
    
            if exploding {
                if let ship = scene.rootNode.childNode(withName: "ship", recursively: true),
                    let particles = SCNParticleSystem(named: "Explosion", inDirectory: nil) {
    
                    let node = SCNNode()
                    node.addParticleSystem(particles)
                    node.position = ship.position
                    scnView.scene?.rootNode.addChildNode(node)
                    ship.removeFromParentNode()
                }
            }
        }
    }
    
    struct DemeSKParticles_Previews: PreviewProvider {
        static var previews: some View {
            DemoSceneKitParticles()
        }
    }