Skip to content

Instantly share code, notes, and snippets.

@arthurschiller
Last active December 6, 2023 11:59
Show Gist options
  • Save arthurschiller/f299b5aa002db0caabd45418858b6642 to your computer and use it in GitHub Desktop.
Save arthurschiller/f299b5aa002db0caabd45418858b6642 to your computer and use it in GitHub Desktop.
RealityKit visionOS – Animate Custom Bind Target Parameters πŸƒβ€β™€οΈ
import SwiftUI
import RealityKit
struct AnimationDemoView: View {
@Environment(\.realityKitScene) var scene: RealityKit.Scene?
init() {
// register System and Component – Important!
AnimationSystem.registerSystem()
AnimationComponent.registerComponent()
}
var body: some View {
RealityView { content in
guard let scene else {
return
}
let cube = ModelEntity(
mesh: .generateBox(size: 0.5, cornerRadius: 0.1),
materials: [SimpleMaterial(color: .red, isMetallic: true)]
)
cube.position = [0, 1.5, -2]
content.add(cube)
let _ = content.subscribe(to: SceneEvents.DidAddEntity.self, on: cube) { event in
event.entity.animateFloatValue(
from: 0,
to: 1,
duration: 3,
delay: 1,
timing: .easeInOut,
withScene: scene,
onAnimationFrame: { value, entity in
entity?.components.set(OpacityComponent(opacity: value))
}
)
}
}
}
}
// Custom Animation System
class AnimationSystem: System {
private static let query = EntityQuery(
where: .has(AnimationComponent.self)
)
required init(scene: RealityKit.Scene) {}
func update(context: SceneUpdateContext) {
let animationEntites = context.scene.performQuery(Self.query)
for entity in animationEntites {
if let animationComponent = entity.components[AnimationComponent.self] {
animationComponent.onUpdate()
}
}
}
}
// Custom Animation Component
struct AnimationComponent: Component {
let bindableValueIdentifier: String
let onUpdate: (() -> Void)
init(
bindableValueIdentifier: String,
onUpdate: @escaping (() -> Void)
) {
self.bindableValueIdentifier = bindableValueIdentifier
self.onUpdate = onUpdate
}
}
// Bindable Value Animations
extension Entity {
func animateBindableValue<Value: AnimatableData>(
from: Value,
to: Value,
duration: TimeInterval,
delay: TimeInterval,
timing: AnimationTimingFunction,
identifier: String,
withScene scene: RealityKit.Scene,
onAnimationFrame: @escaping () -> Void,
onCompletion: (() -> Void)?
) {
components.set(
AnimationComponent(
bindableValueIdentifier: identifier,
onUpdate: {
onAnimationFrame()
}
)
)
let animationDefinition = FromToByAnimation(
name: identifier,
from: from,
to: to,
duration: duration,
timing: timing,
bindTarget: .parameter(identifier), // set custom binding target
delay: delay
)
if let animation = try? AnimationResource.generate(with: animationDefinition) {
playAnimation(animation)
scene.subscribe(to: AnimationEvents.PlaybackCompleted.self, on: self) { [weak self] event in
onCompletion?()
self?.components.remove(AnimationComponent.self)
}
.storeWhileEntityActive(self)
}
}
func animateFloatValue(
from: Float,
to: Float,
duration: TimeInterval,
delay: TimeInterval = 0,
timing: AnimationTimingFunction = .easeIn,
identifier: String = UUID().uuidString,
withScene scene: RealityKit.Scene,
onAnimationFrame: @escaping (Float, Entity?) -> Void,
onCompletion: ((Entity?) -> Void)? = nil
) {
parameters[identifier] = BindableValue<Float>(from)
animateBindableValue(
from: from,
to: to,
duration: duration,
delay: delay,
timing: timing,
identifier: identifier,
withScene: scene,
onAnimationFrame: { [weak self] in
if let bindableValue = self?.bindableValues[.parameter(identifier), Float.self] {
onAnimationFrame(bindableValue.value, self)
}
},
onCompletion: { [weak self] in
onAnimationFrame(to, self)
onCompletion?(self)
}
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment