如何在 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],避免循环引用。
完成事件用于确定操作顺序,不保证回调在某个绝对时间点执行,也不能证明最终画面已经显示到屏幕上。
备注:内容仅供参考。
