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

如何用UITapGestureRecognizer在SCNView中移动SCNNode?位置异常求助

解决SCNNode点击移动位置不准确的问题

问题分析

你当前代码的核心错误在于:

  • 使用result.simdModelTransform获取的是被点击节点的局部模型变换,这个坐标是相对于该节点父节点的局部位置,不是世界空间的点击位置。如果点击的是场景中的其他物体,拿到的是那个物体的局部坐标,自然会导致你的节点移动到错误位置。

解决方案

根据你的需求,分两种场景给出修复代码:

场景1:将节点移动到点击物体的世界位置(保留原Y高度)

直接使用SCNHitTestResult的worldPosition属性,它返回的是点击点在世界空间的坐标:

@objc func handleTapGesture(_ gesture: UITapGestureRecognizer) {
    let location = gesture.location(in: scnView)
    
    guard let result = self.scnView?.hitTest(location, options: nil).first else { return }
    // 获取点击点的世界空间坐标
    let worldPos = result.worldPosition
    // 保留节点原Y高度,替换X、Z坐标
    let newPosition = SCNVector3(worldPos.x, node.position.y, worldPos.z)
    
    node.position = newPosition
}

场景2:将节点移动到指定平面(比如Y=0的地面)的点击位置

如果需要点击空白区域也能让节点移动到地面对应位置,可通过计算射线与平面的交点实现:

@objc func handleTapGesture(_ gesture: UITapGestureRecognizer) {
    let location = gesture.location(in: scnView)
    
    // 将屏幕坐标转换为近、远平面的3D点,构建射线
    let nearPoint = scnView.unprojectPoint(SCNVector3(location.x, location.y, 0))
    let farPoint = scnView.unprojectPoint(SCNVector3(location.x, location.y, 1))
    
    // 计算射线与Y=0平面的交点(可根据你的地面高度修改Y值)
    let t = -nearPoint.y / (farPoint.y - nearPoint.y)
    let targetX = nearPoint.x + t * (farPoint.x - nearPoint.x)
    let targetZ = nearPoint.z + t * (farPoint.z - nearPoint.z)
    
    // 设置节点位置,Y可保留原高度或设为平面高度
    node.position = SCNVector3(targetX, node.position.y, targetZ)
}

额外优化:限制点击检测范围

如果只想让点击地面时触发移动,可给地面节点设置categoryBitMask,并在hitTest中指定检测该类别:

// 先给地面节点设置分类位
groundNode.categoryBitMask = 1 << 0;

// 修改手势处理代码
@objc func handleTapGesture(_ gesture: UITapGestureRecognizer) {
    let location = gesture.location(in: scnView)
    
    // 只检测categoryBitMask为1<<0的节点
    let hitOptions: [SCNHitTestOption: Any] = [
        .categoryBitMask: 1 << 0,
        .searchMode: SCNHitTestSearchMode.all.rawValue
    ]
    
    guard let result = self.scnView?.hitTest(location, options: hitOptions).first else { return }
    let worldPos = result.worldPosition
    node.position = SCNVector3(worldPos.x, node.position.y, worldPos.z)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 11:10:22