Несколько серийных анимаций в 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)
}
Другие вопросы по тегам