沿路径的 SceneKit 动画节点
SceneKit animate node along path
我有一个盒子节点
_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]];
我也遇到了这个问题,我写了一个小游乐场。动画效果很好。需要做一件事。必须计算每个点之间的距离,以便可以缩放时间以获得流畅的动画。只需将代码复制并粘贴到操场上即可。代码在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
这里我做了一个关于如何在搅拌机中创建 NURBS 路径的快速教程,然后有一个对象跟随它(在本例中是 Xcode 中默认新项目代码附带的船。
你也可以在我的gist here
下找到这个
需要考虑的事项
移动。: 你需要space [SCNVector]
中的点
点。:一个物体向前移动,因此原始物体(船)可以跟随,尊重路径的方向。
方框只是为了说明路径。它们可以被移除
在RoutePath
中查看如何导出 NURBS
这是一个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
文件。
选项 - 重要
导出时,勾选如下选项
- 'curves as NURBS'
- 'keep vertex order'
在文本编辑器中打开 .obj
文件并复制顶点位置,如您在 rawPath String
中所见
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
"""
}
}
我有一个盒子节点
_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]];
我也遇到了这个问题,我写了一个小游乐场。动画效果很好。需要做一件事。必须计算每个点之间的距离,以便可以缩放时间以获得流畅的动画。只需将代码复制并粘贴到操场上即可。代码在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
这里我做了一个关于如何在搅拌机中创建 NURBS 路径的快速教程,然后有一个对象跟随它(在本例中是 Xcode 中默认新项目代码附带的船。
你也可以在我的gist here
下找到这个需要考虑的事项
移动。: 你需要space [SCNVector]
中的点点。:一个物体向前移动,因此原始物体(船)可以跟随,尊重路径的方向。
方框只是为了说明路径。它们可以被移除
在
中查看如何导出 NURBSRoutePath
这是一个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
文件。
选项 - 重要 导出时,勾选如下选项
- 'curves as NURBS'
- 'keep vertex order'
在文本编辑器中打开 .obj
文件并复制顶点位置,如您在 rawPath String
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
"""
}
}