如何让SCNNode以固定锚点旋转?SceneKit球体半径旋转求助
解决SceneKit中圆柱锚定球心并指向球面的旋转问题
我帮你梳理下代码里的问题,一步步解决这个圆柱固定球心、旋转时末端指向球面任意点的需求:
1. 先修正明显的变量名笔误
你代码里定义了cylinder(圆柱),但初始化节点时写成了SCNNode(geometry: cone),后续又用了conenode,这是典型的拼写错误,得统一改成cylinder和cylindernode,不然运行会报错:
// 修正前错误代码 let cylindernode = SCNNode(geometry: cone) sceneView.scene?.rootNode.addChildNode(conenode) // 修正后 let cylindernode = SCNNode(geometry: cylinder) // 后面添加节点时也要对应改成cylindernode
2. 调整圆柱的锚点,让一端固定在球心
默认情况下,SCNCylinder的锚点在它的中心位置,所以直接放在球心的话,圆柱会一半在球内一半在球外。我们需要把锚点移到圆柱的一端,这样当圆柱位置和球心重合时,它的一端就牢牢固定在球心:
// 把圆柱的锚点平移到底部(Y轴负方向移动高度的一半) cylindernode.pivot = SCNMatrix4MakeTranslation(0, -cylinder.height/2, 0)
3. 将圆柱节点挂载为球节点的子节点
这样做的好处是,圆柱会自动继承球节点的位置,不用手动同步球心坐标,而且旋转时会以球心为中心转动,完美符合你的需求:
// 不要把圆柱加到rootNode,而是加到spherenode spherenode.addChildNode(cylindernode) // 此时圆柱的位置设为原点即可,因为会继承球节点的位置 cylindernode.position = SCNVector3Zero
4. 优化旋转动作,让末端能遍历球面任意点
原来的单次90度旋转肯定达不到“指向任意点”的效果,我们可以创建组合旋转动作,让圆柱同时绕X轴和Y轴循环旋转,这样末端就能覆盖整个球面:
// 修正角度转换函数,返回Float更适配SceneKit的API func degreesToRadians(_ degrees: Float) -> Float { return degrees * .pi / 180 } // 创建两个旋转动作,组合后无限循环 let rotateX = SCNAction.rotate(by: degreesToRadians(360), around: SCNVector3(1, 0, 0), duration: 10) let rotateY = SCNAction.rotate(by: degreesToRadians(360), around: SCNVector3(0, 1, 0), duration: 15) let rotateLoop = SCNAction.repeatForever(SCNAction.group([rotateX, rotateY])) // 给圆柱节点添加动作 cylindernode.runAction(rotateLoop)
完整修正后的代码
struct SceneKitView: UIViewRepresentable { func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView { // 初始化SceneView let sceneView = SCNView() sceneView.scene = SCNScene() sceneView.allowsCameraControl = true sceneView.autoenablesDefaultLighting = true sceneView.backgroundColor = UIColor.white // 修正frame,原来的尺寸会导致视图不可见,改成适配屏幕 sceneView.frame = CGRect(x: 0, y: 10, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height - 10) // 创建作为球面材质的SKScene let surfacematerial = SKScene(size: CGSize(width: 300, height: 200)) let point = SKShapeNode(circleOfRadius: 2) surfacematerial.backgroundColor = SKColor.blue point.fillColor = SKColor.red point.lineWidth = 0 point.position = CGPoint(x: surfacematerial.size.width/2.0, y: surfacematerial.size.height/2.0) surfacematerial.addChild(point) // 创建球体节点 let sphereRadius: CGFloat = 2 let sphere = SCNSphere(radius: sphereRadius) let spherematerial = SCNMaterial() spherematerial.diffuse.contents = surfacematerial sphere.materials = [spherematerial] let spherenode = SCNNode(geometry: sphere) spherenode.position = SCNVector3(x: 1.0, y: 1.0, z: 1.0) spherenode.opacity = 0.6 sceneView.scene?.rootNode.addChildNode(spherenode) // 创建圆柱(模拟球体半径) let cylinder = SCNCylinder(radius: 0.02, height: sphereRadius) // 高度和球体半径一致 let cylindernode = SCNNode(geometry: cylinder) cylinder.firstMaterial?.diffuse.contents = UIColor.green // 调整圆柱锚点到一端,固定在球心 cylindernode.pivot = SCNMatrix4MakeTranslation(0, -cylinder.height/2, 0) // 挂载为球节点的子节点,位置设为原点 cylindernode.position = SCNVector3Zero spherenode.addChildNode(cylindernode) // 角度转换工具函数 func degreesToRadians(_ degrees: Float) -> Float { return degrees * .pi / 180 } // 添加循环旋转动作,让圆柱末端遍历球面 let rotateX = SCNAction.rotate(by: degreesToRadians(360), around: SCNVector3(1, 0, 0), duration: 10) let rotateY = SCNAction.rotate(by: degreesToRadians(360), around: SCNVector3(0, 1, 0), duration: 15) let rotateLoop = SCNAction.repeatForever(SCNAction.group([rotateX, rotateY])) cylindernode.runAction(rotateLoop) return sceneView } func updateUIView(_ uiView: SCNView, context: UIViewRepresentableContext<SceneKitView>) { } typealias UIViewType = SCNView }
额外小优化
- 我把球体半径抽成了变量
sphereRadius,这样修改半径时,圆柱高度会自动匹配,不用手动改两处。 - 修正了SceneView的frame,原来的width=0、height=1会导致视图完全看不到,改成了适配屏幕的尺寸。
内容的提问来源于stack exchange,提问作者AtharvSalokhe
相关产品推荐
相关产品推荐

