返回

SK3DNode碰撞检测:问题分析与解决方案

IOS

SK3DNode 碰撞检测问题分析与解决

在 SpriteKit 中使用 SK3DNode 集成 SceneKit 场景是一个常见的需求。当需要与 3D 场景中的节点进行交互时,碰撞检测便显得至关重要。但是,有时会遇到 hitTest 方法无法正确找到对应 SCNNode 的问题。本文将分析这个问题的原因,并提供几种可能的解决方案。

问题

问题集中在使用 SK3DNodehitTest 函数来识别在 SCNScene 中的 SCNNode 时候失效。尽管触摸位置的坐标看起来正确,但 hitTest 始终无法找到点击的节点。这种现象会导致交互功能无法正常使用。

可能的原因分析

  1. 坐标系转换问题 : SK3DNode 有其自身的坐标系,触摸事件发生时,获取的是相对于 SKView 的坐标。这个坐标需要转换到 SCNScene 坐标系中才能进行准确的碰撞检测。如果坐标转换不正确,hitTest 无法在正确的区域内搜索节点。
  2. SK3DNode 的尺寸和位置 : SK3DNodeviewportSizeposition 属性直接影响 3D 场景在 2D 屏幕上的呈现和交互。如果这些属性设置不当,会导致触摸位置与 3D 场景中的对象不对应,从而使 hitTest 失败。
  3. 相机设置 : 相机的位置和朝向定义了观察 3D 场景的视角。如果相机设置不正确,例如位置过远或者方向错误,可能导致 3D 对象在屏幕上的投影与预期不符,影响碰撞检测的结果。
  4. 节点层级结构问题 : 如果要检测的 SCNNode 位于错误的层级下,也可能会导致hitTest失败。
  5. isUserInteractionEnabled 没有开启 SK3DNode 或者它父节点的isUserInteractionEnabled属性没有设置为 true.

解决方案

方案 1:确保坐标系转换正确

需要将触摸点从 SKView 坐标系转换到 SCNScene 坐标系。可以使用 SK3DNodeconvertPoint(_:to:) 方法来进行坐标转换。

操作步骤:

  1. 获取触摸点在 SKView 坐标系下的坐标。
  2. 将触摸点坐标转换为 SK3DNode 坐标系下的坐标。
  3. 使用转换后的坐标进行 hitTest

代码示例:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else { return }
    let locationInView = touch.location(in: self.view) // 获取在 SKView 中的位置
    let locationInSKNode = self.convertPoint(fromView: locationInView) // 转换到 SKScene 坐标
    let locationInSK3DNode = convert(locationInSKNode, to: node3D) //转换到 SK3DNode的坐标
    let hitResults = node3D.hitTest(locationInSK3DNode, options: nil)

    if !hitResults.isEmpty {
        // 处理碰撞结果
        print("Hit node: \(hitResults.first?.node)")
    } else {
        print("No node hit.")
    }
}

方案 2:调整 SK3DNode 的尺寸和位置

确保 SK3DNodeviewportSizeSCNScene 的比例匹配,并且 SK3DNodeposition 正确设置在 SKScene 中。可以尝试调整 viewportSize,使其与 SCNScene 的内容比例一致。同时,检查 SK3DNodeposition 是否正确,避免遮挡或偏移。

操作步骤:

  1. 检查 SCNScene 的内容范围。
  2. 设置 SK3DNodeviewportSize 以匹配内容范围的宽高比。
  3. 调整 SK3DNodeposition,使其在 SKScene 中正确显示。

代码示例:

node3D.viewportSize = CGSize(width: 200, height: 400) // 根据 SCNScene 内容调整尺寸
node3D.position = CGPoint(x: 100, y: 200) // 调整 SK3DNode 在 SKScene 中的位置

方案 3:优化相机设置

检查相机的位置、朝向和视野范围。确保相机能够正确地观察到 3D 场景中的对象,并且视野范围覆盖了所有需要交互的节点。如果相机的位置过远或朝向错误,可能导致无法正确检测到碰撞。

操作步骤:

  1. 调整相机的位置,使其能够清晰地看到 3D 场景中的对象。
  2. 调整相机的朝向,使其对准需要交互的节点。
  3. 调整相机的视野范围,确保所有需要交互的节点都在视野内。

代码示例:

cameraNode.position = SCNVector3(x: 5, y: 5, z: 15) // 调整相机位置
cameraNode.eulerAngles = SCNVector3(x: -Float.pi/6, y: 0, z: 0) // 调整相机角度
camera.fieldOfView = 45 // 调整视野范围

或者通过正交投影来设置一个更大的可见范围:

camera.usesOrthographicProjection = true
camera.orthographicScale = 5 // adjust this value to fit the scene within the SK3DNode
camera.zNear = 0
camera.zFar = 100

方案 4:检查节点层级结构

检查目标 SCNNode 的层级结构,确认其是否被添加到正确的父节点下。如果节点位于错误的层级下,可能会导致 hitTest 无法找到它。
操作步骤:
确认父节点的范围包含所有的子节点。

代码示例:
确认box 在 playground中

playground.addChildNode(box)

并且确认playground在scene的 rootnode中.

rootNode.addChildNode(playground)

方案 5: 确认 UserInteractionEnabled 设置正确

确认 SK3DNode 和它的父节点是否开启了isUserInteractionEnabled. 默认该值是 false.

操作步骤:

将所有的相关的父节点的 isUserInteractionEnabled设置为 True.

node3D.isUserInteractionEnabled = true

其他建议

  • 使用 SceneKit 编辑器来可视化场景结构和相机设置,有助于发现潜在问题。
  • 简化场景,逐步添加复杂元素,以便更容易地定位问题所在。
  • 添加调试代码,输出触摸位置和碰撞检测结果,帮助理解坐标转换和碰撞检测的过程。

通过仔细检查坐标系转换、尺寸位置、相机设置,并确保目标节点在正确的层级结构中,大多数 SK3DNodehitTest 问题都可以得到解决。如果问题依然存在,可以尝试简化场景并逐步调试,以便更容易地找到根本原因。