SceneKit - 旋转和动画一个SCNNode

17

我想展示一个沿z轴指向的金字塔,并在z轴周围自转。

由于我的相机在z轴上,我希望从上面看到金字塔。我成功地旋转了金字塔以这种方式查看它,但当我添加动画时,它似乎会在多个轴上旋转。

以下是我的代码:

// The following create the pyramid and place it how I want
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)
pyramidNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: Float(M_PI / 2))
scene.rootNode.addChildNode(pyramidNode)

// But the animation seems to rotate aroun 2 axis and not just z
var spin = CABasicAnimation(keyPath: "rotation")
spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(M_PI)))
spin.duration = 3
spin.repeatCount = HUGE
pyramidNode.addAnimation(spin, forKey: "spin around")
3个回答

18

尝试手动设置和动画相同属性可能会导致问题。使用byValue动画会使问题变得更加复杂--这会连接到当前的变换,因此更难跟踪当前的变换是否是动画期望的起始状态。

相反,将金字塔的固定方向(其顶点朝向-z方向)与动画分离开来(它围绕指向轴旋转)。有两种好的方法可以做到这一点:

  1. pyramidNode作为另一个节点的子级,该节点获得一次性旋转(π/2绕x轴),并直接对pyramidNode应用旋转动画。(在这种情况下,金字塔的顶点仍将指向其局部空间的+y方向,因此您需要围绕该轴而不是z轴旋转。)

  2. 使用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 (或传递该表达式的类型)来使错误消失。当然,这会使您的代码难以阅读和丑陋,因此,一旦使其正常工作,您可能会开始一次删除一个转换,直到找到完成工作的转换为止。


谢谢rickster,这正是我在寻找的。关于使用CGFloat还有一件事。我开始使用它们,但是Xcode报了一个编译错误“'Int'不可转换为'Float'”,因此我将它们更改为Float。有什么想法吗? - Newalp
哇!Rickster做得很好,使用了我的编辑答案。检查时间戳! - FlippinFun
请参阅编辑后的答案,了解有关 CGFloat 转换问题的更多信息。@FlippinFun:我没有看到你的编辑答案。 :) 我花了超过8分钟来确保我的代码解决了“方向与旋转轴不同”的问题,并进行了清理和测试。 - rickster
谢谢rickster,但我认为我与CGFloat的问题与SCNMatrix4MakeRotation使用Float而不是CGFloat作为参数有关。我不明白为什么,因为我已经看到许多示例在此函数中使用CGFloat。https://developer.apple.com/library/prerelease/iOS/documentation/SceneKit/Reference/SceneKit_Functions/index.html#//apple_ref/c/func/SCNMatrix4MakeRotation - Newalp
@rickster,你能回答一下这个问题吗?https://dev59.com/QaPia4cB1Zd3GeqP6vce - Dhara
显示剩余2条评论

15

SceneKit包含动画助手,比CAAnimations更简单且使用更短。这是ObjC,但能传达要点:

[pyramidNode runAction:
    [SCNAction repeatActionForever:
        [SCNAction rotateByX:0 y:0 z:2*M_PI duration:3]]];

2
动作并不是动画的快捷方式,它们在SceneKit渲染架构的不同部分生效。有时这种差异很重要,因此您不能总是可以互换使用动作和动画。但如果对您的应用程序没有影响,那就直接使用吧。 :) - rickster
@rickster 有没有什么地方可以了解更多的区别?我现在不确定何时应该使用其中之一。 - Graham Perks
2014年苹果全球开发者大会(SceneKit讲座)是一个不错的开始。否则这个主题太大了,无法在评论中涵盖,但它可以成为一个好问题。 - rickster

0
我把 byValue 改成了 toValue,这对我起作用了。所以请更改这一行...
spin.byValue = NSValue(SCNVector4: SCNVector4(...

改成......

spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y:0, z:1, w: 2*float(M_PI))

那行代码是调用 NSValue 的初始化器 init(SCNVector4 v: SCNVector4)。你的更改无法编译。 - rickster
Swift要求所有初始化器参数都有标签,除非初始化器的声明指定“_”作为标签。NSValue没有声明init(_ v: SCNVector4) - rickster
你是对的rickster。我用toValue让它工作了。我改变了我的答案。 - FlippinFun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接