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

SceneKit+SwiftUI相机无法重置问题求助

SceneKit + SwiftUI 相机重置无效问题解决

问题根源

  1. 重复加载场景:初始代码中loadScene()直接作为SceneView参数,SwiftUI视图刷新时会重复调用该函数,导致相机节点和默认状态被反复重置,修改操作失效。
  2. 相机控制器冲突:开启.allowsCameraControl后,SceneKit内置的相机控制器会持续接管相机节点的transform属性,直接赋值transform或替换节点的操作会被控制器覆盖,无法生效。
  3. 状态绑定不及时:更新后的代码替换相机节点后,SwiftUI的pointOfView绑定未触发SceneView实时更新,导致新相机节点未被正确识别。

解决方案

核心思路:保留原相机节点,临时禁用相机控制器以修改状态,再恢复控制,同时确保场景仅加载一次。

关键修改点

  • 用@State存储场景实例,通过.task仅加载一次,避免重复创建。
  • 保存相机的默认position和eulerAngles(或完整transform),作为重置基准。
  • 重置时,先关闭相机控制,通过SCNTransaction执行动画修改相机状态,完成后重新启用控制。

修正后的完整代码

import SwiftUI
import SceneKit

struct ContentView: View {
    @State private var scene: SCNScene?
    @State private var mainCameraNode: SCNNode?
    @State private var defaultCameraPosition: SCNVector3?
    @State private var defaultCameraEulerAngles: SCNVector3?
    @State private var allowCameraControl = true

    private func createCameraNode() -> SCNNode {
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.camera?.zNear = 1
        cameraNode.camera?.zFar = 10000
        
        let screenSize = UIScreen.main.bounds.size
        let screenAspectRatio = Float(screenSize.width / screenSize.height)
        let baseDistance: Float = 1000
        let heightFactor: Float = 0.3
        
        let xOffset = -baseDistance * screenAspectRatio * 1.5
        let yOffset = baseDistance * heightFactor
        let zOffset = -baseDistance
        
        cameraNode.position = SCNVector3(x: xOffset, y: yOffset, z: zOffset)
        cameraNode.look(at: SCNVector3(0, 0, 0))
        
        // 保存默认状态
        defaultCameraPosition = cameraNode.position
        defaultCameraEulerAngles = cameraNode.eulerAngles
        
        return cameraNode
    }
    
    private func loadScene() -> SCNScene {
        let scene = SCNScene(named: "myModel.scn") ?? SCNScene()
        let cameraNode = createCameraNode()
        scene.rootNode.addChildNode(cameraNode)
        mainCameraNode = cameraNode
        return scene
    }
    
    private func resetCameraPosition() {
        guard let cameraNode = mainCameraNode,
              let defaultPos = defaultCameraPosition,
              let defaultAngles = defaultCameraEulerAngles else {
            print("Error: Camera or default state is nil!")
            return
        }
        
        // 临时禁用相机控制,避免冲突
        allowCameraControl = false
        
        // 用事务实现平滑动画
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 1.0
        // 动画完成后恢复相机控制
        SCNTransaction.completionBlock = {
            allowCameraControl = true
        }
        
        cameraNode.position = defaultPos
        cameraNode.eulerAngles = defaultAngles
        
        SCNTransaction.commit()
    }

    var body: some View {
        ZStack {
            SceneView(
                scene: scene,
                pointOfView: mainCameraNode,
                options: [.autoenablesDefaultLighting],
                cameraControlConfiguration: allowCameraControl ? .init() : nil
            )
            .ignoresSafeArea()
            .task {
                scene = loadScene()
            }

            VStack {
                Spacer()
                Button {
                    resetCameraPosition()
                } label: {
                    Text("Reset Position")
                        .padding()
                        .background(Color.white)
                        .cornerRadius(10)
                }
                .padding(.bottom, 20)
            }
        }
    }
}

#Preview {
    ContentView()
}

自动重置扩展(实现3秒自动恢复)

添加相机状态监听,当用户调整相机后启动定时器,超时后自动重置:

// 将ContentView改为继承NSObject以支持KVO
class ContentView: NSObject, View {
    @State private var scene: SCNScene?
    @State private var mainCameraNode: SCNNode?
    @State private var defaultCameraPosition: SCNVector3?
    @State private var defaultCameraEulerAngles: SCNVector3?
    @State private var allowCameraControl = true
    @State private var resetTimer: Timer?

    private func createCameraNode() -> SCNNode {
        let cameraNode = SCNNode()
        // ... 原有代码 ...
        
        // 添加transform变化监听
        cameraNode.addObserver(self, forKeyPath: "transform", options: [.new], context: nil)
        
        return cameraNode
    }

    // KVO回调
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "transform" {
            // 用户操作相机,重置定时器
            resetTimer?.invalidate()
            resetTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { [weak self] _ in
                self?.resetCameraPosition()
            }
        }
    }

    var body: some View {
        ZStack {
            // ... 原有SceneView代码 ...
        }
        .onDisappear {
            // 清理监听和定时器
            mainCameraNode?.removeObserver(self, forKeyPath: "transform")
            resetTimer?.invalidate()
        }
    }
}

内容的提问来源于stack exchange,提问作者Matteo Tripolt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 23:32:32