swiftui3drealitykitusdzrealityview

How to change the speed of already running RealityView USDZ animation


I have a USDZ 3d model contains a running man animation and I manage to run the model well using this code:

import SwiftUI
import RealityKit

struct ContentView: View {
    @State var animationResource: AnimationResource?
    @State var animationDefinition: AnimationDefinition?
    @State var manPlayer = Entity()
    @State var speed:Float = 0.5
    
    var body: some View {
        VStack {
            RealityView { content in
                if let item = try? await Entity(named: "run.usdz") {
                    manPlayer = item
                    
                    content.add(manPlayer)
                }
            }
            HStack {
                Button(action: playThis) {
                    Text("Play")
                }
                
                Button(action: increaseSpeed) {
                    Text("increase Speed (+) ")
                }
                
                Button(action: decreaseSpeed) {
                    Text("decrease Speed (-) ")
                }
            }
        }
    }
    
    func playThis() {
        animationResource = manPlayer.availableAnimations[0]
        animationDefinition = animationResource?.definition
        animationDefinition?.repeatMode = .repeat
        animationDefinition?.speed = speed
        
        // i could not add the definition to the animation resource back again
        // so i generated a new one
        
        let animRes = try! AnimationResource.generate(with: animationDefinition!)
        manPlayer.playAnimation(animRes)
    }
    
    func increaseSpeed() {
        speed += 0.1
        animationDefinition?.speed = speed
        
        // Now i wonder is this the best way to increase speed
        // isn't it as if im adding more load to the memory
        // by adding another players
        let animRes = try! AnimationResource.generate(with: animationDefinition!)
        manPlayer.playAnimation(animRes)
    }
    
    func decreaseSpeed() {
        speed -= 0.1
        animationDefinition?.speed = speed
        
        // Now i wonder is this the best way to increase speed
        // isn't it as if im adding more load to the memory
        // by adding another players
        let animRes = try! AnimationResource.generate(with: animationDefinition!)
        manPlayer.playAnimation(animRes)
    }
}

And i wonder how to control speed of this animation while it is running without have to regenerate a resource and replay the animation over and over with every speed change knowing that every time the animation replayed it started from the frame zero meaning its not completing its cycle before its replayed but cut in the middle and start over again from start which I do not prefer to be happen.

The USDZ file link is here if you wanna try (still you can use any other animated USDZ file to test) : https://www.worldhotelity.com/stack/run.usdz

enter image description here


Solution

  • I figured it out thanks to some help from Apple developers forum, The solution was found in switching from AnimationDefinition Method to AnimationPlaybackController as the controller need to be declared as global variable

    @State private var playbackController: AnimationPlaybackController?
    

    And after you use it to play the animation

            guard let animationResource = manPlayer.availableAnimations.first else { return }
            playbackController = manPlayer.playAnimation(animationResource.repeat(duration: .infinity), transitionDuration: 0.25, startsPaused: false) //infinity looping, smooth transition, animation starts immediately
            playbackController?.speed = speed
    
    

    then you can use it to control speed later

            speed += 0.1
            playbackController?.speed = speed
    
    

    Here is the full code below:

    import SwiftUI
    import RealityKit
    
    struct ContentView: View {
        @State var manPlayer = Entity()
        @State var speed:Float = 0.5
            
        @State private var playbackController: AnimationPlaybackController?
    
        var body: some View {
            VStack {
                RealityView { content in
                    if let item = try? await Entity(named: "run.usdz") {
                        manPlayer = item
                        
                        content.add(manPlayer)
                    }
                }
                HStack {
                    Button(action: playThis) {
                        Text("Play")
                    }
                    
                    Button(action: increaseSpeed) {
                        Text("increase Speed (+) ")
                    }
                    
                    Button(action: decreaseSpeed) {
                        Text("decrease Speed (-) ")
                    }
                }
            }
        }
        
        func playThis() {
            guard let animationResource = manPlayer.availableAnimations.first else { return }
            playbackController = manPlayer.playAnimation(animationResource.repeat(duration: .infinity), transitionDuration: 0.25, startsPaused: false) //infinity looping, smooth transition, animation starts immediately
            playbackController?.speed = speed
        }
        
        func increaseSpeed() {
            speed += 0.1
            playbackController?.speed = speed
        }
        
        func decreaseSpeed() {
            speed -= 0.1
            playbackController?.speed = speed
        }
    }