如何在嵌套SCNTransaction动画链中添加暂停/等待逻辑
SceneKit中SCNTransaction串联动画时的等待实现方案
问题背景
- 需求为实现节点动画序列:节点先移动到指定位置,停留5秒后,再执行动画返回初始位置。
- 最初使用
SCNAction实现序列时,通过SCNAction.wait(duration: 5)即可轻松实现等待效果,但多轴旋转场景下触发了万向锁问题,旋转结果异常。 - 为规避万向锁,改用四元数实现旋转逻辑,因此切换到
SCNTransaction做动画控制,通过嵌套completionBlock串联多段动画,但SCNTransaction未提供类似SCNAction.wait()的等待接口。 - 已尝试的无效方案:
- 创建配置了5秒动画时长、但不修改任何节点属性的空事务,事务会被立即跳过,直接执行后续动画
- 在5秒时长的事务中为节点属性赋与当前值完全相同的内容,依然会直接触发
completionBlock,无法实现等待效果
- 目前通过
DispatchQueue.main.asyncAfter(deadline: .now() + 5)包裹返回段动画可以实现预期效果,但不确定该方式是否为标准实现,希望确认是否存在未被发现的原生等待API。
问题代码片段
[...] 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 // <<<--- 期望在此处等待5秒 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() [...]
解答
核心结论
你当前使用的DispatchQueue.main.asyncAfter方案是完全合规、稳定的标准实现,可以直接使用,不存在兼容性或时序问题。
原因说明
SCNTransaction的动画计时仅对事务块内实际发生值变化的可动画属性生效:如果事务内没有修改任何属性,或是修改的属性值与当前值完全一致,系统会判定该事务无需要执行的动画内容,会立刻触发completionBlock,不会等待你设置的animationDuration时长,这也是你之前尝试空事务、赋相同值都无法实现等待的根本原因。
其他可选实现方案
- 无需完全弃用
SCNAction
你不需要因为万向锁问题全量切换到SCNTransaction:SCNAction本身支持四元数旋转能力,你可以使用SCNAction.rotateTo(x:y:z:w:duration:)直接传入四元数分量,或是通过SCNAction.customAction(duration:)在回调中手动更新节点的simdOrientation为插值后的四元数,从根源避免万向锁。这种方式下整个动画序列依然可以通过SCNAction.sequence串联,直接插入SCNAction.wait(duration: 5)即可实现等待,逻辑更简洁。 - 纯SCNTransaction场景的非推荐方案
如果你坚持全量使用SCNTransaction,除了asyncAfter外,也可以在等待段的事务中修改一个肉眼完全不可见的可动画属性(比如将节点的偏移量调整0.0001),触发系统的动画计时,但这种方式属于hack手段,可读性和稳定性都不如asyncAfter,不建议使用。
注:SceneKit的事务回调、动画调度本身就是在主队列执行,主队列的
asyncAfter调度和SceneKit渲染时序完全对齐,不会出现时序错乱、动画断裂的问题,苹果官方的SceneKit示例代码中也常使用该方式控制动画间隔。
内容的提问来源于stack exchange,提问作者rafi
相关产品推荐
相关产品推荐

