SceneKit如何实现相机围绕物体自动匀速旋转并按需变向
嘿,这个需求其实很好实现!不用手势识别,我们可以用两种方式来做:一种是用SceneKit自带的SCNAction,另一种是用CADisplayLink手动更新旋转角度,两种都能实现匀速旋转+按需切换方向。我结合你的代码给你详细讲讲~
核心思路
首先得明确:我们需要一个轨道节点(cameraOrbit),把相机节点挂在这个轨道节点下,然后让轨道节点围绕目标物体旋转——这样相机就会跟着轨道一起绕目标转了,这是SceneKit里实现环绕相机的标准做法。
方法一:用SCNAction实现(简单快捷)
这种方式利用SceneKit的动作系统,创建一个永久重复的旋转动作,切换方向时只需要重启动作并反转旋转方向即可。
先给你的GameViewController补充必要的变量和方法:
class GameViewController: UIViewController { var gameView:SCNView! var gameScene:SCNScene! var cameraNode:SCNNode! var camera:SCNCamera! var cameraOrbit:SCNNode! // 新增:记录旋转方向(1=顺时针,-1=逆时针) private var rotationDirection: Float = 1.0 // 可选:保存当前的旋转动作,方便后续管理 private var currentRotationAction: SCNAction? override func viewDidLoad() { super.viewDidLoad() initView() setupCamera() setupTargetObject() // 替换成你自己的目标物体初始化 startCameraRotation() // 启动自动旋转 } func initView() { gameView = SCNView(frame: view.bounds) gameView.autoresizingMask = [.flexibleWidth, .flexibleHeight] gameView.allowsCameraControl = false // 必须关闭默认相机控制,避免冲突 view.addSubview(gameView) gameScene = SCNScene() gameView.scene = gameScene } func setupCamera() { camera = SCNCamera() cameraNode = SCNNode(camera: camera) // 设置相机到轨道节点的距离(可以根据需求调整) cameraNode.position = SCNVector3(x: 0, y: 2, z: 5) cameraOrbit = SCNNode() cameraOrbit.addChildNode(cameraNode) gameScene.rootNode.addChildNode(cameraOrbit) } func setupTargetObject() { // 示例:创建一个立方体作为旋转目标,你可以替换成自己的3D模型 let cubeGeometry = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0) let cubeNode = SCNNode(geometry: cubeGeometry) gameScene.rootNode.addChildNode(cubeNode) // 把轨道节点放在目标物体的位置,这样就会围绕它旋转 cameraOrbit.position = cubeNode.position } // 启动/重启相机旋转 private func startCameraRotation() { // 先移除轨道节点上的所有旧动作,避免重复执行 cameraOrbit.removeAllActions() // 创建旋转动作:绕Y轴转一圈(2π弧度),耗时10秒(速度可调整) let rotateAction = SCNAction.rotateBy( x: 0, y: rotationDirection * .pi * 2, z: 0, duration: 10 ) // 让动作永久重复 let repeatAction = SCNAction.repeatForever(rotateAction) cameraOrbit.runAction(repeatAction) currentRotationAction = repeatAction } // 切换旋转方向的方法(你可以在按钮点击、特定事件里调用它) func toggleRotationDirection() { rotationDirection *= -1 // 反转方向 startCameraRotation() // 重启旋转动作 } }
优点:
- 代码简单,不用手动处理帧刷新
- 完全利用SceneKit的内置系统,性能稳定
方法二:用CADisplayLink实现(更灵活)
如果需要更精细的控制(比如随时暂停/恢复、动态调整旋转速度),可以用CADisplayLink来手动更新轨道节点的旋转角度。
替换上面的旋转相关代码为:
class GameViewController: UIViewController { // ... 保留之前的变量 ... // 新增:用CADisplayLink的相关变量 private var displayLink: CADisplayLink? private var rotationSpeed: Float = 0.005 // 每帧旋转的角度(可调整) private var rotationDirection: Float = 1.0 // 1=顺时针,-1=逆时针 private var currentRotationY: Float = 0.0 override func viewDidLoad() { super.viewDidLoad() initView() setupCamera() setupTargetObject() setupDisplayLink() // 启动帧刷新回调 } // ... 保留initView、setupCamera、setupTargetObject方法 ... private func setupDisplayLink() { displayLink = CADisplayLink(target: self, selector: #selector(updateCameraRotation)) // 添加到主运行循环,跟随屏幕刷新频率执行 displayLink?.add(to: .main, forMode: .common) } // 每帧更新相机旋转 @objc private func updateCameraRotation() { currentRotationY += rotationSpeed * rotationDirection // 直接设置轨道节点的Y轴欧拉角,实现旋转 cameraOrbit.eulerAngles.y = currentRotationY } // 切换旋转方向 func toggleRotationDirection() { rotationDirection *= -1 } // 可选:暂停旋转 func pauseRotation() { displayLink?.isPaused = true } // 可选:恢复旋转 func resumeRotation() { displayLink?.isPaused = false } }
优点:
- 可以随时暂停/恢复旋转
- 动态调整旋转速度(比如根据场景变化修改
rotationSpeed) - 旋转过程中可以加入额外逻辑(比如检测碰撞、调整相机位置)
注意事项
- 一定要把
gameView.allowsCameraControl设为false,否则默认的相机控制会和你的自定义旋转冲突。 - 确保
cameraOrbit的位置和目标物体的位置一致,这样才会围绕目标旋转。 - 旋转轴可以根据需求修改:比如绕X轴旋转就改
rotateBy的x参数,或者eulerAngles.x。
内容的提问来源于stack exchange,提问作者Pedro de Sá
相关产品推荐
相关产品推荐

