You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
}

关键细节说明

  1. ID提取逻辑优化:原代码直接遍历id_array会导致重复处理节点,现在我们过滤空字符串,只提取第一个有效的数字ID,避免逻辑错误。
  2. 旋转中心对齐:将容器的position设置为目标块相邻面的中心,这样容器旋转时,所有子块都会围绕这个点转动,完全符合你想要的旋转效果。
  3. 坐标系转换:将块从根节点移到容器时,必须用convertTransform(_:from:)把世界坐标转换为容器的局部坐标,否则块的位置会错乱。旋转完成后再转换回世界坐标,保证块在根节点中的位置正确。
  4. 动画支持:用SCNTransaction添加旋转动画,让操作更流畅自然,同时在动画完成后再清理容器和移回块,避免视觉卡顿。

可灵活调整的点

  • 如果需要绕X轴旋转(对应另一个方向的扭转),只需修改rotationAxis为SCNVector4(1, 0, 0, 0)即可。
  • 如果块的连接方向不是Z轴,需要重新计算rotationCenter,比如X轴连接的话,旋转中心就是targetNode.position.x - bounds_x / 2。

内容的提问来源于stack exchange,提问作者Giovanni Piliego

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 15:17:42