Search code examples
swiftscenekitswift-playground

How do I address the “There was a problem running this page” message in Swift Playgrounds?


I am trying to make something with SceneKit for the WWDC Student Challenge, which requires my project be in a Swift playground. I keep getting the message that says “There was a problem running this page.” It offers no error messages. It just suggests that check my code or start over. I have tried removing pieces of code individually, but I cannot locate the source of this issue. I have also tried running it in man Xcode playground, which offered the warning of, and I quote, “The playground could not continue running because the playground source did”. I am stuck. What is wrong with my code.

import UIKit
import SceneKit
import QuartzCore 
import PlaygroundSupport

class GameScene: UIViewController, SCNSceneRendererDelegate {
    var primaryView: SCNView!
    var primaryScene: SCNScene!
    var cameraNode: SCNNode!
    
    override func viewDidLoad() {
        sceneAndViewInit()
        cameraInit()
        createGround()
        moonInit()
    }
    
    func createGround() {
        var ground = SCNBox(width: 200, height: 1, length: 200, chamferRadius: 0)
        var groundPhysicsShape = SCNPhysicsShape(geometry: ground, options: nil) 
        var groundNode = SCNNode(geometry: ground)
        ground.firstMaterial?.diffuse.contents = UIColor.orange
        ground.firstMaterial?.specular.contents = UIColor.white
        groundNode.position = SCNVector3(0, -6, 0)
        groundNode.physicsBody = SCNPhysicsBody(type: .static, shape: groundPhysicsShape)
        primaryScene.rootNode.addChildNode(groundNode)
    }
    
    func sceneAndViewInit() {
        primaryView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
        primaryView.allowsCameraControl = true
        primaryView.autoenablesDefaultLighting = true
        
        primaryScene = SCNScene()
        
        primaryView.scene = primaryScene
        primaryView.isPlaying = true
    }
    
    func cameraInit() {
        var cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
        cameraNode.camera?.fieldOfView = 65
        cameraNode.camera?.wantsHDR = false
        primaryScene.rootNode.addChildNode(cameraNode)
    }
    
    func moonInit() {
        let moonScene = SCNScene(named: "Moon.scn")
        var moonNode = moonScene?.rootNode.childNode(withName: "Sphere", recursively: true)
        var moonPhysicsShape = SCNPhysicsShape(node: moonNode!, options: nil)
        moonNode?.position = SCNVector3(0, 0, 0)
        moonNode?.scale = SCNVector3(1, 1, 1)
        moonNode?.name = "Moon"
        moonNode?.physicsBody = SCNPhysicsBody(type: .static, shape: moonPhysicsShape)
        primaryScene.rootNode.addChildNode(moonNode!)
    }
    
    func renderer(_ aRenderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        print(time)
    }
}

let gameScene = GameScene()
PlaygroundPage.current.liveView = gameScene

(“Moon.scn” is a file that I have in the resources/files section along with its texture, and I still have the issue without any of the moon related code.)


Solution

  • I have found a way to address this issue. However, I can only offer you a round-ish idea of what the issue is and how to fix it.
    The problem seems to be with the variable declarations at the top of the class.

    var primaryView: SCNView!
    var primaryScene: SCNScene!
    var cameraNode: SCNNode!
    

    The exclamation mark following the type indicates that the variable is an implicitly-unwrapped optional (I am quoting from this Stack Overflow response that explains what this is in more depth), but more-or-less the variable will automatically force unwrap whenever it is used. I have a more of a working knowledge of Swift than a technical one, so my guess is that when a GameScene object is created a synthetic initializer tries to set up these variables, it, for some reason, needs to read from the variables, gets nil, and cannot continue.

    The solution appears to be putting the values of the variables in their initial declaration (There might be a better solution, but this at least works in my testing.), so that they are not nil for the initializer. Now the reason that I cannot fully explain the issue here is that this only seems to be necessary for the SCNScene variable. I don't really know why this is the case.

    Code (*** mark important comments):

    import UIKit
    import SceneKit
    import QuartzCore 
    import PlaygroundSupport
    
    class GameScene: UIViewController, SCNSceneRendererDelegate {
        var primaryView: SCNView!// = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300)) //***This seems to work for reasons beyond me with the implicitly-unwrapped optional***
        var primaryScene = SCNScene() //***The implicitly unwrapped optional is removed here***
        var cameraNode = SCNNode() //***The implicitly unwrapped optional is removed here (I am not sure if that is necessary but it probably good practice not to use it anywhere intros code.  I use it earlier to demonstrate its weird complexities.)***
        
        override func viewDidLoad() {
            sceneAndViewInit()
            cameraInit()
            createGround()
            //moonInit() ***I removed this for simplicity in the answer***
        }
        
        func createGround() {
            var ground = SCNBox(width: 200, height: 1, length: 200, chamferRadius: 0)
            var groundPhysicsShape = SCNPhysicsShape(geometry: ground, options: nil) 
            var groundNode = SCNNode(geometry: ground)
            ground.firstMaterial?.diffuse.contents = UIColor.orange
            ground.firstMaterial?.specular.contents = UIColor.white
            groundNode.position = SCNVector3(0, -6, 0)
            groundNode.physicsBody = SCNPhysicsBody(type: .static, shape: groundPhysicsShape)
            primaryScene.rootNode.addChildNode(groundNode)
        }
        
        func sceneAndViewInit() {
            primaryView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 300))
            primaryView.allowsCameraControl = true
            primaryView.autoenablesDefaultLighting = true
            
            //primaryScene = SCNScene() ***This is not necessary when it is declared above***
            
            self.primaryView.scene = primaryScene
            //primaryView.isPlaying = true
            
            self.view = primaryView //***I realized later you need this bit of code or the program will only display a blank screen***
        }
        
        func cameraInit() {
            //var cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
            cameraNode.camera?.fieldOfView = 65
            cameraNode.camera?.wantsHDR = false
            primaryScene.rootNode.addChildNode(cameraNode)
        }
        
        func moonInit() {
            let moonScene = SCNScene(named: "Moon.scn")
            var moonNode = moonScene?.rootNode.childNode(withName: "Sphere", recursively: true)
            var moonPhysicsShape = SCNPhysicsShape(node: moonNode!, options: nil)
            moonNode?.position = SCNVector3(0, 0, 0)
            moonNode?.scale = SCNVector3(1, 1, 1)
            moonNode?.name = "Moon"
            moonNode?.physicsBody = SCNPhysicsBody(type: .static, shape: moonPhysicsShape)
            primaryScene.rootNode.addChildNode(moonNode!)
        }
        
        func renderer(_ aRenderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
            print(time)
        }
    }
    
    let gameScene = GameScene()
    PlaygroundPage.current.liveView = gameScene
    

    One final note: The self.view = primaryView code is really important. I make a note about in the full code, but I have found this way too hard to locate on the internet. I think this has to do with UIViewController (I don't know much about UIKit at all.), but either SceneKit or UIKit just displays a blank screen if you do now have that bit of code. If you don't figure out that these are separate issues they are really hard to decipher.