【问题标题】:How to rotate a SCNNode by multiple angles?如何将 SCNNode 旋转多个角度?
【发布时间】:2025-12-22 05:50:06
【问题描述】:

我在SCNView 中有一个圆柱体作为SCNCylinder。我想将圆柱体旋转多个给定的角度。我正在使用 SwiftUI 提供所需的输入(旋转角度)。假设我给它一个输入 90° 。目前它可以轻松旋转 90°,但如果我给它第二个输入 180°,它会回到原来的位置,然后旋转 180°。我希望它从旋转 90° 后的位置旋转 180°。

这是我的代码:

struct ContentView: View {
 @State var rotationAngle: Float = 0
 var body: some View {

    VStack{

        Text("180°").onTapGesture {
            self.rotationAngle = 180.0
        }

        Divider()

        Text("90°").onTapGesture {
            self.rotationAngle = 90.0
        }

        SceneKitView(angle: $rotationAngle)
            .position(x: 225.0, y: 175)
            .frame(width: 300, height: 300, alignment: .center)

    }
  }    
}

struct SceneKitView: UIViewRepresentable {
  @Binding var angle: Float


func degreesToRadians(_ degrees: Float) -> CGFloat {
    return CGFloat(degrees * .pi / 180)
}

func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {

    let sceneView = SCNView()
    sceneView.scene = SCNScene()
    sceneView.allowsCameraControl = true
    sceneView.autoenablesDefaultLighting = true
    sceneView.backgroundColor = UIColor.white
    sceneView.frame = CGRect(x: 0, y: 10, width: 0, height: 1)

    return sceneView
}

func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {

        sceneView.scene?.rootNode.enumerateChildNodes { (node, stop) in
            node.removeFromParentNode() }


        let cylinder = SCNCylinder(radius: 0.02, height: 2.0)
        let cylindernode = SCNNode(geometry: cylinder)
        cylindernode.position = SCNVector3(x: 0, y: 0, z: 0)
        cylinder.firstMaterial?.diffuse.contents = UIColor.green


        cylindernode.pivot = SCNMatrix4MakeTranslation(0, -1, 0)




        let rotation = SCNAction.rotate(by: self.degreesToRadians(self.angle),
                                        around: SCNVector3(1, 0, 0), duration: 5)



         cylindernode.runAction(rotation)


        sceneView.scene?.rootNode.addChildNode(cylindernode)

   }      

无论我给它多少角度,我都希望它能够正确旋转。

【问题讨论】:

    标签: rotation swiftui scenekit


    【解决方案1】:

    明确地说,您希望每次按下按钮时圆柱体都从当前位置继续旋转让圆柱体转到固定的 90 或 180 位置。

    因为您当前的代码似乎已设置为执行第二件事(rotate:by 除外)。旋转角度保持为固定状态。确实,您应该向节点发送旋转命令,而不会将其旋转的量保留在状态变量中。

    但是,使用您现在拥有的代码:

    updateUIView 中坚持只更新你的节点。现在,每次rotationAngle 更改时,您都在拆除整个节点层次结构并将其重新创建到其原始位置。这可能就是为什么事情正在重绘。 而是在 makeUIViewSceneKitView 的初始化程序中进行设置,在 updateUIView 中只应用新的旋转。

    第二个问题是,当你通过按下按钮设置rotationAngle 说 180 时,如果再次按下相同的按钮,值再次设置为 180。就 SwiftUI 而言,该值没有改变,无需致电updateUIView。有几种方法可以让它更新,但一种快速简单的方法可能是先旋转 0,然后再旋转你想要的角度。 但是你真的不应该在状态变量中保持这个角度。最好在SceneKitView 中创建一个rotateCylinder(angle: Angle) 函数并从按钮调用它。

    另外,为什么不使用新的Angle 类型呢?这是您的代码,其中包含一些更改。我认为这是你想要做的,但我可能是错的。

    import SwiftUI
    import UIKit
    import SceneKit
    
    struct ContentView: View {
    
      @State var rotationAngle: Angle = .zero
    
      var body: some View {
    
        VStack{
    
          HStack {
    
            Spacer()
            Text("180°").onTapGesture {
              self.rotationAngle = .zero
              self.rotationAngle = .degrees(180)
            }
            Spacer()
    
            Divider()
    
            Spacer()
            Text("90°").onTapGesture {
              self.rotationAngle = .zero
              self.rotationAngle = .degrees(90)
            }
            Spacer()
    
            }.frame(height: 100).padding()
    
          SceneKitView(radius: 0.2, height: 2, angle: $rotationAngle)
    
        }
      }
    }
    
    struct SceneKitView: UIViewRepresentable {
    
      @Binding var angle: Angle
    
      let node: SCNNode
    
      init(radius: CGFloat, height: CGFloat, angle: Binding<Angle>) {
    
        let cylinder = SCNCylinder(radius: radius, height: height)
        cylinder.firstMaterial?.diffuse.contents = UIColor.green
        self.node = SCNNode(geometry: cylinder)
    
        self._angle = angle
      }
    
      func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
    
        let sceneView = SCNView()
        sceneView.scene = SCNScene()
        sceneView.autoenablesDefaultLighting = true
        sceneView.scene?.rootNode.addChildNode(node)
    
        return sceneView
      }
    
      func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
        print("Updating view \(angle.degrees)")
    
        // To continue rotating
        let rotation = SCNAction.rotate(by: CGFloat(angle.radians), around: SCNVector3(1, 0, 0), duration: 3)
    
        // To go to a fixed angle state
        //let rotation = SCNAction.rotate(toAxisAngle: SCNVector4(1, 0, 0, angle.radians), duration: 3)
    
        node.runAction(rotation)
    
      }
    
    }
    

    【讨论】:

    • 非常感谢您的回复。您的解决方案确实有效。我只是想问你这个问题的一个稍微不同的变体。我想围绕两个不同的轴旋转每个角度(180° 和 90°),比如围绕 SCNVector(1,0,0)旋转 180°,围绕(0,0,1)旋转 90°。我真的很难找到解决方法。有没有办法一样?
    • 如果您只想在角度为 180 时将枢轴旋转为 (1, 0, 0),您可以在 updateUIView 中使用 if 语句并检查是 180 还是 90 或其他值。现在,如果您想要另一个是枢轴变量,它并不像看起来那么简单。您可以为其创建另一个状态变量(这次它应该是一个状态)。但是,视图将在枢轴更改时更新。我不认为这是你想要的。您必须使整个视图符合Equatable 才能仅在角度发生变化而不是枢轴时更新。
    • 感谢您的回复。您的解决方案有效。但是当我使用self.rotationAngle = .zero 时,整个事情就崩溃了。不知道为什么会这样。它运行,但当我删除它时它不会更新视图。
    • 代码和你推荐给我的一样。如果您在 Playground 中运行它,我会收到错误消息“运行此 Playground 时遇到问题。请检查您的代码是否有错误。”
    • 如果您在 Playground 中尝试运行某些东西时遇到这样的错误,请在项目中尝试一下,看看会发生什么。