SwiftUI中SceneKit场景触摸特定节点时的缩放实现方法
嘿,我来帮你搞定这个触摸SceneKit节点后缩放聚焦的需求!其实核心就是两步:先检测用户点击的目标节点,再让相机平滑动画到合适的观察位置。下面是具体的实现方案和优化建议:
实现触摸节点聚焦缩放的完整方案
1. 改造你的HouseView,添加点击检测和相机动画
我们需要给UIViewRepresentable加一个Coordinator来处理UIKit的手势事件,同时实现相机平滑移动的逻辑:
import SwiftUI import SceneKit struct HouseView : UIViewRepresentable { // 用Coordinator来处理SCNView的交互代理逻辑 class Coordinator: NSObject { var parent: HouseView init(parent: HouseView) { self.parent = parent } // 处理点击手势 @objc func handleTap(_ gesture: UITapGestureRecognizer) { guard let scnView = gesture.view as? SCNView else { return } let tapLocation = gesture.location(in: scnView) // 执行点击检测,获取被点击的节点 let hitResults = scnView.hitTest(tapLocation, options: [ .boundingBoxOnly: true, // 用 bounding box 检测更高效 .searchMode: SCNHitTestSearchMode.all.rawValue ]) // 如果点击到了节点,就触发相机聚焦动画 if let hitNode = hitResults.first?.node { animateCameraToTargetNode(hitNode, in: scnView) } } // 相机平滑动画到目标节点的核心方法 private func animateCameraToTargetNode(_ targetNode: SCNNode, in scnView: SCNView) { guard let cameraNode = scnView.scene?.rootNode.childNode(withName: "CameraNode", recursively: true) else { print("找不到CameraNode!") return } // 计算相机的目标位置:在节点前方一段距离,保持合适高度 let nodeWorldPos = targetNode.worldPosition // 这里的偏移量你可以根据自己的场景调整,数值越大相机离节点越远 let targetCameraPos = SCNVector3( x: nodeWorldPos.x, y: nodeWorldPos.y + 2, // 比节点高2个单位,避免被遮挡 z: nodeWorldPos.z - 8 // 在节点的Z轴负方向(前方)8个单位 ) // 让相机自动看向目标节点的中心 let lookAtTransform = SCNLookAtTransform(nodeWorldPos) let targetCameraRotation = SCNQuaternion(lookAtTransform) // 开启SceneKit事务实现平滑动画 SCNTransaction.begin() SCNTransaction.animationDuration = 1.5 // 动画时长,可按需调整 cameraNode.position = targetCameraPos cameraNode.rotation = targetCameraRotation SCNTransaction.commit() } } // 创建Coordinator实例 func makeCoordinator() -> Coordinator { Coordinator(parent: self) } func makeUIView(context: Context) -> SCNView { let scnView = SCNView(frame: .zero) // 添加点击手势识别器,绑定到Coordinator的处理方法 let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(_:))) scnView.addGestureRecognizer(tapGesture) return scnView } func updateUIView(_ scnView: SCNView, context: Context) { guard let houseScene = SCNScene(named: "House.scn") else { print("加载House.scn失败!") return } scnView.scene = houseScene scnView.allowsCameraControl = true scnView.showsStatistics = false scnView.backgroundColor = UIColor.systemBackground scnView.defaultCameraController.maximumVerticalAngle = 10 scnView.defaultCameraController.minimumVerticalAngle = -10 scnView.defaultCameraController.maximumHorizontalAngle = 180 scnView.defaultCameraController.minimumHorizontalAngle = -10 scnView.isJitteringEnabled = true if let cameraNode = houseScene.rootNode.childNode(withName: "CameraNode", recursively: true) { cameraNode.position = SCNVector3(x: 12, y:2, z: 0) } } } struct HouseView_Previews: PreviewProvider { static var previews: some View { HouseView() } }
2. 关键功能解析
- 点击检测:通过
UITapGestureRecognizer获取用户点击位置,再用scnView.hitTest()检测对应的节点,用.boundingBoxOnly选项能提升复杂场景下的检测效率。 - 相机动画:
- 基于目标节点的世界位置计算相机的目标位置,确保视角合适(你可以根据场景调整偏移数值)。
- 用
SCNLookAtTransform自动让相机对准节点,无需手动计算旋转角度。 - 通过
SCNTransaction实现平滑的过渡动画,修改animationDuration可以调整动画快慢。
3. 进阶优化建议
- 只响应特定节点:如果只需要让某些命名的节点触发聚焦,可以在点击检测后加个判断:
if hitNode.name == "LivingRoom" || hitNode.name == "Bedroom" { animateCameraToTargetNode(hitNode, in: scnView) } - 适配不同大小的节点:如果场景里节点大小差异大,可以根据节点的包围盒动态计算相机距离:
let boundingBox = targetNode.boundingBox let boxSize = SCNVector3( x: boundingBox.max.x - boundingBox.min.x, y: boundingBox.max.y - boundingBox.min.y, z: boundingBox.max.z - boundingBox.min.z ) let maxDimension = max(boxSize.x, max(boxSize.y, boxSize.z)) let cameraDistance = maxDimension * 2 // 根据节点大小调整距离 let targetCameraPos = SCNVector3( x: nodeWorldPos.x, y: nodeWorldPos.y + maxDimension, z: nodeWorldPos.z - cameraDistance ) - 避免动画与用户操作冲突:动画过程中可以临时禁用相机控制,动画结束后再恢复:
SCNTransaction.begin() SCNTransaction.animationDuration = 1.5 scnView.allowsCameraControl = false cameraNode.position = targetCameraPos cameraNode.rotation = targetCameraRotation // 动画完成后恢复相机控制 SCNTransaction.completionBlock = { scnView.allowsCameraControl = true } SCNTransaction.commit()
内容的提问来源于stack exchange,提问作者Diego Ortiz
相关产品推荐
相关产品推荐

