【问题标题】:SceneKit: how to animate multiple SCNNodes together then call completion block onceSceneKit:如何一起为多个 SCNNode 设置动画,然后调用完成块一次
【发布时间】:2016-12-29 16:23:50
【问题描述】:

目标是同时为多个 SCNNode 设置动画,然后在所有动画完成后调用完成块。并行动画具有相同的持续时间,因此如果一起开始,将同时完成。

This SO answer 建议对 Sprite Kit 使用 group 函数,但 Scene Kit 中没有类似功能,因为 SCNScene 类缺少 runAction

一个选项是针对每个节点单独运行所有操作,并让每个操作调用相同的完成函数,该完成函数必须维护一个标志以确保它只被调用一次。

另一种选择是避免完成处理程序并在与动画持续时间匹配的延迟后调用完成代码。但是,这会在测试期间产生竞争条件,因为有时动画在完成之前就被暂停了。

不过,这似乎很笨拙。在 SceneKit 中对多个节点的动画进行分组然后调用完成处理程序的正确方法是什么?

【问题讨论】:

    标签: ios animation scenekit skaction scnnode


    【解决方案1】:

    我最初的处理方式是,因为所有初始动画都具有相同的持续时间,所以只将完成处理程序应用于其中一个动作。但是,有时,动画会挂断 (SCNAction completion handler awaits gesture to execute)。

    我目前成功的解决方案是不将完成处理程序与 SCNAction 结合使用,而是使用延迟:

    func delay(delay:Double, closure:()->()) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(delay * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), closure)
    }
    

    调用示例:

    delay(0.95) {
         self.scaleNode_2.runAction(moveGlucoseBack)
         self.fixedNode_2.runAction(moveGlucoseBack)
         self.scaleNode_3.hidden = true
         self.fixedNode_3.hidden = true
    }
    

    我怀疑这可以称为“正确的方式”,但它适用于我的使用,并且消除了我在尝试使用完成处理程序在多个节点上运行动画时遇到的随机挂起。

    【讨论】:

    • 应该更新了问题,但这也是我们使用的方法。不幸的是,它会产生竞争条件,因为动画似乎偶尔会延迟。
    【解决方案2】:

    我还没有完全考虑到这一点,但我会发布它以希望有用。

    一般问题,在一组动作中的最后一个完成后做某事,这就是 GCD 的dispatch_barrier 的意义所在。将所有块提交到私有并发队列,然后使用dispatch_barrier 提交 Grand Finale 完成块。总决赛在之前的所有区块完成后运行。

    我没有立即看到的是如何将这些 GCD 调用与 SceneKit 调用和完成处理程序集成在一起。

    也许dispatch_group 是一个更好的方法。

    欢迎编辑和 cmets!

    【讨论】:

      【解决方案3】:

      试试这样的:

      private class CountMonitor {
          var completed: Int = 0
          let total: Int
          let then: ()->Void
      
          init(for total: Int, then: @escaping(()->Void)) {
              self.total = total
              self.then = then
          }
      
          func didOne() {
              completed += 1
              if completed == total {
                  then()  // Generally you should dispatch this off the main thread though
              }
          }
      }
      

      然后创建动作看起来像:

      private func test() {
          // for context of types
          let nodes: [SCNNode] = []
          let complexActionsToRun: SCNAction = .fadeIn(duration: 100)
      
          // Set up the monitor so it knows how many 'didOne' calls it should get, and what to do when they are all done ...
          let monitor = CountMonitor(for: nodes.count) { () in
              // do whatever you want at the end here
              print("Done!")
          }
          for node in nodes {
              node.runAction( complexActionsToRun ) { () in
                  monitor.didOne()
              }
          }
      }
      

      请注意,您还应该考虑节点数组为空(您可能仍想在最后做任何您想做的事情,在这种情况下立即执行)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-07
        • 1970-01-01
        • 1970-01-01
        • 2021-08-22
        • 2014-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多