lostcoder
lostcoder

Reputation: 31

RealityKit – Animate a transform on a loop

I can't figure out how to have the move function below repeat in order to create a constant rotation on a simple meshResource. Any suggestions?

var transform = sphereEntity.transform
transform.rotation =  simd_quatf(angle: .pi, axis: [0,1,0])
let anim = sphereEntity.move(to: transform, relativeTo: sphereEntity.parent, duration: 5.0)

Upvotes: 3

Views: 1104

Answers (2)

Andy Jazz
Andy Jazz

Reputation: 58103

.move() instance method starts working when it's placed after the line entity added to a scene.

let boxEntity = ModelEntity(mesh: .generateBox(size: 1.0), 
                       materials: [SimpleMaterial()])

var transform = boxEntity.transform

transform.rotation =  simd_quatf(angle: .pi, axis: [0, 1, 0])
    
let boxAnchor = AnchorEntity()
boxAnchor.children.append(boxEntity)
arView.scene.anchors.append(boxAnchor)
    
boxEntity.move(to: transform, relativeTo: nil, duration: 5.0)

At the moment (December 23, 2022) all RealityKit's .move() methods still can't loop.

Upvotes: 1

MasDennis
MasDennis

Reputation: 746

Looping an animation is possible by subscribing to the AnimationEvents.PlaybackCompleted event.

You'll have to move your animation logic into a separate function which you call once. Inside this function you subscribe to the PlaybackCompleted event. When this event is fired you call the same animation function.

You can subscribe to AnimationEvents by using the Scene.subscribe() method:

func subscribe<E>(to event: E.Type, 
                  on sourceObject: EventSource? = nil, 
                  _ handler: @escaping (E) -> Void) -> Cancellable where E : Event

Please note that you'll have to import the Combine framework to be able to use this:

import Combine

The animation function then looks like this:

// This needs to be an instance variable, otherwise it'll
// get deallocated immediately and the animation won't start.
var animUpdateSubscription: Cancellable?

func animate(entity: HasTransform,
             angle: Float,
             axis: SIMD3<Float>,
             duration: TimeInterval,
             loop: Bool)
{
    var transform = entity.transform
    // We'll add the rotation to the current rotation
    transform.rotation *= simd_quatf(angle: angle, axis: axis)
    entity.move(to: transform, relativeTo: entity.parent, duration: duration)
    
    // Checks if we should loop and if we have already subscribed
    // to the AnimationEvent. We only need to do this once. 
    guard loop,
          animUpdateSubscription == nil
    else { return }
    
    animUpdateSubscription = scene.subscribe(to: AnimationEvents.PlaybackCompleted.self, 
                                             on: entity, 
    { _ in
        self.animate(entity: entity, 
                     angle: angle, 
                     axis: axis, 
                     duration: duration, 
                     loop: loop)
    })
}

Now you can call this function once to trigger a looped animation:

animate(entity: entity, 
        angle: angle, 
        axis: axis, 
        duration: duration, 
        loop: loop)

Upvotes: 1

Related Questions