SceneKit: как анимировать несколько SCNNode вместе, затем вызвать блок завершения один раз
Цель состоит в том, чтобы анимировать несколько узлов SCN одновременно, а затем вызвать блок завершения после завершения всех анимаций. Параллельные анимации имеют одинаковую продолжительность, поэтому будут выполняться одновременно, если они начнутся вместе.
Этот так ответ предложил использовать group
функция для Sprite Kit, но в Scene Kit нет аналогов, потому что SCNScene
класс не хватает runAction
,
Один из вариантов - запускать все действия отдельно для каждого узла, и каждый из них должен вызывать одну и ту же функцию завершения, которая должна поддерживать флаг, чтобы он вызывался только один раз.
Другой вариант - избежать обработчика завершения и вызвать код завершения после задержки, соответствующей продолжительности анимации. Однако это создает условия гонки во время тестирования, поскольку иногда анимация задерживается до завершения.
Это кажется неуклюжим, хотя. Как правильно сгруппировать анимацию нескольких узлов в SceneKit, а затем вызвать обработчик завершения?
2 ответа
Я не продумал это полностью, но я опубликую это в надежде быть полезным.
Общая проблема, сделать что-то после завершения последнего набора действий, это то, что GCD dispatch_barrier
около. Отправьте все блоки в частную параллельную очередь, затем отправьте блок завершения Grand Finale с dispatch_barrier
, Гранд Финал проходит после завершения всех предыдущих блоков.
Чего я сейчас не вижу, так это как интегрировать эти вызовы GCD с вызовами SceneKit и обработчиками завершения.
Может быть dispatch_group
это лучший подход.
Редактирование и комментарии приветствуются!
Первоначально я подошел к этому, поскольку все начальные анимации имеют одинаковую продолжительность, чтобы применить обработчик завершения только к одному из действий. Но иногда анимация зависает ( обработчик завершения SCNAction ожидает выполнения жеста).
Мое текущее, успешное решение состоит в том, чтобы не использовать обработчик завершения в сочетании с 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
}
Я сомневаюсь, что это можно назвать "правильным путем", но он хорошо работает для моих целей и устраняет случайные зависания, с которыми я столкнулся, пытаясь запустить анимацию на нескольких узлах с обработчиками завершения.
Попробуйте что-то вроде этого:
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()
}
}
}
Обратите внимание, что вы также должны учитывать, что массив узлов пуст (вы все равно можете делать все, что хотите, в конце, просто немедленно в этом случае).