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
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
}
}