Swift SpriteKit presentScene error - swift

I am trying to make a asteroids type game, and every time the asteroid collides with the player the end scene loads fine, cut when I try to restart its like the code is stuck looping the collision function that called the end scene. Here is the collision function
func didBeginContact(contact: SKPhysicsContact) {
print("5")
//Creating a variable that holds which two items collide
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
//Creating a switch case statement
//This will run a different case depending on whitch two objects collide
switch(contactMask) {
//If the satellite and the asteroid collide do this
case bitMask.satellite.rawValue | bitMask.asteroid.rawValue:
//removing the satellite
let secondNode = contact.bodyB.node
secondNode?.physicsBody?.allowsRotation = true
let firstNode = contact.bodyA.node
firstNode?.removeFromParent()
//Stopping the mission timer
removeActionForKey("missionDurationTime")
self.removeAllChildren()
print("6")
self.view?.presentScene(EndScene())
print("7")
default:
return
}
}
And here is the function that goes back to my game scene from my end scene
func Restart(){
print("hello")
RestartBtn.removeFromSuperview()
speach.removeFromSuperview()
score.removeFromSuperview()
gameOver.removeFromSuperview()
self.view?.presentScene(GameScene())
}
After I hit the restart button all of the labels in the EndScene go away like they should but then the screen is blank and the console just prints `5
6
7
Too many times to count until the labels in the end scene come back. I am very confused and in way over my head. Any help is much appreciated!
UPDATE:
I changed the code in my restart button to this
print("hello")
RestartBtn.removeFromSuperview()
speach.removeFromSuperview()
score.removeFromSuperview()
gameOver.removeFromSuperview()
let scene = GameScene(size: self.view!.bounds.size)
scene.backgroundColor = SKColor(red: 1, green: 1, blue: 1, alpha: 1)
scene.scaleMode = .AspectFill
scene.backgroundColor = UIColor.whiteColor()
let transition = SKTransition.pushWithDirection(SKTransitionDirection.Left, duration:0.5)
self.scene?.view?.presentScene(scene, transition: transition)
Now the game scene loads and everything is as it should be, except for the fact that everything is five times bigger. This is what it should look like:
What it should look like
And this is what it looks like
What it does look like

In you restart code you are initialising the GameScene with a size of view.bounds.
If you have not changed your GameViewController code than by default you are using the GameScene.sks file as reference (visual level editor) with a default scene size of 1024*768 and scale mode .AspectFill.
In short in your restart function try this
if let scene = GameScene(fileNamed: "GameScene") { // fileNamed is the GameScene.sks file
scene.backgroundColor = SKColor(red: 1, green: 1, blue: 1, alpha: 1)
....
}
or alternatively
let scene = GameScene(size: CGSize(width: 1024, height: 768)
scene.backgroundColor ....

Related

Part of my animation is not working in SpriteKit

init () {
super.init(texture: nil, color: .clear, size: initialSize)
// Create physicsBody based on a frame of the sprite
// basically giving it gravity and physics components
let bodyTexture = textureAtlas.textureNamed("MainCharacterFlying1")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
// Lose momentum quickly with high linear dampening
self.physicsBody?.linearDamping = 0.9
// weighs around 30 kg
self.physicsBody?.mass = 30
// no rotation
self.physicsBody?.allowsRotation = false
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
}
// Animations to make the main character seem like it is flying
func createAnimations() {
let rotateUpAction = SKAction.rotate(byAngle: 0, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(byAngle: -1, duration: 0.8)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying1"), textureAtlas.textureNamed("MainCharacterFlying2"),textureAtlas.textureNamed("MainCharacterFlying3"), textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying5"),textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying3"),textureAtlas.textureNamed("MainCharacterFlying2")]
var flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.1)
flyAction = SKAction.repeatForever(flyAction)
flyAnimation = SKAction.group([flyAction,rotateUpAction])
let soarFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying5")]
var soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAction = SKAction.repeatForever(soarAction)
let soarAnimation = SKAction.group([soarAction,rotateDownAction])
}
When I run this code on the IOS Simulator, I have to click on the screen once in order for my main sprite to show up on the screen, or else it will not. And when I click on the sprite, the sprite will start flapping its wings and go up (I have other code for that) however, the rotateUpAction and rotateDownAction are not showing up at all on the Simulator. So I was wondering if there were any solutions and anyone willing to answer.
Thank you for your time. Also, this code from the class of the main character, the name of the class is "Player"
you declare let soarAnimation inside your createAnimations function, meaning it's not in scope when you call self.run(soarAnimation). Solution: declare soarAnimation as a class property. Alternate solution: have createAnimations() return the SKAction and grab it in init that way

ARKit – How to display the feed from a virtual SCNCamera placed on SCNPlane?

I put some objects in AR space using ARKit and SceneKit. That works well. Now I'd like to add an additional camera (SCNCamera) that is placed elsewhere in the scene attached and positioned by a common SCNNode. It is oriented to show me the current scene from an other (fixed) perspective.
Now I'd like to show this additional SCNCamera feed on i.Ex. a SCNPlane (as the diffuse first material) - Like a TV screen. Of course I am aware that it will only display the SceneKit content which stays in the camera focus and not rest of the ARKit image (which is only possible by the main camera of course). A simple colored background then would be fine.
I have seen tutorials that describes, how to play a video file on a virtual display in ARSpace, but I need a realtime camera feed from my own current scene.
I defined this objects:
let camera = SCNCamera()
let cameraNode = SCNNode()
Then in viewDidLoad I do this:
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 0
camera.zFar = 100
cameraNode.camera = camera
sceneView.scene.rootNode.addChildNode(cameraNode)
Then I call my setup function to place the virtual Display next to all my AR stuff, position the cameraNode as well (pointing in the direction where objects stay in the scene)
cameraNode.position = SCNVector3(initialStartPosition.x, initialStartPosition.y + 0.5, initialStartPosition.z)
let cameraPlane = SCNNode(geometry: SCNPlane(width: 0.5, height: 0.3))
cameraPlane.geometry?.firstMaterial?.diffuse.contents = cameraNode.camera
cameraPlane.position = SCNVector3(initialStartPosition.x - 1.0, initialStartPosition.y + 0.5, initialStartPosition.z)
sceneView.scene.rootNode.addChildNode(cameraPlane)
Everything compiles and loads... The display shows up at the given position, but it stays entirely gray. Nothing is displayed at all from the SCNCamera I put in the scene. Everything else in the AR scene works well, I just don't get any feed from that camera.
Hay anyone an approach to get this scenario working?
To even better visualize, I add some more print screens.
The following shows the Image trough the SCNCamera according to ARGeo's input. But it takes the whole screen, instead of displaying its contents on a SCNPlane, like I need.
The next Print screen actually shows the current ARView result as I got it using my posted code. As you can see, the gray Display-Plane remains gray - it shows nothing.
The last print screen is a photomontage, showing the expected result, as I'd like to get.
How could this be realized? Am I missing something fundamental here?
After some research and sleep, I came to the following, working solution (including some inexplainable obstacles):
Currently, the additional SCNCamera feed is not linked to a SCNMaterial on a SCNPlane, as it was the initial idea, but I will use an additional SCNView (for the moment)
In the definitions I add an other view like so:
let overlayView = SCNView() // (also tested with ARSCNView(), no difference)
let camera = SCNCamera()
let cameraNode = SCNNode()
then, in viewDidLoad, I setup the stuff like so...
camera.automaticallyAdjustsZRange = true
camera.usesOrthographicProjection = false
cameraNode.camera = camera
cameraNode.camera?.focalLength = 50
sceneView.scene.rootNode.addChildNode(cameraNode) // add the node to the default scene
overlayView.scene = scene // the same scene as sceneView
overlayView.allowsCameraControl = false
overlayView.isUserInteractionEnabled = false
overlayView.pointOfView = cameraNode // this links the new SCNView to the created SCNCamera
self.view.addSubview(overlayView) // don't forget to add as subview
// Size and place the view on the bottom
overlayView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width * 0.8, height: self.view.bounds.height * 0.25)
overlayView.center = CGPoint(x: self.view.bounds.width * 0.5, y: self.view.bounds.height - 175)
then, in some other function, I place the node containing the SCNCamera to my desired position and angle.
// (exemplary)
cameraNode.position = initialStartPosition + SCNVector3(x: -0.5, y: 0.5, z: -(Float(shiftCurrentDistance * 2.0 - 2.0)))
cameraNode.eulerAngles = SCNVector3(-15.0.degreesToRadians, -15.0.degreesToRadians, 0.0)
The result, is a kind of window (the new SCNView) at the bottom of the screen, displaying the same SceneKit content as in the main sceneView, viewed trough the perspective of the SCNCamera plus its node position, and that very nicely.
In a common iOS/Swift/ARKit project, this construct generates some side effects, that one may struggle into.
1) Mainly, the new SCNView shows SceneKit content from the desired perspective, but the background is always the actual physical camera feed. I could not figure out, how to make the background a static color, by still displaying all the SceneKit content. Changing the new scene's background property affects also the whole main scene, what is actually NOT desired.
2) It might sound confusing, but as soon as the following code get's included (which is essential to make it work):
overlayView.scene = scene
the animation speed of the entire scenes (both) DOUBLES! (Why?)
I got this corrected by adding/changing the following property, which restores the animation speed behavour almost like normal (default):
// add or change this in the scene setup
scene.physicsWorld.speed = 0.5
3) If there are actions like SCNAction.playAudio in the project, all the effects will no longer play - as long as I don't do this:
overlayView.scene = nil
Of course, the additional SCNView stops working but everything else gets gets back to its normal.
Use this code (as a starting point) to find out how to setup a virtual camera.
Just create a default ARKit project in Xcode and copy-paste my code:
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene(named: "art.scnassets/ship.scn")!
sceneView.scene = scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 1)
cameraNode.camera?.focalLength = 70
cameraNode.camera?.categoryBitMask = 1
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.darkGray
let plane = SCNNode(geometry: SCNPlane(width: 0.8, height: 0.45))
plane.position = SCNVector3(0, 0, -1.5)
// ASSIGN A VIDEO STREAM FROM SCENEKIT-RECORDER TO YOUR MATERIAL
plane.geometry?.materials.first?.diffuse.contents = capturedVideoFromSceneKitRecorder
scene.rootNode.addChildNode(plane)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
}
UPDATED:
Here's a SceneKit Recorder App that you can tailor to your needs (you don't need to write a video to disk, just use a CVPixelBuffer stream and assign it as a texture for a diffuse material).
Hope this helps.
I'm a little late to the party, but I've had a similar issue recently.
As far as I can tell, you cannot directly connect a camera to a node's material. You can, however, use a scene's layer as a texture for a node.
The code below is not verified, but should be more or less ok:
class MyViewController: UIViewController {
override func loadView() {
let projectedScene = createProjectedScene()
let receivingScene = createReceivingScene()
let projectionPlane = receivingScene.scene?.rootNode.childNode(withName: "ProjectionPlane", recursively: true)!
// Here's the important part:
// You can't directly connect a camera to a material's diffuse texture.
// But you can connect a scene's layer as a texture.
projectionPlane.geometry?.firstMaterial?.diffuse.contents = projectedScene.layer
projectedScene.layer.contentsScale = 1
// Note how we only need to connect the receiving view to the controller.
// The projected view is not directly connected as a subview,
// but updates in projectedScene will still be reflected in receivingScene.
self.view = receivingScene
}
func createProjectedScene() -> SCNView {
let view = SCNView()
// ... set up scene ...
return view
}
func createReceivingScene() -> SCNView {
let view = SCNView()
// ... set up scene ...
let projectionPlane = SCNNode(geometry: SCNPlane(width: 2, height: 2)
projectionPlane.name = "ProjectionPlane"
view.scene.rootNode.addChildNode(projectionPlane)
return view
}
}

Swift 3 (SpriteKit): Reseting the GameScene after the game ends

I am wanting to 'reset' and 'restart' the GameScene so it is as if the GameScene was first called. I have looked at different methods for doing this, but each time I get a warning that I'm trying to add a node to a parent which already has a parent. However, in my code I delete all my existing nodes so I'm really confused as to how to reset the GameScene. This is how I do it now (this code is called when I want to restart the GameScene from scratch and it is called within the GameScene class):
let scene = GameScene(size: self.size)
scene.scaleMode = .aspectFill
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(scene, transition: animation)
self.removeAllChildren()
self.removeAllActions()
self.scene?.removeFromParent()
1.Edited: I realised that why I was getting this warning: "I'm trying to add a node to a parent which already has a parent" was because I had all the variables for the scene outside of the class and as global variables. However, now when the game restarts, the game is in the bottom left corner. Why is this the case and how do I fix this? - FIXED
2.Edited: Everything works fine now, but now my concern is that deinit{} isn't called even though every node is deleted and the fps doesn't drop over time. Here is what I have in my GameViewController for setting the scene and in my GameScene (every instance relating to the scenes so basically all that is relevant):
import UIKit
import SpriteKit
import GameplayKit
var screenSize = CGSize()
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
screenSize = scene.size
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
Then my GameScene is basically:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
//Declare and initialise variables and enumerations here
deinit{print("GameScene deinited")}
override func didMove(to view: SKView) {
//Setup scene and nodes
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//Do other things depending on when and where you touch
//When I want to reset the GameScene
let newScene = GameScene(size: self.size)
newScene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
Any answers would be greatly appreciated :)
How to reset the scene?
You just have to present a new, same scene again whenever you want. So, you are doing it fine.
Possible leaking problems?
Also, if you don't have leaks in your game, means no strong reference cycles, you don't even need self.removeAllChildren() and self.removeAllActions()... Of course if you explicitly want to stop actions before transition animation starts, the using this method make sense. The point is, when scene deallocates, all objects that depends on it should / will deallocate as well.
Still, if you don't know from the beginning what you are doing and how to prevent from leaks, eg. you are using strong self in block which is a part of an action sequence, which repeats forever, then you certainly have a leak, and self.removeAllActions() might help (in many cases, but it is not an ultimate solution). I would recommend to read about capture lists and ARC in general because it can be useful to know how all that work just because of these situations.
Scene is a root node
Calling removeFromParent() on a scene itself has no effect. Scene is a root node, so it can't be removed in your current context. If you check scene's parent property you will notice that it is nil. Of course it is possible to add a scene to another scene, but in that case, the scene which is added as a child, will act as an ordinary node.
And finally, how to present the new scene ? Easy, like this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let newScene = GameScene(size: self.size)
newScene.scaleMode = self.scaleMode
let animation = SKTransition.fade(withDuration: 1.0)
self.view?.presentScene(newScene, transition: animation)
}
If something doesn't work for you, it is likely that you have leaks (means your scene isn't deallocated). To check this, somewhere in your GameScene override deinit method, like this:
deinit{print("GameScene deinited")}
To explain you this a bit further... What should happen is that you should present a new scene, a transition should occur, an old scene should be deallocated, and you should see a new scene with an initial state.
Also overriding deinit will just tell you if the scene is deallocated properly or not. But it will not tell you why. It is up to you to find what in your code retaining the scene.
There are 2 main ways that I can think of that do this. The main way that I go this is that if the game is over, (due to the character health falling to zero, or they collide with an object that causes the round to be over, or time is up or whatever), when that happens I like to transition to a new scene that is a summary screen of their score, how far they made it etc.
I do this by having a bool variable in the main GamePlay scene like this.
var gameOver: Bool = false
Then in the code that fires off to cause the game to end set that variable = true.
In the update function check to see if gameOver == true and transition to the GameOverScene.
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
if gameOver == true {
print("Game Over!")
let nextScene = GameOverScene(size: self.scene!.size)
nextScene.scaleMode = self.scaleMode
nextScene.backgroundColor = UIColor.black
self.view?.presentScene(nextScene, transition: SKTransition.fade(with: UIColor.black, duration: 1.5))
}
}
The update function will check at each frame render to see if the game is over and if it is found to be over it will perform any actions that you need it to and then present the next scene.
Then on the GameOverScene I put a button saying "Retry" and when they click that it fires off the GamePlayScene again, running the view DidLoad function and setting up the GamePlayScene from scratch the way that it should.
Here is an example of how I handle that. There are a few different ways to call a scene transtion. You can give this one a try if it isn't working quite right.
if node.name == "retryButton" {
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view! as SKView
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .aspectFill
scene.size = skView.bounds.size
skView.presentScene(scene, transition: SKTransition.fade(withDuration: 2.0))
}
}
That is my preferred method of handling the game over transitions.
The other method would be to create a function that resets all of the variables that have changed during the course of playing. Then you could use the same code from the Update function above, but instead of transitioning you could create a label on the scene. If the user clicks the label it would fire off of that function to reset all of the variables that have changed, reset the players locations, stops all actions, resets players health etc. Depending on how many things you have changing during the course of gameplay it'll probably be more practical, (as I've found), to transition to a new scene to give a summary and then reload the GamePlayScene through a button. Then everything will load up just the same as it does the first time that the user entered that main GamePlayScene.

SceneKit SCNSceneRendererDelegate - renderer function not called

I recently asked a question which had a pretty obvious answer. I'm still working on the same project and running into another problem. I need to implement per frame logic and the SCNSceneRendererDelegate protocol worked perfectly fine on iOS, but on OSX, the renderer function is not firing. I have created a little example project to illustrate my problem. It consists of a Scene Kit View in storyboard and following code in the ViewController class:
import Cocoa
import SceneKit
class ViewController: NSViewController, SCNSceneRendererDelegate {
#IBOutlet weak var sceneView: SCNView!
let cubeNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let sphere = SCNSphere(radius: 0.1)
sphere.firstMaterial!.diffuse.contents = NSColor.yellowColor()
let sphereNode = SCNNode(geometry: sphere)
scene.rootNode.addChildNode(sphereNode)
let cube = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
cube.firstMaterial!.diffuse.contents = NSColor.greenColor()
cubeNode.geometry = cube
cubeNode.position = SCNVector3(1,0,0)
scene.rootNode.addChildNode(cubeNode)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(2, 1, 2)
let constraint = SCNLookAtConstraint(target: cubeNode)
cameraNode.constraints = [constraint]
scene.rootNode.addChildNode(cameraNode)
sceneView.scene = scene
sceneView.backgroundColor = NSColor(red: 0.5, green: 0, blue: 0.3, alpha: 1)
sceneView.allowsCameraControl = true
sceneView.delegate = self
sceneView.playing = true
}
func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
cubeNode.position.x += 0.1
}
}
All I want is to basically move the cube with every frame. But nothing happens. What is weird is that when I set sceneView.allowsCameraControl to true, the renderer function is called whenever I click or drag on the screen (which makes sense because it needs to update the view based on camera angles). But I would want it to be called every frame.
Is there an error I don't see or is this a bug in my Xcode?
Edit:
I have tried following the instructions in the answer below and now have the following code for the ViewController:
import Cocoa
import SceneKit
class ViewController: NSViewController {
#IBOutlet weak var sceneView: SCNView!
let scene = MyScene(create: true)
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = scene
sceneView.backgroundColor = NSColor(red: 0.5, green: 0, blue: 0.3, alpha: 1)
sceneView.allowsCameraControl = true
sceneView.delegate = scene
sceneView.playing = true
}
}
And a MyScene class:
import Foundation
import SceneKit
final class MyScene: SCNScene, SCNSceneRendererDelegate {
let cubeNode = SCNNode()
convenience init(create: Bool) {
self.init()
let sphere = SCNSphere(radius: 0.1)
sphere.firstMaterial!.diffuse.contents = NSColor.yellowColor()
let sphereNode = SCNNode(geometry: sphere)
rootNode.addChildNode(sphereNode)
let cube = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
cube.firstMaterial!.diffuse.contents = NSColor.greenColor()
cubeNode.geometry = cube
cubeNode.position = SCNVector3(1,0,0)
rootNode.addChildNode(cubeNode)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(2, 1, 2)
let constraint = SCNLookAtConstraint(target: cubeNode)
cameraNode.constraints = [constraint]
rootNode.addChildNode(cameraNode)
}
#objc func renderer(aRenderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
cubeNode.position.x += 0.01
}
}
However, it is still not working. What am I doing wrong?
Edit: setting sceneView.loops = true fixes the described problem
I don't understand what's causing the problem, but I was able to replicate it. I got it to work, though, by adding a meaningless SCNAction:
let dummyAction = SCNAction.scaleBy(1.0, duration: 1.0)
let repeatAction = SCNAction.repeatActionForever(dummyAction)
cubeNode.runAction(repeatAction)
The render loop fires only if the scene is "playing" (see SKScene becomes unresponsive while being idle). I expect that setting
sceneView.isPlaying = true
(as you're already doing) would be enough to trigger the render callbacks.
The code I have above is not a solution. It's a nasty hack to work around your problem and allow you to get on with life.
For anyone still having problems, setting the delegate and playing variables will work.
sceneView.delegate = self
sceneView.isPlaying = true
I suspect the answer hinges on what Querent means by "every frame". Querent should probably clarify this, but I'll try to answer anyway because I'm like that.
The simplest interpretation is probably "every frame that would render anyway", but that seems unlikely to be what is desired unless the cube is intended as a kind of activity monitor for the renderer, which doesn't seem likely either; there are much better approaches to that.
What Querent may want is to render repeatedly while the view's playing property is YES. If that's the case, then perhaps the answer is as simple as setting the view's loops property to YES. This recently solved a problem for me in which I wanted rendering to occur while a keyboard key was held down. (I had noticed that setting playing to YES would induce a single call to my delegate.)
In addition to several helpful hints in this chain, the final one for me to get delegate called was the following: If you use the pre Swift 4 methods for the SCNSceneRendererDelegate class, it compiles fine with no errors or warnings, but the delegate is never called.
Thus the obsolete pre-Swift 4 definition:
func renderer(aRenderer:SCNSceneRenderer, updateAtTime time:TimeInterval) {...}
(which I got from a snippet on the web) compiled just fine and was never called, while the correct definition
func renderer(_ renderer:SCNSceneRenderer, updateAtTimet time:TimeInterval) {...}
compiles and gets called!
Since SCNSceneRendererDelegate is a protocol, the normal Swift protections afforded by override are not appropriate. Since SCNSceneRendererDelegate defines its methods as optional (which I like), it is not caught that way either.
I've been struggling with some similar bugs using SCNRenderer rather than SCNView, and this was the first hit on Google, so I just wanted to add a note with my solution in case it helps anyone.
I was using
.render(withViewport:commandBuffer:passDescriptor:)
but this does not call the delegate render method. Instead, use
.render(atTime:viewport:commandBuffer:passDescriptor:)
even if you are not using the time interval parameter. Then the delegate method renderer(_:updateAtTime:) will be called properly.
Try this…put your code in a scene class instead – keep the view controller clean.
final class MySCNScene:SCNScene, SCNSceneRendererDelegate
{
#objc func renderer(aRenderer:SCNSceneRenderer, updateAtTime time:NSTimeInterval)
{
}
}
Also set the view's delegate to your scene:
mySCNView!.delegate = mySCNScene

Changing view with SKView?

I want to know how to change to a scene when the character collides with the enemy at game over. I have made a scene under main.storyboard and I want to know how to hook it up through code, I only know how to hook it up using buttons but thats not what I am looking for as you would not press a button when you die to take you to the game over scene.
UPDATE:
func gameOver() {
gameDelegate?.gameDelegateGameOver(score)
let gameOverScene: GameOverScene = GameOverScene(size: self.size)
self.view!.presentScene(gameOverScene, transition: SKTransition.fadeWithDuration(0.0))
Thats what I have for my gameOver when collision is detected. Yes it does take me to a new scene but not the scene I made in main.storyboard.
What you could do is to create a collision boolean and if it's true (hence, something has collided), you can present a new scene with view.presentScene(YOUR_SCENE, SK_ANIMATION) in the update function.
EDIT:
I've found the tutorial from where you got the code (or at least I assume you did) and got it working with the following:
In "didMoveView" add:
player.physicsBody?.categoryBitMask = PhysicsCategory.Player
player.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
player.physicsBody?.collisionBitMask = PhysicsCategory.None
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
player.physicsBody?.dynamic = true
(PhysicsCategory.Player is just a value I added in the PhysicsCategory)
Then, in the function where you add the enemy sprites you have to add in order to make the two collide:
monster.physicsBody?.contactTestBitMask = PhysicsCategory.Player
Last but not least, you have to add the following code to add "an action" to the collision the didBeginContact function:
if ((secondBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
(firstBody.categoryBitMask & PhysicsCategory.Player != 0)) {
let gameOverScene = GameOverScene(size: self.size, won: false)
view?.presentScene(gameOverScene, transition: SKTransition.flipHorizontalWithDuration(0.5))
}
Hope it's working for you now!
If you want to present a storyboard scene, you need to use something like
let gameOverScene = self.storyboard!.instantiateViewControllerWithIdentifier("GameOverViewController") as! GameOverViewController
self.view!.presentScene(gameOverScene, transition: SKTransition.fadeWithDuration(0.0))