尝试手动设置和动画相同的属性可能会导致问题。使用byValue 动画会使问题变得更糟——它会连接到当前变换,因此更难跟踪当前变换是否是动画预期开始的内容。
相反,将金字塔的固定方向(其顶点位于 -z 方向)与动画(围绕其指向的轴旋转)分开。有两种好方法可以做到这一点:
使pyramidNode 成为另一个节点的子节点,该节点获得一次性旋转(绕x 轴π/2),并将旋转动画直接应用到pyramidNode。 (在这种情况下,金字塔的顶点仍将指向其局部空间的 +y 方向,因此您需要围绕该轴而不是 z 轴旋转。)
使用pivot 属性转换pyramidNode 内容的本地空间,并相对于其包含空间为pyramidNode 设置动画。
这里有一些代码来展示第二种方法:
let pyramid = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
let pyramidNode = SCNNode(geometry: pyramid)
pyramidNode.position = SCNVector3(x: 0, y: 0, z: 0)
// Point the pyramid in the -z direction
pyramidNode.pivot = SCNMatrix4MakeRotation(CGFloat(M_PI_2), 1, 0, 0)
scene.rootNode.addChildNode(pyramidNode)
let spin = CABasicAnimation(keyPath: "rotation")
// Use from-to to explicitly make a full rotation around z
spin.fromValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 0))
spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: CGFloat(2 * M_PI)))
spin.duration = 3
spin.repeatCount = .infinity
pyramidNode.addAnimation(spin, forKey: "spin around")
一些不相关的更改以提高代码质量:
- 当需要显式转换来初始化
SCNVector 组件时,使用CGFloat;专门使用 Float 或 Double 会破坏 32 位或 64 位架构。
- 使用
.infinity 代替传统的BSD 数学常数HUGE。此类型推断 spin.repeatCount 的任何类型,并使用为所有浮点类型定义的常量值。
- 使用
M_PI_2 表示 π/2 对精确度很迂腐。
- 对动画使用
let 而不是var,因为我们从不为spin 分配不同的值。
关于CGFloat 错误业务的更多信息:在 Swift 中,数字文字只有在它们所在的表达式需要类型时才具有类型。这就是为什么你可以做类似spin.duration = 3 的事情——即使duration 是一个浮点值,Swift 允许你传递一个“整数文字”。但是如果你这样做 let d = 3; spin.duration = d 你会得到一个错误。为什么?因为变量/常量具有显式类型,而 Swift 不进行隐式类型转换。 3 是无类型的,但是当它被分配给d 时,类型推断默认选择Int,因为您没有指定任何其他内容。
如果您看到类型转换错误,您的代码可能混合了文字、常量和/或函数返回的值。您可以通过将表达式中的所有内容转换为CGFloat(或您将该表达式传递给的任何类型)来消除错误。当然,这会使您的代码不可读且难看,因此一旦您开始使用它,您可能会开始一次删除一个转换,直到找到可以完成这项工作的那个。