PAYATHON 2026

如何在 RealityKit 的 move 动画完成后可靠地执行后续操作?

数据阿明

可以监听 AnimationEvents.PlaybackCompleted,用事件中的 playbackController 匹配这次 entity.move(...) 返回的控制器,匹配后再启动后续动画。下一步由动画完成事件触发,无需猜测还要等待多久。Apple 的 AnimationPlaybackController 文档说明了 move(...) 返回控制器,以及通过事件接收播放状态通知的机制。

为什么增加 0.1 秒仍然不可靠?

DispatchQueue.main.asyncAfter 只是安排闭包在指定时间之后执行,不会检查 RealityKit 动画的播放状态。延迟时间已经过去,也不能据此确认目标动画已经完成。

仅凭题目中的代码,无法判断观察到的时间偏差具体来自哪里。既然后续操作依赖动画完成,就应以完成事件作为触发条件。

如何监听这一次 move 动画完成?

下面使用 Combine 订阅事件。示例假设 duration > 0、entity 已加入场景,并且一个协调器实例同一时间只等待一段动画完成。

import Foundation
import RealityKit
import Combine

@MainActor
final class AnimationCoordinator {
    private var completionSubscription: AnyCancellable?

    func moveAndThen(
        entity: Entity,
        to newTransform: Transform,
        relativeTo anchor: Entity?,
        duration: TimeInterval,
        completion: @escaping () -> Void
    ) {
        precondition(duration > 0)

        guard let scene = entity.scene else {
            assertionFailure("Entity must belong to a scene")
            return
        }

        // 此示例每次只保留一个动画完成订阅。
        completionSubscription?.cancel()

        var targetController: AnimationPlaybackController?

        // 先建立订阅,再启动动画。
        completionSubscription = scene
            .publisher(
                for: AnimationEvents.PlaybackCompleted.self,
                on: entity
            )
            .filter { event in
                guard let targetController = targetController else {
                    return false
                }
                return event.playbackController == targetController
            }
            .first()
            .sink { _ in
                completion()
            }

        targetController = entity.move(
            to: newTransform,
            relativeTo: anchor,
            duration: duration,
            timingFunction: .easeInOut
        )
    }
}

将 AnimationCoordinator 保存为控制器或协调对象的属性,再调用它:

animationCoordinator.moveAndThen(
    entity: entity,
    to: newTransform,
    relativeTo: anchor,
    duration: duration
) {
    // 在这里启动下一段动画。
}

订阅时需要注意:

  • on: entity 将订阅限定到目标实体。
  • 比较 event.playbackController,避免同一实体上其他动画的完成事件误触发后续操作。
  • .first() 放在 .filter 后面,只处理目标动画的第一次匹配事件,然后结束订阅。

Apple 的 AnimationEvents 文档也使用 scene.publisher(for:on:) 和 Combine 接收动画完成通知。

完成、停止和订阅生命周期需要注意什么?

PlaybackCompleted 表示动画播放到了其持续时间的终点。调用控制器的 stop() 不会触发这个事件。因此,业务主动停止动画时,应另外处理取消状态,不能继续等待完成回调。Apple 的 PlaybackCompleted 文档说明了这一点。

如果动画正常完成或被终止时都需要执行清理操作,可以查看 AnimationEvents.PlaybackTerminated。它表示播放已经终止,不代表动画成功播放到目标状态。使用前应检查目标 SDK 的可用性。Apple 的 PlaybackTerminated 文档说明了两者的语义区别。

还要让协调器和 AnyCancellable 在动画期间持续存活。如果需要并行等待多段动画,应分别保存各段动画的订阅,不能共用示例中的单个属性。如果 completion 捕获了持有协调器的对象,可根据对象生命周期使用 [weak self],避免循环引用。

完成事件用于确定操作顺序,不保证回调在某个绝对时间点执行,也不能证明最终画面已经显示到屏幕上。

备注:内容仅供参考。