ARKit 动画 SCNNode

ARKit Animation SCNNode

我有一个 SCNNode,我在其中显示一个表面 - 我想在这个表面上显示一条路径。这个路径是一个 SCNNode 本身,它被添加到表面 SCNNode。这个 SCNNode(路径)由多个 SCNNode 组成,它们是整个路径的一小部分 - 所以我将它们全部添加到路径 SCNNode。

所以工作流程如下:

  1. 计算路径的 SCNNode 块
  2. 将块添加到完整路径SCNNode
  3. 添加每个SCNNode块时->添加表面SCNNode的完整路径

问题:我不只是想添加这个我想从头到尾(从第一个块到最后一个块)对其进行动画处理,但我该怎么做?

感谢您的帮助!

由于您没有提供任何代码(请下次提供),我将提供一个解决方案,为您指明正确的方向。

让我们从创建一个 PathItem Class 开始,我们将使用它来制作完整的路径,例如其中一行:

/// Path Item Node
class PathItem: SCNNode{


    /// Creates A PathItem
    ///
    /// - Parameters:
    ///   - size: CGFloat (Defaults To 20cm)
    ///   - texture: UIColour
    ///   - position: SCNVector3
    init(size: CGFloat = 0.2, texture: UIColor, position: SCNVector3){

        super.init()

        //1. Create Our Path Geometry
        let pathGeometry = SCNPlane(width: size, height: size)

        //2. Assign The Colour To The Geoemtry
        pathGeometry.firstMaterial?.diffuse.contents = texture

        //3. Assign The Geometry, Position The Node & Rotate The Node So It Is Horizontal
        self.geometry = pathGeometry
        self.position = position
        self.eulerAngles.x = GLKMathDegreesToRadians(-90)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("Path Item Coder Has Not Been Implemented") }

}

完成此操作后,让我们创建一个 func,这将创建一行 PathItem(路径)。

首先像这样创建一个全局变量,它引用每个 PathItem:

的大小
let pathItemSize: CGFloat = 0.2

然后我们创建我们的函数来交替每个 PathItem 的颜色,并为它们提供一个唯一的 name 或我们稍后将在动画中使用的索引:

/// Create A Path With A Number Of Elements
///
/// - Parameter numberOfElements: Int
/// - Returns: PATH (SCNNode)
func createdPathOfSize(_ numberOfElements: Int) {

    var pathColour: UIColor!

    //2. Loop Through The Number Of Path Elements We Want & Place Them In A Line
    for pathIndex in 0 ..< numberOfElements{

        //a. Position Each Peice Next To Each Other Based On The Index
        let pathPosition = SCNVector3(0, -0.2, -pathItemSize * CGFloat(pathIndex+1))

        //b. Alternate The Colour Of Our Path
        if pathIndex % 2 == 0 { pathColour = UIColor.white } else { pathColour = UIColor.black }

        //c. Create Our Path Item With A Unique Index We Can Use For Animating
        let pathItem = PathItem(texture: pathColour, position: pathPosition)
        pathItem.name = String(pathIndex)

        //d. Set It To Hidden Initially
        pathItem.isHidden = true

        //e. Add It To Our Scene
       self.augmentedRealityView.scene.rootNode.addChildNode(pathItem)
    }

}

要生成 Path 我们现在可以这样做:

override func viewDidLoad() {
    super.viewDidLoad()

    //1. Set Up Our ARSession
    augmentedRealityView.session = augmentedRealitySession
    sessionConfiguration.planeDetection = .horizontal
    augmentedRealityView.debugOptions = .showFeaturePoints
    augmentedRealitySession.run(sessionConfiguration, options: [.resetTracking, .removeExistingAnchors])

    //2. Create A Path Of 10 PathItems
    createdPathOfSize(10)
}

因此我有以下 Global variables:

@IBOutlet var augmentedRealityView: ARSCNView!
let augmentedRealitySession = ARSession()
let sessionConfiguration = ARWorldTrackingConfiguration()

现在我们已经生成了我们需要动画的路径!

为了提供一点多样性,让我们创建一个 Enum,我们可以用它来创建不同的路径动画:

 /// Path Item Animation
 ///
 /// - UnHide: UnHides The Path Item
 /// - FadeIn: Fades The Path Item In
 /// - FlipIn: Flips The Path Item In
 enum AnimationType{

     case UnHide
     case FadeIn
     case FlipIn

 }

不,因为我们要制作一些动画,让我们再创建 2 个全局变量,这些变量将用于 运行 我们在定时器上的动画并跟踪我们的进度:

var pathAnimationTimer: Timer?
var time: Int = 0

现在让我们创建动画函数:

/// Animates The Laying Of The Path
///
/// - Parameters:
///   - numberOfElements: Int
///   - animation: AnimationType
func animatePathElements(_ numberOfElements: Int, withAnimation animation: AnimationType ){

    //1. If We Are Flipping The PathItems In We Need To 1st Unhide Them All & Rotate Them To A Vertical Postions
    if animation == .FlipIn {

        let pathItems = self.augmentedRealityView.scene.rootNode.childNodes

        pathItems.forEach({ (pathItemToAnimate) in
            pathItemToAnimate.isHidden = false
            pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(0)
        })

    }

    //2. Create Our Time Which Will Run Every .25 Seconds
    pathAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { timer in

        //3. Whilst Our Time Doesnt Equal The Number Of Path Items Then Continue Our Animation
        if self.time != numberOfElements{

            //a. Get The Current Node Remembering Each One Has A Unique Name (Index
            guard let pathItemToAnimate = self.augmentedRealityView.scene.rootNode.childNode(withName: "\(self.time)", recursively: false) else { return }

            //b. Run The Desired Animation Sequence
            switch animation{
            case .UnHide:
                 //Simply Unhide Each PathItem
                 pathItemToAnimate.isHidden = false
            case .FadeIn:

                //1. Unhide The Item & Sets It's Opacity To 0 Rendering It Invisible
                 pathItemToAnimate.isHidden = false
                 pathItemToAnimate.opacity = 0

                 //2. Create An SCNAction To Fade In Our PathItem
                 let fadeInAction = SCNAction.fadeOpacity(to: 1, duration: 0.25)
                 pathItemToAnimate.runAction(fadeInAction)

            case .FlipIn:
                 //Simply Rotate The Path Item Horizontally
                 pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(-90)
            }

            self.time += 1

        }else{
            //4. Our Animation Has Finished So Invalidate The Timer
            self.pathAnimationTimer?.invalidate()
            self.time = 0
        }
    }
}

然后我们需要将其添加到我们的 createPathOfSize 函数的末尾,如下所示:

/// Create A Path With A Number Of Elements Which Can Be Animated
///
/// - Parameter numberOfElements: Int
/// - Returns: PATH (SCNNode)
func createdPathOfSize(_ numberOfElements: Int) {

    var pathColour: UIColor!

    //1. Loop Through The Number Of Path Elements We Want & Place Them In A Line
    for pathIndex in 0 ..< numberOfElements{

        //a. Position Each Peice Next To Each Other Based On The Index
        let pathPosition = SCNVector3(0, -0.2, -pathItemSize * CGFloat(pathIndex+1))

        //b. Alternate The Colour Of Our Path

        if pathIndex % 2 == 0 { pathColour = UIColor.white } else { pathColour = UIColor.black }

        //b. Create Our Path Item With A Unique Index We Can Use For Animating
        let pathItem = PathItem(texture: pathColour, position: pathPosition)
        pathItem.name = String(pathIndex)

        //c. Set It To Hidden Initially
        pathItem.isHidden = true

        //d. Add It To Our Scene
       self.augmentedRealityView.scene.rootNode.addChildNode(pathItem)
    }

    //2. Animate The Path
    animatePathElements(10, withAnimation: .FlipIn)

}

这里是完整的例子:

import UIKit
import ARKit

//----------------------
//MARK: - Path Animation
//----------------------

/// Path Item Animation
///
/// - Show: UnHides The Path Item
/// - FadeIn: Fades The Path Item In
enum AnimationType{

    case UnHide
    case FadeIn
    case FlipIn

}

//-----------------
//MARK: - Path Item
//-----------------

/// Path Item Node
class PathItem: SCNNode{


    /// Creates A PathItem
    ///
    /// - Parameters:
    ///   - size: CGFloat (Defaults To 20cm)
    ///   - texture: UIColour
    ///   - position: SCNVector3
    init(size: CGFloat = 0.2, texture: UIColor, position: SCNVector3){

        super.init()

        //1. Create Our Path Geometry
        let pathGeometry = SCNPlane(width: size, height: size)

        //2. Assign The Colour To The Geoemtry
        pathGeometry.firstMaterial?.diffuse.contents = texture

        //3. Assign The Geometry, Position The Node & Rotate The Node So It Is Horizontal
        self.geometry = pathGeometry
        self.position = position
        self.eulerAngles.x = GLKMathDegreesToRadians(-90)
    }

    required init?(coder aDecoder: NSCoder) { fatalError("Path Item Coder Has Not Been Implemented") }

}

class ViewController: UIViewController {

    typealias PATH = SCNNode
    @IBOutlet var augmentedRealityView: ARSCNView!
    let augmentedRealitySession = ARSession()
    let sessionConfiguration = ARWorldTrackingConfiguration()

    var pathPlaced = false
    let pathItemSize: CGFloat = 0.2
    var pathAnimationTimer: Timer?
    var time: Int = 0

    //----------------------
    //MARK: - View LifeCycle
    //----------------------

    override func viewDidLoad() {
        super.viewDidLoad()

        //1. Set Up Our ARSession
        augmentedRealityView.session = augmentedRealitySession
        sessionConfiguration.planeDetection = .horizontal
        augmentedRealityView.debugOptions = .showFeaturePoints
        augmentedRealitySession.run(sessionConfiguration, options: [.resetTracking, .removeExistingAnchors])

        //2. Create A Path Of 10 Path Items
        createdPathOfSize(10)
    }

    //---------------------------------
    //MARK: - Path Creation & Animation
    //---------------------------------

    /// Animates The Laying Of The Path
    ///
    /// - Parameters:
    ///   - numberOfElements: Int
    ///   - animation: AnimationType
    func animatePathElements(_ numberOfElements: Int, withAnimation animation: AnimationType ){

        if animation == .FlipIn {

            let pathItems = self.augmentedRealityView.scene.rootNode.childNodes

            pathItems.forEach({ (pathItemToAnimate) in
                pathItemToAnimate.isHidden = false
                pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(0)
            })

        }

        pathAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { timer in

            //1. Whilst Our Time Doesnt Equal The Number Of Path Items Then Continue Our Animation
            if self.time != numberOfElements{

                guard let pathItemToAnimate = self.augmentedRealityView.scene.rootNode.childNode(withName: "\(self.time)", recursively: false) else { return }

                //2. Run The Desired Animation Sequence
                switch animation{
                case .UnHide:
                    pathItemToAnimate.isHidden = false
                case .FadeIn:
                    pathItemToAnimate.isHidden = false
                    pathItemToAnimate.opacity = 0
                    let fadeInAction = SCNAction.fadeOpacity(to: 1, duration: 0.3)
                    pathItemToAnimate.runAction(fadeInAction)
                case .FlipIn:

                    pathItemToAnimate.eulerAngles.x = GLKMathDegreesToRadians(-90)
                }

                self.time += 1

            }else{
                self.pathAnimationTimer?.invalidate()
                self.time = 0
            }
        }


    }


    /// Create A Path With A Number Of Elements Which Can Be Animated
    ///
    /// - Parameter numberOfElements: Int
    /// - Returns: PATH (SCNNode)
    func createdPathOfSize(_ numberOfElements: Int) {

        var pathColour: UIColor!

        //1. Loop Through The Number Of Path Elements We Want & Place Them In A Line
        for pathIndex in 0 ..< numberOfElements{

            //a. Position Each Peice Next To Each Other Based On The Index
            let pathPosition = SCNVector3(0, -0.2, -pathItemSize * CGFloat(pathIndex+1))

            //b. Alternate The Colour Of Our Path

            if pathIndex % 2 == 0 { pathColour = UIColor.white } else { pathColour = UIColor.black }

            //b. Create Our Path Item With A Unique Index We Can Use For Animating
            let pathItem = PathItem(texture: pathColour, position: pathPosition)
            pathItem.name = String(pathIndex)

            //c. Set It To Hidden Initially
            pathItem.isHidden = true

            //d. Add It To Our Scene
            self.augmentedRealityView.scene.rootNode.addChildNode(pathItem)
        }

        //2. Animate The Path
        animatePathElements(10, withAnimation: .FlipIn)

    }

}

这应该足以为您指明正确的方向^__________^.