iOS16下SCNLookAtConstraint与SCNDistanceConstraint失效问题求助
问题
运行环境为iOS16,尝试掌握SCNLookAtConstraint的使用,但Demo应用出现异常:点击场景时立方体节点移动,但相机节点并未跟随。已尝试在构建逻辑、Coordinator及Renderer回调中添加相机约束(包含SCNLookAtConstraint和SCNDistanceConstraint),代码能识别到约束但未生效,鼠标拖动场景和点击操作均无法让相机正常跟随。相关代码如下:
import SwiftUI import SceneKit import Combine let screenSize: CGRect = UIScreen.main.bounds let screenWidth: CGFloat = UIScreen.main.bounds.width let screenHeight: CGFloat = UIScreen.main.bounds.height let push = PassthroughSubject<Float,Never>() var pusher: AnyCancellable! struct CoreView: View { @State var scene = SCNScene() @State var place:Float = 0.0 var body: some View { CustomSceneView(scene: scene, options: []) .frame(width: screenWidth, height: screenHeight) .onTapGesture { place += 0.1 push.send(place) } } } struct ContentView: View { var body: some View { CoreView() } } struct CustomSceneView: UIViewRepresentable { var scene: SCNScene var options: [Any] var delegate: SCNRenderer! var view = SCNView() func makeUIView(context: Context) -> SCNView { view.scene = scene view.allowsCameraControl = true view.autoenablesDefaultLighting = false let cubeGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0) cubeGeometry.firstMaterial?.diffuse.contents = UIColor.red let cubeNode = SCNNode(geometry: cubeGeometry) cubeNode.name = "cubeNode" cubeNode.simdPosition = SIMD3(0,0,0) let camera = SCNCamera() camera.fieldOfView = 60.0 camera.focalLength = 100.0 camera.sensorHeight = 0.0 camera.wantsDepthOfField = true camera.focusDistance = 0.8 camera.fStop = 5.6 camera.wantsHDR = false camera.apertureBladeCount = 4 camera.motionBlurIntensity = 1.0 camera.name = "camera" let cameraNode = SCNNode() cameraNode.camera = camera cameraNode.name = "cameraNode" let lookAtConstraint = SCNLookAtConstraint(target: cubeNode) lookAtConstraint.influenceFactor = 1.0 lookAtConstraint.isGimbalLockEnabled = true view.delegate = context.coordinator scene.rootNode.addChildNode(cameraNode) view.pointOfView = scene.rootNode.childNode(withName: "cameraNode", recursively: true) view.pointOfView?.simdPosition = SIMD3<Float>(0, 0.0, 2) scene.rootNode.addChildNode(cubeNode) view.defaultCameraController.interactionMode = .fly view.defaultCameraController.inertiaEnabled = true //view.defaultCameraController.maximumVerticalAngle = 45 view.allowsCameraControl = true pusher = push.debounce(for: .seconds(0.5), scheduler: RunLoop.main) .throttle(for: 0.01, scheduler: RunLoop.main, latest: true) .sink(receiveValue: { [self] distance in cubeNode.simdPosition = SIMD3(distance,0,0) }) return view } func updateUIView(_ view: SCNView, context: Context) { print("update") } func makeCoordinator() -> Coordinator { Coordinator(view) } } class Coordinator: NSObject, SCNSceneRendererDelegate { private let view: SCNView private var cubeNode: SCNNode! private var cameraNode: SCNNode! init(_ view: SCNView) { self.view = view super.init() } func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { cameraNode = renderer.scene?.rootNode.childNode(withName: "cameraNode", recursively: true) cubeNode = renderer.scene?.rootNode.childNode(withName: "cubeNode", recursively: true) print("cubeNode \(cubeNode)") print("cameraNode \(cameraNode)") if cubeNode != nil && cameraNode != nil { let distanceConstraint = SCNDistanceConstraint(target: cubeNode) distanceConstraint.minimumDistance = 1 distanceConstraint.maximumDistance = 2 let lookAtConstraint = SCNLookAtConstraint(target: cubeNode) lookAtConstraint.influenceFactor = 1.0 lookAtConstraint.isGimbalLockEnabled = true cameraNode.constraints = [lookAtConstraint, distanceConstraint] } } }
解决方案
问题出在三个核心点,逐一修复即可解决:
- 默认相机控制与约束冲突:开启
allowsCameraControl = true后,SceneKit的默认相机控制器会直接接管相机的位置和旋转,完全覆盖自定义约束。必须将其设置为false,让约束生效。 - 重复添加约束:在
renderer(_:updateAtTime:)回调中每帧都重新创建并添加约束,既无必要还会导致逻辑混乱。应在相机节点初始化时一次性添加约束,后续无需重复操作。 - 全局Combine订阅风险:全局的
push和pusher容易引发内存泄漏或订阅异常,改用SwiftUI的Binding来传递位置变化,将订阅逻辑放在Coordinator中管理生命周期更安全。
修复后的完整代码:
import SwiftUI import SceneKit import Combine struct CoreView: View { @State private var scene = SCNScene() @State private var place: Float = 0.0 var body: some View { CustomSceneView(scene: scene, targetPosition: $place) .frame(maxWidth: .infinity, maxHeight: .infinity) .onTapGesture { place += 0.1 } } } struct ContentView: View { var body: some View { CoreView() } } struct CustomSceneView: UIViewRepresentable { let scene: SCNScene @Binding var targetPosition: Float func makeUIView(context: Context) -> SCNView { let view = SCNView() view.scene = scene view.allowsCameraControl = false // 关闭默认相机控制,让约束生效 view.autoenablesDefaultLighting = true // 开启默认光照,便于观察立方体 // 创建立方体节点 let cubeGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0) cubeGeometry.firstMaterial?.diffuse.contents = UIColor.red let cubeNode = SCNNode(geometry: cubeGeometry) cubeNode.name = "cubeNode" cubeNode.simdPosition = SIMD3(0, 0, 0) scene.rootNode.addChildNode(cubeNode) // 创建相机和相机节点 let camera = SCNCamera() camera.fieldOfView = 60.0 camera.wantsDepthOfField = true camera.focusDistance = 0.8 camera.fStop = 5.6 let cameraNode = SCNNode() cameraNode.camera = camera cameraNode.name = "cameraNode" cameraNode.simdPosition = SIMD3<Float>(0, 0.0, 2) scene.rootNode.addChildNode(cameraNode) // 添加相机约束(一次性设置,无需重复操作) let lookAtConstraint = SCNLookAtConstraint(target: cubeNode) lookAtConstraint.influenceFactor = 1.0 lookAtConstraint.isGimbalLockEnabled = true let distanceConstraint = SCNDistanceConstraint(target: cubeNode) distanceConstraint.minimumDistance = 1 distanceConstraint.maximumDistance = 2 cameraNode.constraints = [lookAtConstraint, distanceConstraint] view.pointOfView = cameraNode // 监听位置变化,移动立方体 context.coordinator.cubeNode = cubeNode context.coordinator.positionSubscription = $targetPosition .debounce(for: .seconds(0.5), scheduler: RunLoop.main) .throttle(for: 0.01, scheduler: RunLoop.main, latest: true) .sink { [weak cubeNode] distance in cubeNode?.simdPosition = SIMD3(distance, 0, 0) } return view } func updateUIView(_ view: SCNView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject { var cubeNode: SCNNode? var positionSubscription: AnyCancellable? } }
内容的提问来源于stack exchange,提问作者user3069232
相关产品推荐
相关产品推荐

