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

RealityKit中实体持续循环旋转动画失效问题排查

问题:RealityKit实体无法实现持续循环旋转动画

使用环境

  • Ventura 13.4
  • Xcode 14.3.1
  • Swift 5.8.1
  • 目标系统iOS 16.4

问题描述

开发基于RealityKit的应用时,需要让AR场景中的实体绕轴持续旋转。目前已实现加载时单次旋转180°,也能通过UI滑块控制旋转,但无法实现无限循环动画。

尝试通过监听AnimationEvents.PlaybackCompleted事件,在动画完成后启动下一次动画来实现循环,但遇到两个问题:

  1. scene.subscribe(to:on:_:)返回nil,事件从未触发,动画仅执行一次。
  2. 改用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

问题原因分析

  1. 订阅生命周期管理错误:@State private var animUpdateSubscription在UIViewRepresentable中使用会导致订阅生命周期不稳定,视图初始化完成后可能被意外销毁。
  2. 事件监听对象错误:AnimationEvents.PlaybackCompleted事件的发送者是动画实例,而非实体或场景,直接监听实体无法捕获事件。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 11:44:59