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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:48:21