- Home /
Parameters and States of Animator Reset on Object's Disable
Hi whenever I disable an object with an animator or its parent the animator states and all its parameters go back to default. In addition the object still stays where I animated it, for example is I have the object animated in its x axis + 5; the object still stays in the positive 5 position even if the states and all its booleans are reset. I was wondering if there is a work around to this so that when I reenable the objects the states stay the same from where I left it.
The states shouldn’t go back to default, they should stay the way they were when disabled. Could you provide the code you use, and some more context.
We have just faced the same issue in our project. We have a complex state machine with a long list of parameters on our enemy objects. When an enemy is killed we are setting the parameters to play proper 'dead' animation. The body supposed to stay visible for all time. However, when the Renderer is beco$$anonymous$$g invisible, the Animator is reset to its default state and as well as all the parameters. So, next time you see a dead enemy it plays its default animation. I am going to adopt FamerJoe's solution to our case, but I feel like such functionality needs to be provided by the engine itself. Please vote to bring Unity attention to the issue.
Answer by FamerJoe · Mar 30, 2015 at 08:54 PM
I had the same problem, where I wanted to hide the character during teleportation; I did create this workaround. Unfortunately, by the time OnDisable is called, the anim.parameters array is empty (probably another bug), so you actually have to call OnAnimDisable before disabling the object. Then, the state will be saved and restored OnEnable. Just attach this script to the object with the Animator on it. I don't know if this will work for triggers though, as I don't use any in my project. It works for bools, floats and ints though.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PreserveAnimatorOnDisable : MonoBehaviour {
private class AnimParam {
public AnimatorControllerParameterType type;
public string paramName;
object data;
public AnimParam (Animator anim, string paramName, AnimatorControllerParameterType type) {
this.type = type;
this.paramName = paramName;
switch (type) {
case AnimatorControllerParameterType.Int:
this.data = (int)anim.GetInteger(paramName);
break;
case AnimatorControllerParameterType.Float:
this.data = (float)anim.GetFloat(paramName);
break;
case AnimatorControllerParameterType.Bool:
this.data = (bool)anim.GetBool(paramName);
break;
}
}
public object getData() {
return data;
}
}
Animator anim;
List<AnimParam> parms = new List<AnimParam>();
void Start() {
anim = GetComponent<Animator>();
}
public void OnAnimDisable() {
Debug.Log("Saving Animator state: " + anim.parameters.Length);
for (int i = 0; i < anim.parameters.Length; i++)
{
AnimatorControllerParameter p = anim.parameters[i];
AnimParam ap = new AnimParam(anim, p.name, p.type);
parms.Add(ap);
}
}
void OnEnable() {
Debug.Log("Restoring Animator state.");
foreach(AnimParam p in parms)
{
switch (p.type)
{
case AnimatorControllerParameterType.Int:
anim.SetInteger(p.paramName,(int)p.getData());
break;
case AnimatorControllerParameterType.Float:
anim.SetFloat(p.paramName,(float)p.getData());
break;
case AnimatorControllerParameterType.Bool:
anim.SetBool(p.paramName,(bool)p.getData());
break;
}
}
parms.Clear();
}
}
Just a quick question what is AnimatorControllerParameterType is that an enum you declared somewhere? anim.param is also not recognised in the animator component.
AnimatorControllerParameterType is Unity5 only. Also, I call anim.parameters, not anim.params ?
Is there any alternative for these since I'm using unity 4.6.2?
Answer by Skeldal · Mar 17, 2020 at 08:10 AM
For those who hit this thread recently (and use at least Unity 2018.1), use Animator.keepAnimatorControllerStateOnDisable = true.
If i'm not mistaken, works for states but not parameters, right?
Answer by wallstop · Feb 20, 2018 at 06:24 PM
Here's a script that will restores full state, including current animation and progress within the animation, as well as trigger values. @deeds0l
/// <summary>
/// Will restore an Animator to it's full state whenever it's game object is disabled or enabled.
/// </summary>
/// <note>
/// Requires the animator to be BENEATH this component in the component heirarchy on the object.
/// Nothing special required for script execution order.
/// </note>
[RequireComponent(typeof (Animator))]
public sealed class RehydrateAnimator : MonoBehaviour
{
private Animator _animator;
private readonly Dictionary<int, AnimatorStateInfo> _stateInfoByLayer;
private readonly Dictionary<AnimatorControllerParameter, object> _parameterValues;
public RehydrateAnimator()
{
_stateInfoByLayer = new Dictionary<int, AnimatorStateInfo>();
_parameterValues = new Dictionary<AnimatorControllerParameter, object>();
}
private void Start()
{
_animator = GetComponent<Animator>();
}
private void OnEnable()
{
if (!_animator)
{
return;
}
foreach (KeyValuePair<int, AnimatorStateInfo> layerAndStateInfo in _stateInfoByLayer)
{
int layer = layerAndStateInfo.Key;
AnimatorStateInfo stateInfo = layerAndStateInfo.Value;
_animator.Play(stateInfo.shortNameHash, layer, stateInfo.normalizedTime);
}
foreach (KeyValuePair<AnimatorControllerParameter, object> parameterAndValue in _parameterValues)
{
AnimatorControllerParameter parameter = parameterAndValue.Key;
object value = parameterAndValue.Value;
switch (parameter.type)
{
case AnimatorControllerParameterType.Bool:
_animator.SetBool(parameter.name, (bool) value);
break;
case AnimatorControllerParameterType.Float:
_animator.SetFloat(parameter.name, (float) value);
break;
case AnimatorControllerParameterType.Int:
_animator.SetInteger(parameter.name, (int) value);
break;
case AnimatorControllerParameterType.Trigger:
if ((bool) value)
{
_animator.SetTrigger(parameter.name);
}
break;
default:
continue;
}
}
ResetInternalState();
}
private void OnDisable()
{
ResetInternalState();
if (!_animator)
{
return;
}
for (int i = 0; i < _animator.layerCount; ++i)
{
_stateInfoByLayer[i] = _animator.GetCurrentAnimatorStateInfo(i);
}
foreach (AnimatorControllerParameter parameter in _animator.parameters)
{
object value;
switch (parameter.type)
{
case AnimatorControllerParameterType.Bool:
case AnimatorControllerParameterType.Trigger:
value = _animator.GetBool(parameter.name);
break;
case AnimatorControllerParameterType.Float:
value = _animator.GetFloat(parameter.name);
break;
case AnimatorControllerParameterType.Int:
value = _animator.GetInteger(parameter.name);
break;
default:
continue;
}
_parameterValues[parameter] = value;
}
}
private void ResetInternalState()
{
_stateInfoByLayer.Clear();
_parameterValues.Clear();
}
}
I get an error that the Animator is not playing an AnimatorController with this script. I thought it might be my version of Unity (2017) but it doesn't seem like it. Once the Animator is disabled, it seems that this won't work. Am I missing something, or maybe this was resolved in the later version of Unity?
Answer by RedHillbilly · Nov 05, 2020 at 06:34 PM
Might be useful for somebody else: I used @Skeldal's solution but couldn't use @FamerJoe's solution (not working in this case) to swap the animation override controller at runtime, so I also used parts of @wallstop's answer. I also needed the layer weights to remain so I added this as well. I expressed the code as an extension of the Animator class, so you can call the function on the animator itself:
animator.SwitchAnimatorController(newController);
With the extension looking like this:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public static class AnimatorEx
{
public static void SwitchAnimatorController(this Animator animator, RuntimeAnimatorController animatorController)
{
// save states
List<AnimParam> parms = SaveAnimationStates(animator);
float[] layerWeights = SaveLayerWeights(animator);
AnimatorStateInfo[] animatorStateInfo = SaveLayerStateInfo(animator);
// swap
animator.runtimeAnimatorController = animatorController;
// restore states
RerstoreAnimationState(parms, animator);
RestoreLayerWeights(animator, layerWeights);
RestaureLayerStateInfor(animator, animatorStateInfo);
}
private static AnimatorStateInfo[] SaveLayerStateInfo(Animator animator)
{
int animatorLayerCount = animator.layerCount;
AnimatorStateInfo[] animatorStateInfo = new AnimatorStateInfo[animatorLayerCount];
for (int i = 0; i < animatorLayerCount; i++)
{
animatorStateInfo[i] = animator.GetCurrentAnimatorStateInfo(i);
}
return animatorStateInfo;
}
private static void RestaureLayerStateInfor(Animator animator, AnimatorStateInfo[] animatorStateInfo)
{
for (int i = 0; i < animator.layerCount; i++)
{
animator.Play(animatorStateInfo[i].shortNameHash, i, animatorStateInfo[i].normalizedTime);
}
}
private static float[] SaveLayerWeights(Animator animator)
{
int animatorLayerCount = animator.layerCount;
float[] layerWeights = new float[animatorLayerCount];
for (int i = 0; i < animatorLayerCount; i++)
{
layerWeights[i] = animator.GetLayerWeight(i);
}
return layerWeights;
}
private static void RestoreLayerWeights(Animator animator, float[] layerWeights)
{
for (int i = 0; i < layerWeights.Length; i++)
{
animator.SetLayerWeight(i, layerWeights[i]);
}
}
private class AnimParam
{
public AnimatorControllerParameterType type;
public string paramName;
object data;
public AnimParam(Animator animator, string paramName, AnimatorControllerParameterType type)
{
this.type = type;
this.paramName = paramName;
switch (type)
{
case AnimatorControllerParameterType.Int:
this.data = (int) animator.GetInteger(paramName);
break;
case AnimatorControllerParameterType.Float:
this.data = (float) animator.GetFloat(paramName);
break;
case AnimatorControllerParameterType.Bool:
this.data = (bool) animator.GetBool(paramName);
break;
}
}
public object getData()
{
return data;
}
}
static List<AnimParam> SaveAnimationStates(Animator animator)
{
List<AnimParam> parms = new List<AnimParam>();
foreach (AnimatorControllerParameter p in animator.parameters)
{
AnimParam ap = new AnimParam(animator, p.name, p.type);
parms.Add(ap);
}
return parms;
}
static void RerstoreAnimationState(List<AnimParam> parms, Animator animator)
{
foreach (AnimParam p in parms)
{
switch (p.type)
{
case AnimatorControllerParameterType.Int:
animator.SetInteger(p.paramName, (int) p.getData());
break;
case AnimatorControllerParameterType.Float:
animator.SetFloat(p.paramName, (float) p.getData());
break;
case AnimatorControllerParameterType.Bool:
animator.SetBool(p.paramName, (bool) p.getData());
break;
}
}
}
}
It might be a bit more heavy computationally though since it creates a new list each times, but I don't think it's much, and it's a bit more practical since you don't need to add the script to the GameObject. Hope it helps somebody :)
Your answer
Follow this Question
Related Questions
Imported Blender Animations not Working 2 Answers
How to change animation clips of an animator state at runtime? Is there a way? 5 Answers
How can I get the legnth of an animation? (Mechanim Animator/C#) 2 Answers
Mechanim - should be simple, but... 2 Answers
How to achieve this EXTREMELY SIMPLE task with Mechanim? 1 Answer