KybernetikGames / animancer

Documentation for the Animancer Unity Plugin.
65 stars 8 forks source link

Is there a nicer way to fade into an animation with a weight other than 1? #255

Closed ADH-LukeBollam closed 1 year ago

ADH-LukeBollam commented 1 year ago

Use Case

I have some layered animations that I play on top of the base movement, things such as a landing animation when falling a long way. I don't want to play the animation at full strength, just at 0.5 so the character is still mostly running, but they compress and stabilize a little bit.

Describe what the problem is

I'm having trouble figuring out what the best way to implement this fade is, because it's confusing when playing immediately sets (animation / layer) weight to 1. This is the best I've been able to come up with, I'm not sure if it's correct though:

var state = _animancer.Layers[animConfig.PlaybackLayer].Play(selectedAnimation);    // play it
state.SetWeight(0);                                                               // reset the weight
state.StartFade(animConfig.PlaybackWeight, selectedAnimation.FadeDuration);         // now fade in to our target weight

I'm not sure what happens when I Play() a clip transition, then call StartFade on the State afterwards. Does this replace the fade duration specified in the ClipTransition?

Maybe I'm reading it wrong, but I'm having trouble getting consistent information from the documentation on Layers and Fading. For example:

Layer docs:

"Similarly, Cross Fading will actually Play the animation at Weight = 1 immediately and fade the layer in instead of fading the animation."

Ok the layer is faded when CrossFading.

Cross Fading section:

"These methods fade the new animation in over time and fade the old animation out at the same time, which is known as Cross Fading"

Wait the animation is faded? I thought it was set to weight = 1?

Is the rule here: If the layer weight is > 0 and you crossfade Play, then the layer weight isn't touched and animations are crossfaded. if the layer weight is 0 and you crossfade Play, the layer weight is faded in to 1 and the animation weight is set at 1.

I'm not clear on what counts as 'CrossFade'. Is it a call with the second 'FadeDuration' argument? Or does a Play()ing a ClipTransition also count as crossfade?

What is the recommended way to fade in an additive, upper body layer, animation in to 0.5f weight?

Solution

  1. It would be really nice if we could configure the target weight in a ClipTransition.
  2. The different fade behavior depending on current state is quite confusing, it probably should be standardized
    • eg. Animation weight is always the value faded, Layer weight isn't touched

or different methods to do different behaviors

KybernetikGames commented 1 year ago

when I Play() a clip transition, then call StartFade on the State afterwards. Does this replace the fade duration specified in the ClipTransition?

Yes.

Is the rule here: ...

Your understanding is correct. I'll try to clarify the docs.

The reason it's implemented like that is because of what happens if you fade in an animation on a layer then interrupt that fade by fading out the layer. If it fades in for 0.1 seconds then you tell it to fade out at the same speed, it should only take 0.1 seconds to fade out (instead of the full 0.25 or whatever your fade duration is). But if the state was fading in instead of the layer, then fading out the layer would always take the full 0.25 seconds while the state continues fading in which would look odd.

Or does a Play()ing a ClipTransition also count as crossfade?

Yes, transitions are just a collection of data which feeds into the other Play methods.

What is the recommended way to fade in an additive, upper body layer, animation in to 0.5f weight?

Try playing your animation with its fade normally then calling _animancer.Layers[animConfig.PlaybackLayer].StartFade(0.5f, fadeDuration) to put the layer at half weight instead of the state.

It would be really nice if we could configure the target weight in a ClipTransition.

I don't think this use case is common enough to be worth adding that extra complexity and overhead for everyone, but you could easily make your own class which inherits from ClipTransition to add a target weight field and override its Apply method to call state.Layer.StartFade(targetLayerWeight, FadeDuration);.

it probably should be standardized eg. Animation weight is always the value faded, Layer weight isn't touched

It was originally implemented like that, but the result just wasn't as good if the state fade got interrupted by fading the layer out as I explained above.

or different methods to do different behaviors eg. PlayInstant / PlayCrossFade / PlayLayerFade

That was also how it was originally implemented, but I changed it to the current setup in Animancer v4.0. Aside from the bloated number of methods it used to have, the main motivation was to make it so you can change directly between a Play/CrossFade and using a Transition without needing to also figure out which method is right to use.

Conclusion

To clarify my stance on this: I'm not trying to dismiss your opinions, but I don't believe it's worth adding additional parameters and complexity just to support an uncommon use case as long as it can be achieved in another way without too much effort. If fading the layer to 0.5 after playing achieves what you want, then I'd see that as an acceptable solution (and you could easily make an extension method to wrap it cleanly). If it doesn't work, I'll be happy to look into it more deeply and consider other options.

ADH-LukeBollam commented 1 year ago

Thanks for getting back to me and clarifying all of my questions, there is a lot more going on than I considered and it seems like you've found a nice middle ground. As all I need to do is fade the layer instead of the state, that is fine for me and appears to be working.