在ARKit的SCNScene中添加带动画SKScene的技术问题求助
嘿,我之前研究WWDC 2017 Session 609的时候也碰到过一模一样的问题,折腾了好一阵才搞明白关键所在,给你拆解下可能遗漏的步骤:
1. 确保SKScene正确关联到SCNNode的材质,且开启渲染循环
首先,你不能只把SKScene加载出来就丢给SCNMaterial,得确保SpriteKit的渲染循环能跑起来。通常的做法是:
- 创建一个
SCNPlane作为承载SKScene的载体节点 - 把SKScene赋值给平面材质的
diffuse.contents - 关键:要把这个SCNPlane节点添加到ARKit的
ARSCNView的场景中,并且确保ARSCNView的isPlaying属性为true(默认就是true,但如果手动停过要打开)
示例代码:
// 加载你的SKScene(假设你的SKScene文件名为AnimatedSKScene.sks) guard let skScene = SKScene(fileNamed: "AnimatedSKScene") else { return } skScene.scaleMode = .resizeFill // 创建承载的SCNPlane let planeNode = SCNNode(geometry: SCNPlane(width: 1, height: 1)) // 翻转平面,因为SCNPlane默认是背面朝向相机 planeNode.eulerAngles.x = -.pi / 2 // 关联SKScene到材质 planeNode.geometry?.firstMaterial?.diffuse.contents = skScene // 添加到ARKit场景 arSceneView.scene.rootNode.addChildNode(planeNode)
2. 为什么didMove(to:)和update(_:)不触发?
这是最容易踩的坑:当SKScene被作为材质内容嵌入到SceneKit场景中时,它不会自动触发SpriteKit的标准生命周期方法,除非你手动把它关联到一个SKView或者手动驱动它的更新循环。
两种解决办法:
办法一:手动绑定到ARSCNView的渲染回调
在你的ARKit视图控制器里,实现ARSCNViewDelegate的renderer(_:updateAtTime:)方法,手动调用SKScene的更新方法:
extension YourViewController: ARSCNViewDelegate { func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { // 假设skScene是你保存的SKScene实例引用 skScene.update(time) skScene.didEvaluateActions() } }
至于didMove(to:),你可以在把SKScene赋值给材质之后,手动调用一次:
skScene.didMove(to: arSceneView)
(这里传入ARSCNView是因为它遵循了SKView的协议,能作为SKScene的宿主视图)
办法二:把SKScene先加载到一个隐藏的SKView里
创建一个隐藏的SKView,把SKScene加载进去,这样它的生命周期方法就会自动触发,然后再把这个SKScene赋值给SCNMaterial:
let hiddenSKView = SKView(frame: .zero) hiddenSKView.isHidden = true view.addSubview(hiddenSKView) guard let skScene = SKScene(fileNamed: "AnimatedSKScene") else { return } hiddenSKView.presentScene(skScene) // 之后再把skScene赋值给SCNMaterial planeNode.geometry?.firstMaterial?.diffuse.contents = skScene
这种方式更接近原生SpriteKit的使用体验,不需要手动管理更新循环。
3. 为什么sceneDidLoad()里加动作不生效?
sceneDidLoad()是SKScene从.sks文件加载完成时调用的方法,但此时SKScene还没有被添加到任何渲染上下文里(不管是SKView还是ARSCNView的材质系统),所以动作会因为没有渲染循环驱动而不执行。
解决办法:
- 把添加动作的代码移到
didMove(to:)里(记得手动调用或者通过隐藏SKView触发) - 或者在确认SKScene已经关联到SCNMaterial之后,再手动调用动作的
run()方法
示例代码(在你的SKScene子类里):
class AnimatedSKScene: SKScene { override func didMove(to view: SKView) { super.didMove(to: view) // 在这里添加精灵和动作 let sprite = SKSpriteNode(imageNamed: "yourSprite") sprite.position = CGPoint(x: size.width/2, y: size.height/2) addChild(sprite) let rotateAction = SKAction.rotate(byAngle: .pi, duration: 1) sprite.run(SKAction.repeatForever(rotateAction)) } }
额外提醒:Session 609里的细节
记得WWDC讲师当时提到,要确保SKScene的scaleMode设置正确(比如.resizeFill或者.aspectFill),不然可能会出现拉伸或者显示不全的问题。另外,当SKScene作为材质时,它的帧率会和SceneKit的渲染帧率保持同步,所以不需要单独设置SKScene的preferredFramesPerSecond。
内容的提问来源于stack exchange,提问作者Hartix

