I'm trying to modify Xcode's default game setup so that I can: program an animation into the geometry, scrub through that animation, and let the user playback the animation automatically.
I managed to get the scrubbing of the animation to work by setting the view's scene time based on the value of a scrubber. However, when I set the isPlaying boolean on the SCNSceneRenderer to true, it resets the time to 0 on every frame, and I can't get it to move off the first frame.
From the docs, I'm assuming this means it won't detect my animation and thinks the duration of all animations is 0.
Here's my viewDidLoad function in my GameViewController:
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// 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)!
// define the animation
//ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
let positionAnimation = CAKeyframeAnimation(keyPath: "position.y")
positionAnimation.values = [0, 2, -2, 0]
positionAnimation.keyTimes = [0, 1, 3, 4]
positionAnimation.duration = 5
positionAnimation.usesSceneTimeBase = true
// retrieve the SCNView
let scnView = self.view as! SCNView
scnView.delegate = self
// add the animation
ship.addAnimation(positionAnimation, forKey: "position.y")
// set the scene to the view
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
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
// play the scene
scnView.isPlaying = true
//scnView.loops = true
}
Any help is appreciated! :)
References:
sceneTime:
https://developer.apple.com/documentation/scenekit/scnscenerenderer/1522680-scenetime
isPlaying:
https://developer.apple.com/documentation/scenekit/scnscenerenderer/1523401-isplaying
related question:
SceneKit SCNSceneRendererDelegate - renderer function not called
I couldn't get it to work in an elegant way, but I fixed it by adding this Timer call:
Timer.scheduledTimer(timeInterval: timeIncrement, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)
timeIncrement is a Double set to 0.01, and updateTimer is the following function:
// helper function updateTimer
@objc func updateTimer() {
let scnView = self.view.subviews[0] as! SCNView
scnView.sceneTime += Double(timeIncrement)
}
I'm sure there's a better solution, but this works.