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

Swift实现:检测子SCNNode点击而非父节点点击

问题解决方案:SceneKit中子节点点击检测失效

问题根源

你的场景中地球父节点的几何体遮挡了子节点(点),导致hitTest优先命中层级更高的地球节点;同时默认的hitTest配置未针对性优化,无法优先检测到点节点。

核心解决步骤

1. 优化Hit Test检测逻辑

修改touchesBegan方法,配置hitTest选项以返回所有命中节点,并优先筛选点节点:

public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, touch.view == self.sceneView else {
        return
    }

    let touchLocation = touch.location(in: sceneView)
    // 配置hitTest选项,获取所有可见节点的命中结果
    let hitTestOptions: [SCNHitTestOption: Any] = [
        .firstFoundOnly: false,
        .ignoreHiddenNodes: true,
        .boundingBoxOnly: false,
        .searchRadius: 5.0 // 扩大检测范围,适配小点的点击
    ]
    let hitTestResults = sceneView.hitTest(touchLocation, options: hitTestOptions)
    
    // 遍历结果,优先处理点节点
    for result in hitTestResults {
        let tappedNode = result.node
        switch tappedNode.name {
        case "NewYorkDot":
            print("Tapped on New York! Position: \(tappedNode.position)")
            // 这里添加坐标转换逻辑
            break
        case "Other":
            print("Tapped on a regular dot. Position: \(tappedNode.position)")
            // 这里添加坐标转换逻辑
            break
        case "EarthNode": // 跳过地球父节点
            continue
        default:
            continue
        }
        // 找到目标节点后终止遍历
        break
    }
}

2. 调整渲染顺序,让点节点优先显示

给地球节点和点节点的材质设置不同渲染顺序,确保点节点在地球前面渲染:

private func setupGlobe() {
    self.earthNode = EarthNode(radius: earthRadius, earthColor: earthColor, earthGlow: glowColor, earthReflection: reflectionColor)
    earthNode.name = "EarthNode"
    // 设置地球材质渲染顺序为0(更低层级)
    for material in earthNode.geometry!.materials {
        material.renderingOrder = 0
    }
    sceneView.scene?.rootNode.addChildNode(earthNode)
}

private func setupDotGeometry() {
    // ... 原有代码
    let dotGeometry = SCNSphere(radius: dotRadius)
    dotGeometry.firstMaterial?.diffuse.contents = dotColor
    dotGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
    dotGeometry.firstMaterial?.renderingOrder = 1 // 点节点渲染顺序高于地球
    
    let highlightGeometry = SCNSphere(radius: dotRadius)
    highlightGeometry.firstMaterial?.diffuse.contents = highlightColor
    highlightGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
    highlightGeometry.firstMaterial?.renderingOrder = 1
    
    let oceanGeometry = SCNSphere(radius: dotRadius)
    oceanGeometry.firstMaterial?.diffuse.contents = oceanColor
    oceanGeometry.firstMaterial?.lightingModel = SCNMaterial.LightingModel.constant
    oceanGeometry.firstMaterial?.renderingOrder = 1
    // ... 原有代码
}

3. 可选:禁用地球节点的交互

如果不需要地球节点的点击响应,直接禁用其交互性,彻底排除干扰:

private func setupGlobe() {
    self.earthNode = EarthNode(radius: earthRadius, earthColor: earthColor, earthGlow: glowColor, earthReflection: reflectionColor)
    earthNode.name = "EarthNode"
    earthNode.isUserInteractionEnabled = false // 禁用地球节点交互
    sceneView.scene?.rootNode.addChildNode(earthNode)
}

坐标转换提示

将SCNVector3转换为经纬度可使用球面坐标公式:

func positionToCoordinate(_ position: SCNVector3, radius: Double) -> CLLocationCoordinate2D {
    let lat = asin(Double(position.y)/radius) * 180 / .pi
    let lon = atan2(Double(position.x), Double(position.z)) * 180 / .pi
    return CLLocationCoordinate2D(latitude: lat, longitude: lon)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 14:37:05