This question must have a very simple answer, but I couldn't find it after searching for hours. Unless I just need to manually add some timer to wait.
Basically, I have an animation sequence to move a node to a certain place, here I want to wait 5 seconds and then I have another animation sequence to bring the node back to its original position.
Initially, I used SCNActions, grouping some actions and then sequencing them all. Here I just added an SCNAction.wait(duration: 5)
and that did the trick.
However, one of the actions is to move the node 90 degrees around the X-axis and I need to simultaneously rotate around another axis as well. The result is incorrect and I have a feeling I've run into the gimbal lock issue.
So instead of using SCNAction.rotate(by: )
I decided to rotate using quaternions which don't have the gimbal lock problem, but then I needed to switch to using an SCNTransaction
.
I'm nesting these transaction in the completionBlock of the previous SCNTransaction
.
For the pause between the 2 animations, I don't have a wait action here, but I thought that just adding a transaction with a duration that does nothing will at least wait for as long. But it doesn't. The transaction immediately skips to the 3rd step.
So is there a simple command to tell the animation transaction to wait?
I did try to add DispatchQueue.main.asyncAfter(deadline: .now() + 5) { <3rd SCNTransaction here> }
and that worked, but is that really the way to do it? I have a feeling there's something else that I just don't know that replaces SCNAction.wait()
.
Here's my current code which does the animations correctly, except it doesn't wait where I want it to (this is an excerpt of the relevant code only):
[...]
let moveSideQuat = simd_quatf(angle: -currentYRotation, axis: simd_float3(0, 0, 1))
let moveAwaySimd = simd_float3(-20 * sinf(currentYRotation), 0, -20 * cosf(currentYRotation))
let moveUpQuat = simd_quatf(angle: .pi/2, axis: simd_float3(1, 0, 0))
let moveDownQuat = simd_quatf(angle: -.pi/2, axis: simd_float3(1, 0, 0))
let moveTowardsQuat = simd_float3(0, 0, pivotZLocation)
let moveBackSideQuat = simd_quatf(angle: currentYRotation, axis: simd_float3(0, 0, 1))
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
baseNode?.simdOrientation = moveSideQuat * baseNode!.simdOrientation
baseNode?.simdPosition = moveAwaySimd
baseNode?.simdOrientation = moveUpQuat * baseNode!.simdOrientation
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 5 // <<<--- I WANT TO WAIT HERE
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
self.baseNode?.simdOrientation = moveDownQuat * self.baseNode!.simdOrientation
self.baseNode?.simdPosition = moveTowardsQuat
self.baseNode?.simdOrientation = moveBackSideQuat * self.baseNode!.simdOrientation
SCNTransaction.commit()
}
SCNTransaction.commit()
}
SCNTransaction.commit()
[...]
In the 2nd completion block, after the animationDuration = 5
I even tried to add a simple assignment for the node's simdPosition, basically leaving it in the same position in the hope to trick the sequence to take 5 seconds to basically do nothing, but it still skipped to the next completionBlock.
So is the asyncAfter
actually the way to go or am I missing something else?
Thanks!
Using asyncAfter
to wait is fine. That is the purpose of this method, after all.
Creating an empty transaction with the duration of 5 seconds doesn't work because SceneKit can easily see that you haven't changed anything, and knows that the animation can complete immediately. You can hide a node somewhere in the scene and animate that node for 5 seconds, but that feels way more like a hack.
If you want, you can still use SCNAction
s for the rest of your animations, and just use SCNTransaction
for the ones that need the quaternions. For example:
let step1 = SCNAction.group([
SCNAction.run {
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
// do rotations here with quaternions...
SCNTransaction.commit()
},
SCNAction.move(to: /* destination */, duration: 1)
])
let step2 = SCNAction.wait(duration: 5)
let step3 = SCNAction.group([
SCNAction.run {
SCNTransaction.begin()
SCNTransaction.animationDuration = 1
// rotate back...
SCNTransaction.commit()
},
SCNAction.move(to: /* origin */, duration: 1)
])
let entireAction = SCNAction.sequence([
step1, step2, step3
])