animator.crossfade() not working properly
I'm trying to create a class that can save and load the exact animation state of an animator. I can load up a normal animation state just fine using animator.Play(), the problem is I can't seem to load a transition between 2 states properly.
my code looks like this right now:
[Serializable]
class AnimState {
//variables...
/// <summary>
/// Apply this AnimState object to the given animator: LOADING
/// </summary>
public void Apply(int layer, Animator anim) {
//current animation info
anim.Play(hash, layer, time);
anim.SetLayerWeight(layer, weight);
//transition info
if(inTransition) {
anim.CrossFade(transitionHash, transitionDuration, layer, transitionTime);
}
}
}
But the result doesn't set the animator in the transition state between the two animations, it just puts the animator in either the source or the goal state completely. Did I misunderstand how CrossFade() works? How should I accomplish the desired effect?
Answer by WARdd · Sep 08, 2017 at 09:11 PM
Managed to find the solution, just posting it for people who might stumble on it. CrossFade() can be used to start a transition, but the animator needs to be updated manually to get it to the right point in time. Getting this to work requires a few nasty workarounds, but I arrived at a reasonable solution:
[Serializable]
class AnimState {
//MAKE SURE THAT NEXT STATE ANIMATION OFFSET == 0
//variables...
/// <summary>
/// Create AnimState object using state of given anim: SAVING
/// </summary>
public AnimState(int layer, Animator anim) {
//retrieve current animation info
AnimatorStateInfo info = anim.GetCurrentAnimatorStateInfo(layer);
this.currentHash = info.fullPathHash;
this.currentLength = info.length;
this.currentTime = info.normalizedTime * currentLength;
this.weight = anim.GetLayerWeight(layer);
//if there is a transition, move on and gather transition info
inTransition = anim.IsInTransition(layer);
if(inTransition) {
AnimatorTransitionInfo transInfo = anim.GetAnimatorTransitionInfo(layer);
AnimatorStateInfo nextInfo = anim.GetNextAnimatorStateInfo(layer);
if(transInfo.normalizedTime == 0f) {
//move animation forward so we have proper info to work with.
anim.Update(timeShift);
transInfo = anim.GetAnimatorTransitionInfo(layer);
nextInfo = anim.GetNextAnimatorStateInfo(layer);
anim.Update(-timeShift);
}
//retrieve all necessary info
nextHash = nextInfo.fullPathHash;
nextLength = nextInfo.length;
transitionTime = nextInfo.normalizedTime * nextLength;
transitionLength = transitionTime / transInfo.normalizedTime;
}
}
/// <summary>
/// Apply this AnimState object to the given animator: LOADING
/// </summary>
public void Apply(int layer, Animator anim) {
//apply current animation info
anim.Play(currentHash, layer, currentTime/currentLength);
anim.SetLayerWeight(layer, weight);
anim.Update(0f);
//if necessary, apply transition info
if(inTransition) {
//rewind current animation, so it can be wound forward along with the fading
float playTime = Util.PosModulo((currentTime - transitionTime) / currentLength, 1f);
anim.Play(currentHash, layer, playTime);
anim.Update(0f);
//set crossfade time and update to the required point
float crossPlayTime = Util.PosModulo(transitionTime / nextLength, 1f);
anim.CrossFade(nextHash, transitionLength / currentLength, layer, crossPlayTime);
anim.Update(transitionTime);
}
}
}
Answer by RedGirafeGames · Nov 13, 2020 at 11:22 PM
After hours of tests I ended with this version (based on yours, thanks @WARdd ). An advantage is that it works even with animator.speed == 0 (which is not the case using the "length" properties because they have "infinity" values in that case)
Get State :
private void TakeSnap()
{
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(layer);
currentHash = info.fullPathHash;
currentLength = info.length;
normalizedTime = info.normalizedTime;
inTransition = animator.IsInTransition(layer);
if (inTransition)
{
Debug.Log("In transition...");
AnimatorTransitionInfo transInfo = animator.GetAnimatorTransitionInfo(layer);
AnimatorStateInfo nextInfo = animator.GetNextAnimatorStateInfo(layer);
transitionDuration = transInfo.duration;
transitionNormalizedTime = transInfo.normalizedTime;
nextHash = nextInfo.fullPathHash;
nextNormalizedTime = nextInfo.normalizedTime;
}
}
Apply State :
private void ApplySnap()
{
if (inTransition)
{
animator.Play(currentHash, layer, normalizedTime);
animator.Update(0.0f);
var transitionFixedTime = transitionDuration * transitionNormalizedTime;
animator.CrossFade(nextHash, transitionDuration, layer, nextNormalizedTime,
transitionNormalizedTime);
}
else
{
animator.Play(currentHash, layer, normalizedTime);
}
}
Some Unity documentation is really a shame... not even possible to understand how to use crossFade without countless tests...