Несколько серийных анимаций в SceneKit
Я хочу иметь возможность запускать несколько анимаций, одну за другой, в SceneKit. Я реализовал функцию, которая запускает одну анимацию так:
fileprivate func animateMove(_ move: Move) {
print("Animate move started " + move.identifier)
// I am creating rotateNode
let rotateNode = SCNNode()
rotateNode.eulerAngles.x = CGFloat.pi
scene.rootNode.addChildNode(rotateNode)
// Then I am selecting nodes which I want to rotate
nodesToRotate = ...
// Then I am adding the nodes to rotate node
_ = nodesToRotate.map { rotateNode.addChildNode($0) }
SCNTransaction.begin()
SCNTransaction.animationDuration = move.animationDuration
SCNTransaction.completionBlock = {
rotateNode.enumerateChildNodes { node, _ in
node.transform = node.worldTransform
node.removeFromParentNode()
scene.rootNode.addChildNode(node)
}
rotateNode.removeFromParentNode()
print("Animate move finished " + move.identifier)
}
SCNTransaction.commit()
}
А потом я попытался запустить несколько последовательных анимаций, например, так:
func animateMoves(_ moves: [Move]) {
for (index, move) in moves.enumerated() {
perform(#selector(animateMove(_:)),
with: move,
afterDelay: TimeInterval(Double(index) * move.duration)
}
}
Все оживляет, но анимация не запускается последовательно. Анимации начинаются и заканчиваются в непредсказуемое время. Примеры журналов отладчика:
Animate move started 1
Animate move started 2
Animate move finished 1
Animate move finished 2
Animate move started 3
Animate move finished 3
Я понимаю, что мой подход не самый лучший, но только так я смог добиться почти работающей анимации.
Я знаю, что есть класс SCNAction. Может быть, я должен сделать много действий в рамках одной транзакции? Если да, может ли кто-нибудь объяснить мне, как именно работает SCNTransactions и почему блок завершения SCNTransaction срабатывает в непредсказуемое время?
2 ответа
Попробуйте использовать SCNAction.sequence ():
class func sequence([SCNAction])
Создает действие, которое запускает коллекцию действий последовательно
let sequence = SCNAction.sequence([action1, action2, action3]) // will be executed one by one
let node = SCNNode()
node.runAction(sequence, completionHandler:nil)
После ответа @Oleh Zayats я попытался реализовать свой случай, используя метод SCNAction.sequence(_:), но проблема заключалась в том, что мне нужно было запускать обработчик завершения после каждой завершенной операции, чтобы иметь возможность удалять узлы из rotationNode.
После нескольких часов борьбы у меня получилось довольно хорошее решение, и оно сработало как шарм.
А именно:
Я сделал функцию rotateAction, которая выглядит примерно так:
func rotateAction(with move: Move, from rotateNode: SCNNode) -> SCNAction {
let preAction = SCNAction.run { (rotateNode) in
// all the pre action setup like choosing nodes to rotate
}
let action = SCNAction.rotate(by: -move.angle, around: vector, duration: move.animationDuration)
let postAction = SCNAction.run { (rotateNode) in
// completion handler for each action
}
return SCNAction.sequence([preAction, action, postAction])
}
Затем я смог написать функцию для запуска нескольких анимаций одна за другой:
func animateRotateMoves(_ moves: [Move]) {
let rotateNode = SCNNode()
scene.rootNode.addChildNode(rotateNode)
var actions: [SCNAction] = []
for move in moves {
let action = rotateAction(with: move, from: rotateNode)
actions.append(action)
}
actions.append(SCNAction.removeFromParentNode())
let sequence = SCNAction.sequence(actions)
rotateNode.runAction(sequence)
}