[英]SceneKit animate node along path
I have a box node我有一个盒子节点
_boxNode = [SCNNode node];
_boxNode.geometry = [SCNBox boxWithWidth:1 height:1 length:1 chamferRadius:0];
_boxNode.position = SCNVector3Make(0, 0, -2);
[scene.rootNode addChildNode:_boxNode];
I have a path我有一条路
CGPathRef path = CGPathCreateWithEllipseInRect(CGRectMake(-2, -2, 4, 4), nil);
I want to have my box travel along my path once.我想让我的盒子沿着我的路径行进一次。
How do I do this in SceneKit?我如何在 SceneKit 中执行此操作?
I'd like to make a method that would look like我想做一个看起来像的方法
[_boxNode runAction:[SCNAction moveAlongPath:path forDuration:duration]];
I came across that question as well and I wrote a little playground. 我也遇到了这个问题,我写了一个小游乐场。 The animation works well.
动画效果很好。 One thing needs to be done.
有一件事需要做。 The distance between every point has to be calculated so that time can be scaled to get a smooth animation.
必须计算每个点之间的距离,以便可以缩放时间以获得平滑的动画。 Just copy & paste the code into a playground.
只需将代码复制并粘贴到游乐场即可。 The code is in Swift 3.
代码在Swift 3中。
Here is my solution (the BezierPath extension is not from me, found it here): 这是我的解决方案(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
Here I made a quick tutorial on how to create a NURBS path in blender, and then have an object to follow it (in this case the ship that comes with the default new project code in Xcode.在这里,我做了一个关于如何在搅拌机中创建 NURBS 路径的快速教程,然后有一个 object 跟随它(在这种情况下,默认新项目代码 Xcode 中的船。
You can also find this under mygist here你也可以在这里找到我的要点
Things to consider需要考虑的事情
Move.: You need the points in space [SCNVector]移动:你需要空间中的点 [SCNVector]
Point.: One object to move ahead, so the original object (ship) can follow, respecting the orientation of the path. Point.: 一个 object 向前移动,所以原来的 object (船)可以跟随,尊重路径的方向。
The boxes are just for illustration of the path.这些框仅用于说明路径。 They can be removed
它们可以被移除
See how to export NURBS in RoutePath
查看如何在
RoutePath
中导出 NURBS
This is an XCode project that comes when you start a new project -> game -> Swift -> SceneKit这是一个 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 }
For the Path Object to follow:对于要遵循的路径 Object:
This path was made in blender, with a Nurbs Path.这条路径是在搅拌机中制作的,带有 Nurbs 路径。 Then it was exported as
.obj
file.然后将其导出为
.obj
文件。
OPTIONS - IMPORTANT When exporting, mark the following options选项 - 重要导出时,标记以下选项
Open the .obj
file in text editor and copy the vertex positions, as you see in the rawPath String在文本编辑器中打开
.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
"""
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.