I am using SceneKit to load a Collada (.dae) file.
Whatever I have tried, the animation repeats in a continuous loop whereas I only want it play once and then stop.
Here's the loading code where sceneView is my SCNView:
//Load the scene and play
let scene = SCNScene(named: "Scenes.scnassets/test4.dae")
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
The scene loads correctly and the animation plays on loading as required, but in a continuous loop.
I have tried removing all animations as follows:
sceneView.scene?.rootNode.removeAllAnimations()
but this seems to have no effect.
I have also tried retrieving all animations and setting the repeatCount = 1 or even to 0
let url = NSBundle.mainBundle().URLForResource("Scenes.scnassets/test4", withExtension: "dae")
let sceneSource = SCNSceneSource(URL:url!, options: nil)
let animationIndentifiers = sceneSource?.identifiersOfEntriesWithClass(CAAnimation)
if let ids = animationIndentifiers
{
for id in ids {
let animation: CAAnimation = sceneSource!.entryWithIdentifier(id as! String, withClass: CAAnimation.self)! as! CAAnimation
animation.repeatCount = 1
}
}
I have also tried this another way:
let keys = sceneView.scene!.rootNode.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = sceneView.scene?.rootNode.animationForKey(key as! String)
animation?.repeatCount = 1
}
}
but again no luck.
I can find nothing in the Collada file itself that is obviously causing the animation to repeat. However, I notice that the .dae file icon on my Mac disk has a play button and clicking this also plays the animation within the icon on a continuous loop.
Update:
I now notice that in the code above I am setting the constant 'animation' attributes and this is not copied back to the actual scene nodes. Also, the only animation in the .dae file is in a child node. So here's my next attempt:
//Get the child nodes
let children = scene?.rootNode.childNodes
//Initialise a childNode counter
var childCount = 0
//Iterate through the child nodes
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
//Iterate through the animations
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
println("Initial Repeat count: \(animation.repeatCount)")
animation.repeatCount = 1
//Remove existing animation
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
//Add amended animation
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
There is actually only one animation attached to one child node. (I have also tried setting this using the the actual animation id string in place of 'key' above.)
The above shows Initial Repeat count: inf
and on checking afterwards it is indeed set to 1.
However, the animation still runs in an infinite loop :-(
Any help to resolve this would be much appreciated.
Further update
I have now created a new Collada file with simple animation using Maya and for some reason one of the trials attempted above actually works:
func sceneSetup() {
let scene = SCNScene(named: "Scenes.scnassets/test10.dae")
let children = scene?.rootNode.childNodes
var childCount = 0
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
animation.repeatCount = 1
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
}
if anybody can explain that would be great!
Ah, but there's another problem!
Here's the start frame of the animation:
and here's the end frame where we want to end up:
But at the end of the animation the scene jumps back to the start view.
I have 'fixed' this by amending the animation so that frame 1 is copy of the final frame. This works and isn't noticeable but doesn't seem a very elegant solution.
For the animation jumping back to the first frame, the isAppliedOnCompletion
value is what you are looking for.
animation.repeatCount = 1
animation.isAppliedOnCompletion = true
This will make sure that the animation pauses on the final frame.
This works for me. It's based off your answer but will go through the node's entire tree and limit all of their animation counts to one.
Personally I found that if I missed any of the descendant nodes when manually traversing and setting the node animation counts to one, I would have a problem. This ensures that the node passed in itself, and all child nodes will only animate once, and then hold the model in place after finishing.
Use like this animateEntireNodeTreeOnce(mostRootNode: nodeYouImportedFromCollada)
.
func animateEntireNodeTreeOnce(mostRootNode node: SCNNode){
onlyAnimateThisNodeOnce(node)
for childNode in node.childNodes {
animateEntireNodeTreeOnce(mostRootNode: childNode)
}
}
func onlyAnimateThisNodeOnce(_ node: SCNNode) {
if node.animationKeys.count > 0 {
for key in node.animationKeys {
let animation = node.animation(forKey: key)!
animation.repeatCount = 1
animation.isRemovedOnCompletion = false
node.removeAllAnimations()
node.addAnimation(animation, forKey: key)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With