【问题标题】:SceneKit animate node along pathSceneKit 沿路径动画节点
【发布时间】:2015-07-15 21:16:09
【问题描述】:

我有一个盒子节点

_boxNode = [SCNNode node];
_boxNode.geometry = [SCNBox boxWithWidth:1 height:1 length:1 chamferRadius:0];
_boxNode.position = SCNVector3Make(0, 0, -2);
[scene.rootNode addChildNode:_boxNode];

我有一条路

CGPathRef path = CGPathCreateWithEllipseInRect(CGRectMake(-2, -2, 4, 4), nil);

我想让我的盒子沿着我的路径移动一次。

如何在 SceneKit 中执行此操作?

我想做一个看起来像的方法

[_boxNode runAction:[SCNAction moveAlongPath:path forDuration:duration]];

【问题讨论】:

  • 是什么阻止了您进行搜索?我以前看过类似的话题。
  • 我以前用 CAKeyAnimation 做过这个。 SceneKit 没有内置的 API 来沿着路径移动。我的工作作品体积庞大,充其量也过于复杂。所以我想知道是否有人有一个简单的解决方案。
  • 不将CoreAnimation动画添加到节点工作吗?
  • Scenekit 确实需要对 Splines 的支持...一种手动完成的方法是在 3D 软件中构建路径,然后自己插入点之间的位置。这就是我通常的做法。
  • 查看这个答案。希望对您有所帮助:stackoverflow.com/a/24332672/885189

标签: ios objective-c swift scenekit


【解决方案1】:

我也遇到了这个问题,我写了一个小操场。动画效果很好。需要做一件事。必须计算每个点之间的距离,以便可以缩放时间以获得平滑的动画。只需将代码复制并粘贴到操场上。代码在 Swift 3 中。

这是我的解决方案(BezierPath 扩展不是来自我,在这里找到):

import UIKit
import SceneKit
import PlaygroundSupport

let animationDuration = 0.1

public extension UIBezierPath {

    var elements: [PathElement] {
        var pathElements = [PathElement]()
        withUnsafeMutablePointer(to: &pathElements) { elementsPointer in
            cgPath.apply(info: elementsPointer) { (userInfo, nextElementPointer) in
                let nextElement = PathElement(element: nextElementPointer.pointee)
                let elementsPointer = userInfo!.assumingMemoryBound(to: [PathElement].self)
                elementsPointer.pointee.append(nextElement)
            }
        }
        return pathElements
    }
}

public enum PathElement {

    case moveToPoint(CGPoint)
    case addLineToPoint(CGPoint)
    case addQuadCurveToPoint(CGPoint, CGPoint)
    case addCurveToPoint(CGPoint, CGPoint, CGPoint)
    case closeSubpath

    init(element: CGPathElement) {
        switch element.type {
        case .moveToPoint: self = .moveToPoint(element.points[0])
        case .addLineToPoint: self = .addLineToPoint(element.points[0])
        case .addQuadCurveToPoint: self = .addQuadCurveToPoint(element.points[0], element.points[1])
        case .addCurveToPoint: self = .addCurveToPoint(element.points[0], element.points[1], element.points[2])
        case .closeSubpath: self = .closeSubpath
        }
    }
}

public extension SCNAction {

    class func moveAlong(path: UIBezierPath) -> SCNAction {

        let points = path.elements
        var actions = [SCNAction]()

        for point in points {

            switch point {
            case .moveToPoint(let a):
                let moveAction = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                actions.append(moveAction)
                break

            case .addCurveToPoint(let a, let b, let c):
                let moveAction1 = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                let moveAction2 = SCNAction.move(to: SCNVector3(b.x, b.y, 0), duration: animationDuration)
                let moveAction3 = SCNAction.move(to: SCNVector3(c.x, c.y, 0), duration: animationDuration)
                actions.append(moveAction1)
                actions.append(moveAction2)
                actions.append(moveAction3)
                break

            case .addLineToPoint(let a):
                let moveAction = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                actions.append(moveAction)
                break

            case .addQuadCurveToPoint(let a, let b):
                let moveAction1 = SCNAction.move(to: SCNVector3(a.x, a.y, 0), duration: animationDuration)
                let moveAction2 = SCNAction.move(to: SCNVector3(b.x, b.y, 0), duration: animationDuration)
                actions.append(moveAction1)
                actions.append(moveAction2)
                break

            default:
                let moveAction = SCNAction.move(to: SCNVector3(0, 0, 0), duration: animationDuration)
                actions.append(moveAction)
                break
            }   
        }
        return SCNAction.sequence(actions)
    }
}



let scnView = SCNView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
scnView.autoenablesDefaultLighting = true

let scene = SCNScene()
scnView.scene = scene

let light = SCNLight()
light.type = .ambient
let lightNode = SCNNode()
lightNode.light = light
scene.rootNode.addChildNode(lightNode)

let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(0,0,10)
scene.rootNode.addChildNode(cameraNode)

let box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
let boxNode = SCNNode(geometry: box)
boxNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red

scene.rootNode.addChildNode(boxNode)

let path1 = UIBezierPath(roundedRect: CGRect(x: 1, y: 1, width: 2, height: 2), cornerRadius: 1)

let moveAction = SCNAction.moveAlong(path: path1)
let repeatAction = SCNAction.repeatForever(moveAction)
SCNTransaction.begin()
SCNTransaction.animationDuration = Double(path1.elements.count) * animationDuration
boxNode.runAction(repeatAction)
SCNTransaction.commit()

PlaygroundPage.current.liveView = scnView

【讨论】:

    【解决方案2】:

    这里我做了一个关于如何在搅拌机中创建 NURBS 路径的快速教程,然后有一个对象跟随它(在本例中是 Xcode 中默认的新项目代码附带的船。

    你也可以在我的gist here下找到这个

    需要考虑的事项

    1. Move.: 你需要空间中的点 [SCNVector]

    2. 点。:一个物体向前移动,因此原始物体(船)可以跟随,尊重路径的方向。

    3. 这些框仅用于说明路径。它们可以被删除

    4. 看看如何在RoutePath中导出NURBS

    5. 这是一个 XCode 项目,当你开始一个新项目时 -> 游戏 -> Swift -> SceneKit

      override func viewDidLoad() {
      
          super.viewDidLoad()
      
          // create a new scene
          let scene = SCNScene(named: "art.scnassets/ship.scn")!
      
          // create and add a camera to the scene
          let cameraNode = SCNNode()
          cameraNode.camera = SCNCamera()
          scene.rootNode.addChildNode(cameraNode)
      
          // place the camera
          cameraNode.position = SCNVector3(x: 0, y: 0, z: -10)
      
          // create and add a light to the scene
          let lightNode = SCNNode()
          lightNode.light = SCNLight()
          lightNode.light!.type = .omni
          lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
          scene.rootNode.addChildNode(lightNode)
      
          // create and add an ambient light to the scene
          let ambientLightNode = SCNNode()
          ambientLightNode.light = SCNLight()
          ambientLightNode.light!.type = .ambient
          ambientLightNode.light!.color = NSColor.darkGray
          scene.rootNode.addChildNode(ambientLightNode)
      
          // MARK: - Path (Orientation)
      
          // Orientation node: Ahead of the ship, the orientation node is used to
          // maintain the ship's orientation (rotating the ship according to path's next point)
          let orientationNode = SCNNode()
          scene.rootNode.addChildNode(orientationNode)
      
          // MARK: - Path (Ship)
      
          // retrieve the ship node
          let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
          ship.scale = SCNVector3(0.15, 0.15, 0.15)
      
          // Get the path you want to follow
          var pathToFollow:[SCNVector3] = RoutePath.decodePath()
      
          // Set the ship to start at the path's first point
          ship.position = pathToFollow.first!
      
          // Constraint ship to look at orientationNode
          let shipLook = SCNLookAtConstraint(target: orientationNode)
          shipLook.localFront = SCNVector3(0, 0, 1)
          shipLook.worldUp = SCNVector3(0, 1, 0)
          shipLook.isGimbalLockEnabled = true
          ship.constraints = [shipLook]
      
          // Camera Constraints (Following ship)
          let look = SCNLookAtConstraint(target: ship)
          let follow = SCNDistanceConstraint(target: ship)
          follow.minimumDistance = 3
          follow.maximumDistance = 6
          cameraNode.constraints = [look, follow]
      
          // MARK: - Actions
      
          // Ship's actions
          var shipActions:[SCNAction] = []
      
          // Actions for the orientation node
          var orientationActions:[SCNAction] = []
      
          // Populate Path Animations
          while !pathToFollow.isEmpty {
      
              pathToFollow.remove(at: 0)
              if let next = pathToFollow.first {
      
                  let act = SCNAction.move(to: next, duration: 0.8)
      
                  if pathToFollow.count > 1 {
                      let dest = pathToFollow[1]
                      let oriact = SCNAction.move(to: dest, duration: 0.8)
                      orientationActions.append(oriact)
                  }
      
                  shipActions.append(act)
      
                  // add box
                  let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
                  let boxNode = SCNNode(geometry: box)
                  boxNode.geometry?.materials.first?.diffuse.contents = NSColor.blue
                  boxNode.position = SCNVector3(Double(next.x), Double(next.y + 0.4), Double(next.z))
                  scene.rootNode.addChildNode(boxNode)
              }
          }
      
          // Animate Orientation node
          let oriSequence = SCNAction.sequence(orientationActions)
          orientationNode.runAction(oriSequence)
      
          // Animate Ship node
          let sequence = SCNAction.sequence(shipActions)
          ship.runAction(sequence) {
              print("Ship finished sequence")
          }
      
          // MARK: - View Setup
      
          // retrieve the SCNView
          let scnView = self.view as! SCNView
      
          // set the scene to the view
          scnView.scene = scene
      
          // show statistics such as fps and timing information
          scnView.showsStatistics = true
      
          // configure the view
          scnView.backgroundColor = NSColor.black
      
      }
      

    对于要遵循的路径对象:

    这条路径是在搅拌机中制作的,带有 Nurbs 路径。然后导出为.obj文件。

    选项 - 重要 导出时,标记以下选项

    1. '作为 NURBS 的曲线'
    2. '保持顶点顺序'

    在文本编辑器中打开 .obj 文件并复制顶点位置,如您在 rawPath 字符串中看到的那样

    struct RoutePath {
    
    /// Transforms the `rawPath` into an array of `SCNVector3`
    static func decodePath() -> [SCNVector3] {
        
        let whole = rawPath.components(separatedBy: "\n")
        print("\nWhole:\n\(whole.count)")
        
        var vectors:[SCNVector3] = []
        
        for line in whole {
            
            let vectorParts = line.components(separatedBy: " ")
            if let x = Double(vectorParts[1]),
               let y = Double(vectorParts[2]),
               let z = Double(vectorParts[3]) {
                
                let vector = SCNVector3(x, y, z)
                print("Vector: \(vector)")
                
                vectors.append(vector)
            }
        }
        
        return vectors
    }
    
    static var rawPath:String {
        """
        v 26.893915 -4.884228 49.957905
        v 26.893915 -4.884228 48.957905
        v 26.893915 -4.884228 47.957905
        v 26.901930 -4.884228 46.617016
        v 26.901930 -4.884228 45.617016
        v 26.901930 -4.884228 44.617016
        v 26.901930 -4.884228 43.617016
        v 26.901930 -4.884228 42.617016
        v 26.901930 -4.884228 41.617016
        v 26.901930 -4.884228 40.617016
        v 26.901930 -4.884228 39.617016
        v 26.391232 -4.884228 38.617016
        v 25.574114 -4.884228 37.617016
        v 25.046391 -4.884228 36.617016
        v 24.552715 -4.884228 35.617016
        v 24.365459 -4.884228 34.617016
        v 24.365459 -4.884228 33.617016
        v 24.314390 -4.884228 32.617016
        v 24.212250 -4.884228 31.617016
        v 24.110109 -4.884228 30.617016
        v 23.995176 -4.884228 29.617016
        v 23.913080 -4.884228 28.617016
        v 23.814566 -4.884228 27.617016
        v 24.356396 -4.884228 26.978235
        v 25.356396 -4.884228 26.978235
        v 26.356396 -4.884228 26.978235
        v 27.356396 -4.736906 26.978235
        v 28.356396 -4.549107 26.978235
        v 29.356396 -4.549107 26.978235
        """
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-29
      • 2012-10-29
      • 1970-01-01
      • 2023-03-25
      • 2018-05-23
      • 2014-02-05
      • 1970-01-01
      相关资源
      最近更新 更多