Does anyone have experience of creating their own EntityActions?
Say for example I wanted one that faded up the opacity of an entity, then once it had completed set another property on one of the entity's components.
I understand that I could use the FromToByAction
to control the opacity (and have this working), but I am interested to learn how to create my own dedicated EntityAction, and finding the documentation hard to fathom.
I got as far as creating a struct conforming to EntityAction protocol:
var animatedValueType: (any AnimatableData.Type)?
}
Subscribing to update events on this:
FadeUpAction.subscribe(to: .updated) { event in
guard let animationState = event.animationState else {
return
}
// My animation state is always nil, so I never get here!
let newValue = \\\Some Calc...
animationState.storeAnimatedValue(newValue)
}
And setting it up as an animation on an entity:
let action = FadeUpAction()
if let animation = try? AnimationResource.makeActionAnimation(
for:action,
duration: 2.0,
bindTarget: .opacity
) {
entity.playAnimation(animation)
}
...but haven't been able to understand how to extract the current timeDelta or set the value in the event handler.
Any pointers?
Hi @peggers123
Looks like you're on the right track! Here's how I would go about creating the custom "fade up" EntityAction
you're describing.
Start by defining your custom EntityAction
structure:
struct FadeUpAction: EntityAction {
public let fromOpacity: Float
var animatedValueType: (any AnimatableData.Type)? {
Float.self
}
}
Here, fromOpacity
specifies the opacity the animation should start from as it animates towards a final opacity value of 1, and animatedValueType
describes the type of the value that will be animated (in this case a Float
).
Next, define the animation logic:
FadeUpAction.subscribe(to: .updated) { event in
// Get the animation state.
guard let animationState = event.animationState else {
return
}
// Linearly interpolate the opacity with the normalized animation time.
let opacity = simd_mix(event.action.fromOpacity, 1, Float(animationState.normalizedTime))
// Store the updated opacity value.
animationState.storeAnimatedValue(opacity)
}
In this example, I animate the opacity from fromOpacity
to 1 by using linear interpolation with animationState.normalizedTime
, but you can use a different function and perform your calculation with animationState.deltaTime
instead, if you prefer.
Finally, create an entity to play the animation on:
// Create an entity to apply the animation to.
let myEntity = ModelEntity(mesh: .generateBox(size: 0.2), materials: [SimpleMaterial()])
myEntity.position = [0, 1, -1]
content.add(myEntity)
// Give the entity an opacity component.
let opacityComponent = OpacityComponent(opacity: 0.1)
myEntity.components.set(opacityComponent)
// Create and play the animation.
let fadeAction = FadeUpAction(fromOpacity: opacityComponent.opacity)
if let fadeAnimation = try? AnimationResource.makeActionAnimation(for: fadeAction, duration: 2.0, bindTarget: .opacity) {
myEntity.playAnimation(fadeAnimation)
}
You also mention wanting to perform logic when the animation is complete. You can achieve this by subscribing to the .ended
event on your custom action, for example:
// Set the entity's color to green when the fade up animation completes.
FadeUpAction.subscribe(to: .ended) { event in
event.targetEntity?.components[ModelComponent.self]?.materials = [SimpleMaterial(color: .green, isMetallic: false)]
}
Let me know if you have any questions!