RealityKit中实体持续循环旋转动画失效问题排查
问题:RealityKit实体无法实现持续循环旋转动画
使用环境
- Ventura 13.4
- Xcode 14.3.1
- Swift 5.8.1
- 目标系统iOS 16.4
问题描述
开发基于RealityKit的应用时,需要让AR场景中的实体绕轴持续旋转。目前已实现加载时单次旋转180°,也能通过UI滑块控制旋转,但无法实现无限循环动画。
尝试通过监听AnimationEvents.PlaybackCompleted事件,在动画完成后启动下一次动画来实现循环,但遇到两个问题:
scene.subscribe(to:on:_:)返回nil,事件从未触发,动画仅执行一次。- 改用Combine的
scene.publisher(for:).sink方式后,日志显示订阅被立即取消,仍无法循环。
尝试的Demo代码
import Combine import RealityFoundation import RealityKit import SwiftUI struct ARKitPOC: View { var body: some View { ARViewContainer().edgesIgnoringSafeArea(.all) } } struct ARViewContainer: UIViewRepresentable { @State private var animUpdateSubscription: Cancellable? func makeUIView (context: Context) -> ARView { let redMaterial = SimpleMaterial(color: .red, roughness: 0.05, isMetallic: false) let boxResource = MeshResource.generateBox(width: 1.0, height: 1.0, depth: 1.0, cornerRadius: 0.1, splitFaces: false) let boxEntity = ModelEntity(mesh: boxResource, materials: [redMaterial]) boxEntity.name = "box" boxEntity.position.z = -2 let anchorEntity = AnchorEntity(world: .zero) anchorEntity.name = "anchor" anchorEntity.addChild(boxEntity) let arView = ARView(frame: .zero) arView.scene.addAnchor(anchorEntity) arView.cameraMode = .nonAR rotateForever(scene: arView.scene, entity: boxEntity, angle: -.pi, axis: [0,0,1], duration: 1.0) return arView } func updateUIView(_ arView: ARView, context: Context) { } func rotateForever(scene: RealityFoundation.Scene, entity: HasTransform, angle: Float, axis: SIMD3<Float>, duration: TimeInterval) { if animUpdateSubscription == nil { animUpdateSubscription = scene.subscribe(to: AnimationEvents.PlaybackCompleted.self, on: entity, { _ in print("subscription triggered") self.rotateForever(scene: scene, entity: entity, angle: angle, axis: axis, duration: duration) }) print("subscription:", animUpdateSubscription) } var transform = entity.transform transform.rotation *= simd_quatf(angle: angle, axis: axis) entity.move(to: transform, relativeTo: entity.parent, duration: duration, timingFunction: .linear) } } struct ARKitPOC_Previews: PreviewProvider { static var previews: some View { ARKitPOC() } }
日志输出(Combine方式)
publisher(for:): receive subscription: (RealityFoundation.REEventDispatcher<__C.REAnimationHasCompletedEvent>.(unknown context at $10b680970).EventSubscription<RealityKit.Scene.CorePublisher<__C.REAnimationHasCompletedEvent>.(unknown context at $10b686788).Inner<Combine.Publishers.CompactMap<RealityKit.Scene.CorePublisher<__C.REAnimationHasCompletedEvent>, RealityKit.AnimationEvents.PlaybackCompleted>.(unknown context at $10a11e878).Inner<Combine.Publishers.Print<RealityKit.Scene.Publisher<RealityKit.AnimationEvents.PlaybackCompleted>>.(unknown context at $10a119ff8).Inner<Combine.Subscribers.Sink<RealityKit.AnimationEvents.PlaybackCompleted, Swift.Never>>>>>) publisher(for:): request unlimited publisher(for:): receive cancel
问题原因分析
- 订阅生命周期管理错误:
@State private var animUpdateSubscription在UIViewRepresentable中使用会导致订阅生命周期不稳定,视图初始化完成后可能被意外销毁。 - 事件监听对象错误:
AnimationEvents.PlaybackCompleted事件的发送者是动画实例,而非实体或场景,直接监听实体无法捕获事件。 move(to:)方法的局限性:该方法创建的动画默认不会向实体发送完成事件,必须通过动画实例本身监听。
解决方案
方法1:通过动画实例监听完成事件实现循环
使用Coordinator管理订阅生命周期,直接监听动画实例的完成事件,递归启动下一次动画:
import Combine import RealityFoundation import RealityKit import SwiftUI struct ARKitPOC: View { var body: some View { ARViewContainer().edgesIgnoringSafeArea(.all) } } struct ARViewContainer: UIViewRepresentable { // 用Coordinator管理订阅,避免State导致的生命周期问题 class Coordinator: NSObject { var animSubscription: Cancellable? } func makeCoordinator() -> Coordinator { Coordinator() } func makeUIView (context: Context) -> ARView { let redMaterial = SimpleMaterial(color: .red, roughness: 0.05, isMetallic: false) let boxResource = MeshResource.generateBox(width: 1.0, height: 1.0, depth: 1.0, cornerRadius: 0.1) let boxEntity = ModelEntity(mesh: boxResource, materials: [redMaterial]) boxEntity.position.z = -2 let anchorEntity = AnchorEntity(world: .zero) anchorEntity.addChild(boxEntity) let arView = ARView(frame: .zero) arView.scene.addAnchor(anchorEntity) arView.cameraMode = .nonAR startLoopRotation(for: boxEntity, duration: 1.0, axis: [0,0,1], angle: -.pi, coordinator: context.coordinator) return arView } func updateUIView(_ arView: ARView, context: Context) {} func startLoopRotation(for entity: ModelEntity, duration: TimeInterval, axis: SIMD3<Float>, angle: Float, coordinator: Coordinator) { // 创建单次旋转动画 let endTransform = entity.transform.rotated(by: simd_quatf(angle: angle, axis: axis)) let animation = entity.move(to: endTransform, relativeTo: entity.parent, duration: duration, timingFunction: .linear) // 监听动画实例的完成事件 coordinator.animSubscription = animation.subscribe(to: AnimationEvents.PlaybackCompleted.self) { _ in // 递归启动下一次旋转 self.startLoopRotation(for: entity, duration: duration, axis: axis, angle: angle, coordinator: coordinator) } } } struct ARKitPOC_Previews: PreviewProvider { static var previews: some View { ARKitPOC() } }
方法2:使用内置无限重复动画(更简洁)
利用RealityKit的repeatBehavior属性直接创建无限循环动画,无需手动监听事件:
func makeUIView (context: Context) -> ARView { // ... 其他初始化代码不变 ... // 创建无限循环的360°旋转动画 let rotateAnimation = TransformTargetAnimation( target: .rotation, to: simd_quatf(angle: .pi * 2, axis: [0,0,1]), duration: 2.0, timingFunction: .linear ) rotateAnimation.repeatBehavior = .forever boxEntity.playAnimation(rotateAnimation) return arView }
关键改进点
- 使用
Coordinator管理订阅,确保订阅生命周期与ARView绑定,避免被意外取消。 - 直接监听动画实例的完成事件,确保事件能被正确捕获。
- 方法2利用RealityKit内置的重复机制,代码更简洁高效,无需手动递归。
内容的提问来源于stack exchange,提问作者JakeRobb
相关产品推荐
相关产品推荐

