EntityAction implementation example

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?

Answered by Vision Pro Engineer in 807813022

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!

Accepted Answer

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!

Hey, Thank you! Phil

EntityAction implementation example
 
 
Q