iOS SceneKit开发:魔方扭扭乐应用中节点绕指定轴旋转问题
解决SceneKit中围绕目标块相邻面法线旋转前序块的问题
看起来你已经走对了方向——用容器节点管理要旋转的块,但问题出在容器的旋转中心没有对齐到目标块的相邻面位置,导致旋转轴不符合预期。下面是具体的修正方案和逻辑解释:
问题根源
你创建的容器节点默认是scene.rootNode的子节点,且位置为(0,0,0),所以旋转时所有子块都会围绕世界原点转动,而不是你期望的被点击块的相邻面法线轴。要解决这个问题,核心是让容器的原点刚好落在旋转中心(也就是被点击块和前序块的连接面中心),同时正确转换子块的坐标系。
修正后的完整代码
我们重新调整handleTap函数,分步骤实现正确的旋转逻辑:
@objc func handleTap(_ gestureRecognize: UIGestureRecognizer){ let sceneView = self.view as! SCNView let p = gestureRecognize.location(in: sceneView) let hitResults = sceneView.hitTest(p, options: [:]) guard hitResults.count > 0 else { return } let targetNode = hitResults[0].node // 1. 优化节点ID提取逻辑(避免原代码的重复处理问题) guard let nodeName = targetNode.name, let idStr = nodeName.components(separatedBy: CharacterSet.decimalDigits.inverted) .first(where: { !$0.isEmpty }), let targetId = Int(idStr), targetId > 0 else { return } // 2. 确定旋转中心和旋转轴(根据你的魔方布局调整) // 你的块在z轴方向相邻连接,所以旋转中心是目标块的-z面中心 let rotationCenter = SCNVector3( targetNode.position.x, targetNode.position.y, targetNode.position.z - bounds_z / 2 ) // 旋转轴:绕Y轴(垂直于x-z平面,符合魔方扭扭乐的旋转逻辑) let rotationAxis = SCNVector4(0, 1, 0, 0) let rotationAngle = Float.pi / 2 // 单次旋转90度 // 3. 创建容器节点并对齐旋转中心 let container = SCNNode() container.position = rotationCenter // 让容器原点精准落在旋转中心 // 4. 将前序块移到容器中(转换坐标系到容器局部空间) for i in 0..<targetId { guard let pieceNode = scene.rootNode.childNode(withName: "piece_\(i)", recursively: true) else { continue } // 将块的世界坐标转换为容器的局部坐标,避免位置错乱 let localTransform = container.convertTransform(pieceNode.transform, from: scene.rootNode) pieceNode.removeFromParentNode() pieceNode.transform = localTransform container.addChildNode(pieceNode) } // 5. 添加容器到场景并执行旋转(带动画提升用户体验) scene.rootNode.addChildNode(container) SCNTransaction.begin() SCNTransaction.animationDuration = 0.3 // 设置动画时长 // 叠加旋转角度:基于容器当前的旋转状态新增90度 container.rotation = SCNVector4( rotationAxis.x, rotationAxis.y, rotationAxis.z, container.rotation.w + rotationAngle ) // 6. 动画完成后将块移回根节点,清理临时容器 SCNTransaction.completionBlock = { for pieceNode in container.childNodes { // 将块的局部坐标转换回世界坐标 let worldTransform = scene.rootNode.convertTransform(pieceNode.transform, from: container) pieceNode.removeFromParentNode() pieceNode.transform = worldTransform scene.rootNode.addChildNode(pieceNode) } container.removeFromParentNode() } SCNTransaction.commit() }
关键细节说明
- ID提取逻辑优化:原代码直接遍历
id_array会导致重复处理节点,现在我们过滤空字符串,只提取第一个有效的数字ID,避免逻辑错误。 - 旋转中心对齐:将容器的
position设置为目标块相邻面的中心,这样容器旋转时,所有子块都会围绕这个点转动,完全符合你想要的旋转效果。 - 坐标系转换:将块从根节点移到容器时,必须用
convertTransform(_:from:)把世界坐标转换为容器的局部坐标,否则块的位置会错乱。旋转完成后再转换回世界坐标,保证块在根节点中的位置正确。 - 动画支持:用
SCNTransaction添加旋转动画,让操作更流畅自然,同时在动画完成后再清理容器和移回块,避免视觉卡顿。
可灵活调整的点
- 如果需要绕X轴旋转(对应另一个方向的扭转),只需修改
rotationAxis为SCNVector4(1, 0, 0, 0)即可。 - 如果块的连接方向不是Z轴,需要重新计算
rotationCenter,比如X轴连接的话,旋转中心就是targetNode.position.x - bounds_x / 2。
内容的提问来源于stack exchange,提问作者Giovanni Piliego
相关产品推荐
相关产品推荐

