Search Unity

  1. Are you interested in providing feedback directly to Unity teams? Sign up to become a member of Unity Pulse, our new product feedback and research community.
    Dismiss Notice

Reset Animator (usage with Pooled Object)

Discussion in 'Animation' started by NicolasHognon, Jan 13, 2015.

  1. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    Hello,

    On my project I have pooled objects

    - instead of destroying objects, objects are deactivated using SetActive(false)
    - when I need a new object I look for an inactive object and I active it using SetActive(true)e

    Everything seems ok but when my pooled gameObject as animator attached to it sometimes things goes wrong. Animation and state are not "coherent". I suppose I need to correctly reset/stop the Animator when I deactivate or reactivate my object but I did not now how to do it.

    If found other people (on this forum, stackoverflow, ....) having similar problems but no one seems to have to a solution.

    Is there a solution to save the animator in Awake and restore it latter ?
     
    EGA likes this.
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    11,558
    When you disable and enable an Animator component, it resets the animator controller state. How about setting the parameters to the values you want in OnEnable()?

    [EDIT: No longer the case in Unity 5.x. In Unity 5.6 (maybe earlier), deactivating & reactivating the GameObject doesn't reset it either, nor does Rebind().]
     
    Last edited: Apr 28, 2017
    kantagara likes this.
  3. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
  4. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,674
    There is no solution right now to restore the state of an animator. Every time an animator is disabled, all resources owned by this animator are free.

    We are looking at what could be done so we would like to get more information on your user case.

    In your particular user case it look like you are using the first free animator from the pool but you probably don't want to restore the state of this animator but rather the state of another animator.

    We were looking at adding some new API function that would allow you to push and pop an Animator's memory state. Just before calling deactivate you would push the state and right after reactivating the object you would pop the state but I think that in your case that wouldn't work right?

    In your case I think you would need to own and manage the animator memory state, so it would be better if we create a scripting object that hold the animator state that you could save on disk and then later push it on your next free animator from the pool right?

    something like this
    Code (CSharp):
    1.  
    2.        Animator animator = GetComponent<Animator>();
    3.  
    4.         AnimatorSnapshot snapShot = animator.snapShot;
    5.         animator.SetActive (false);
    6.         .....
    7.         animator.SetActive (true);
    8.         animator.snapShot = snapShot;
    9.  
     
  5. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,674
    Using reflection won't help you here because the animator internal memory state is not exposed at all, neither by script or through Unity transfer function.
     
  6. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    I'll give you some details :

    - I have got a class called PooledObject which looks like this

    Code (CSharp):
    1. public abstract class PooledObject : MonoBehaviour {
    2.    public int pooledAmount = 20;
    3.    private Transform _poolRoot;
    4.    private PooledObject[] _pooledObjects;
    5.    private bool _terminate = false;
    6.    public abstract void CloneFromOriginal(PooledObject original);
    7.    public GameObject GetObject(PooledObject original)
    8.    {
    9.      for (int i=0 ; i<_pooledObjects.Length ; i++) {
    10.        if (_pooledObjects.gameObject.activeInHierarchy==false) {
    11.          _pooledObjects.gameObject.transform.parent = null;
    12.          _pooledObjects.CloneFromOriginal(original);
    13.          _pooledObjects.gameObject.SetActive(true);
    14.          return _pooledObjects.gameObject;
    15.        }
    16.      }
    17.      Debug.LogError("Not enough pooled Objects for " + gameObject.name + " : " + _pooledObjects.Length);
    18.      return null;
    19.    }
    20.    public void Release()
    21.    {
    22.      gameObject.transform.parent = _poolRoot;
    23.      gameObject.SetActive(false);
    24.    }
    25.    ...
    26.    more code here
    27.    ...
    28.    
    29. }

    - I have a class called EnnemyColorSwitch which inherit from PooledObject

    - The CloneFromOriginal method of this class is empty ... but if I have to do something about Animator I will do it here.

    - I have got a prefab which a EnneColorSwitch Component and an Animator component.

    - To create "ennemy" I do it like this



    Code (CSharp):
    1. [I][I][I][I][I]PooledObject ennemiePooled = prefab.GetComponent<PooledObject>();[/I][/I][/I][/I][/I]
    2.    GameObject p = ennemiePooled.GetObject(ennemiePooled);
    3.  
    4.    p.transform.parent = transform;
    5.    p.transform.localPosition = gd.position;
    6.      
    7.    EnnemyColorSwitch ennemy = p.GetComponent<EnnemyColorSwitch>();
    8.    if (ennemy!=null) {
    9.      ennemy.CheckChapoColor();
    10.    }
    11.  
    with

    Code (CSharp):
    1.  
    2.    public void CheckChapoColor()
    3.    {
    4.      _anim.SetBool("sameColor",ComputeBooleanValue());
    5.    }
    6.  
    - Here is two screenshots "showing" the problem. Both "monsters" have the parameter "sameColor" to true and the animation "monster_A_Blue_Off" running which is normal in this configuration (the Player is also blue). But the first monster looks different than the second one. This my problem ... the first monster looks like if the animation "monster_A_Blue_On" is running.

    - Do you think I can use a transition from "Any State" to help me solve my problem ?

    Any help is welcome.

    Regards
     

    Attached Files:

  7. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    And to answer your question : yes "AnimatorSnapshot" can be a great help.
     
  8. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    I try to use a transition from "Any State" but it does not help.
     
  9. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    Hello @Mecanim.Dev do you have any work around to solve my problem. Today I discuss with a coworker and he proposed me some change to do in my mecanim graph .... I will try it tonigh at home. But if you have any recommendations it can be helpful. Thanks again.
     
  10. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,674
    It a little bit hard to find the problem without the project. Both are using the same controller and both monster are instantiated from the same prefab so it should be the same unless you have some script triggering something.

    If you think there is a bug please log it with your project so we can repro.
     
  11. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    ok
    I'll try to create a minimal project to reproduce the problem as it not easy to reproduce and understand it with the full project because you have to play sometimes before it occurs.
     
  12. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    Hello @Mecanim.Dev.

    I have created a minimal project and report a bug though the unity editor (Case 663363).
    While working of the minimal project I found a workaround but I am not sure it will be easy to integrate to my full project.
    The idea was to "reset" the animator to it is default state before "releasing" the pooled object. In my case it means set "sameColor" parameter to true. But I cannot called SetBool and Release in the same so I have to wait one frame before releasing my pooled object. I explained it in my bug report and in the project attached to it.

    Thanks again.
     
  13. Anx

    Anx

    Joined:
    Jan 2, 2014
    Posts:
    6
    That worked perfectly for me too in the Unity editor. But once I tried to run this on an iOS device it never seemed to "reset" it. I starts up a loading screen at the same time tho... but I also tried changing the Animator to "Always Update" so it shouldn't cull the transform.

    Tried it on an iOS device too by any chance?
    Or any updates from Unity on your Case yet.
     
    Last edited: Jan 27, 2015
  14. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,674
    Hi Nicolas,

    So I had some time today to look at your issue and effectively it look wrong.

    One way to fix this for you very easily is to make sure that every clip have at least one curve to animate your sprite renderer. You first clip monster_A_blue_Off doesn't write any values in SpriteRender component to set correctly the sprite to render. So if you add those curve on you monster_A_Blue_off clip you will always have the same behaviour when you activate any of your pooled object.

    For long term we are thinking that we should add a new API function Animator.WriteDefaultValues() that would ease the workflow for anybody using pooled object that want a deterministic behaviour when polling a new object.

    We can't enforce this when the animator is deactivated because there is many other use case where some user want their object to keep the last evaluated animation frame.

    monster.png
     
  15. NicolasHognon

    NicolasHognon

    Joined:
    Nov 15, 2010
    Posts:
    31
    ho .... I missed your answer .... I will look at it ....
    and thanks again for your time
     
  16. Ash-Blue

    Ash-Blue

    Joined:
    Aug 18, 2013
    Posts:
    99
    I wrote a script that will repair an animators child objects to their original state OnDisable. It can be adopted easily to repiar any kind of property you need to restore. Hopefully this saves some time for someone.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. namespace Anim {
    5.     // OnDisable this will repair all first level transforms to their original state
    6.     // Fixes an issue where the animator loses all notion of enabled/disabled GameObject states
    7.     public class AnimatorRepair : MonoBehaviour {
    8.         [Tooltip("Defaults to self")]
    9.         [SerializeField] Transform target;
    10.  
    11.         Dictionary<Transform, bool> transRecord = new Dictionary<Transform, bool>();
    12.  
    13.         void Awake () {
    14.             if (target == null) target = transform;
    15.  
    16.             // Cache and record the default state of the character          
    17.             foreach (Transform child in target) {
    18.                 transRecord[child] = child.gameObject.activeInHierarchy;
    19.             }
    20.         }
    21.  
    22.         void OnDisable () {
    23.             foreach (Transform child in target) {
    24.                 if (transRecord.ContainsKey(child)) {
    25.                     child.gameObject.SetActive(transRecord[child]);
    26.                 }
    27.             }
    28.         }
    29.     }
    30. }
     
  17. kantagara

    kantagara

    Joined:
    Sep 15, 2014
    Posts:
    11
    I actually tried Rebind in Unity 2017.3 and it works perfectly! Thank You so much.
     
  18. PZ4_Bailey

    PZ4_Bailey

    Joined:
    Oct 15, 2017
    Posts:
    6
    Any solution to this yet?
    Its driving me crazy! The Unity team should stop sleeping
     
  19. kantagara

    kantagara

    Joined:
    Sep 15, 2014
    Posts:
    11
    I found the best workaround for this. whenever my enemy gets deactivated i put him in reset state where I put every object that's on him to the prefabs' rotation position scale etc.
    And that works perfectly.
     
  20. Punfish

    Punfish

    Joined:
    Dec 7, 2014
    Posts:
    92
    This is such an annoying issue. Especially since you can't keyframe multiple transforms at each by simply selecting them and pressing keyframe. This causes transforms to assume their last state if they aren't updated in the next animation. So while the animation may look fine while you record because you're building off the default pose, it may not play correctly because some bones will be that of the last animation rather than the prefabs pose.

    This NEEDS to be an option; so many issue threads about it.

    Worth noting I tried to reset all of the transforms positions, scales, rotations, and enabled state OnEnable and they're still behaving very oddly. It's almost like bones which aren't modified in animations are stuck on the last animation played before disabled (I'm pooling them).
     
    Last edited: Jul 9, 2018
    yyylny likes this.
  21. fedorenkosergiy

    fedorenkosergiy

    Joined:
    Jun 11, 2014
    Posts:
    5
    Try use
    Code (CSharp):
    1.  
    2. private void Start()
    3. {
    4.     Animator myAnimator = GetComponent<Animator>();
    5.     myAnimator.keepAnimatorControllerStateOnDisable = true;
    6. }
    7.  
     
    xodennisxo likes this.
  22. MadWatch

    MadWatch

    Joined:
    May 26, 2016
    Posts:
    96
    I see the WriteDefaultValues() method has been added to the Animator class to fix this problem. But I didn't manage to make it work. I call it into OnDisable(), but it does nothing, the problem is still the same.

    How am I supposed to use it ?
     
  23. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    1,773
    @MadWatch

    Try calling it before disabling the object. I haven't used that method specifically, but when trying to do something similar with the Playables API I found that it was possible to reset the object before calling GameObject.SetActive(false), but trying to do so in my script's OnDisable had no effect.
     
  24. MadWatch

    MadWatch

    Joined:
    May 26, 2016
    Posts:
    96
    Thank you SilentSin.

    That might be difficult in my case because my game object is being disable as a result of the parent being disable. I'm not calling SetActive(false) on it explicitly.

    I made it work by moving my script component (the one with the OnDisable() callback) above the animator component. That way WriteDefaultValues() is called before the animator is disabled. I'm not sure this is a good solution though. I don't know if Unity guarantees callback order.
     
  25. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    1,773
    Yeah I never really found a satisfactory solution and just moved on because the Playables API doesn't have the same issue with reactivating objects.

    I never considered the component order though. Try giving your script a [DefaultExecutionOrder] attribute with -10000 then 10000 to see what happens with it very early and very late. If that doesn't affect it, then the component order is probably reliable.
     
  26. MadWatch

    MadWatch

    Joined:
    May 26, 2016
    Posts:
    96
    Really ?

    The playable API allows you not to use an animator controller, but you still need an animator component so the issue with disabled object should be the same. Or do you use the playable API without an animator component ?
     
  27. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    1,773
    Yeah, you still need an Animator component. I honestly don't know why it's a problem in the first place, it's not like I had to do anything to work around it, it just works out of the box so the AnimatorController must be intentionally doing something that screws it up. I wrote a bit about it in the user manual of my Animancer plugin.
     
    Last edited: Jan 26, 2020
  28. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    217
    I'm running into a related problem when "virtualizing" NPCs... basically we use a dynamic scene loading model similar to INSIDE where we hide NPCs when they are not in a loaded scene and then bring them back when they are. This creates bizarre issues with animation.

    It seems that when a gameObject is de-activated the animator writes over the "default state" of the animator using the currently active animation. When activated a new animation can be played but the default state is now that of the last running animation not the true default state of an neutral non-animated NPC.

    So at deactive time if I'm running Idle_LookAround, then whatever activation and transform state I have in that animation is now the default state for all other animations.

    Of course this causes all sorts of problems b/c ALL my animations (done in the unity animation editor) assume the transform and activation state of the "neutral non-animated" character.

    Calling
    WriteDefaultValues()
    doesn't work... it appears that it actually forces the NEW, on de-activate(), values to be written :|.

    I don't think I want to
    keepAnimatorControllerStateOnDisable
    b/c I actually do want the animator to reset to its true defaults.

    UPDATE: I've read elsewhere people suggesting to add a "reset" animation to your animator and then calling it via a trigger... I imagine this will work, but it's definitely a hack and prone to error :|.

    UPDATE: neither of the above works.
    UPDATE: I tried calling
    StopPlayback()
    before doing
    WriteDefaultValues
    but it makes no difference. FWIW, I make sure to deactivate the gameObject on the NEXT frame, allowing, I would hope, time for the write to execute before disabling :|.

    I'm going to try @Ash-Blue 's solution next... but although clever it seems like this is a Unity problem, and should be addressed correctly.

    UPDATE: I'd need to not only maintain a list of the default active/deactive state but of all position, scale and rotate too... that seems like overkill since I assume that is already done somewhere inside the animator.

    Any thots here? TIA!
     
    Last edited: Apr 19, 2019
    MaskedMouse, yyylny and Flurgle like this.
  29. OrderOfOut

    OrderOfOut

    Joined:
    Sep 21, 2018
    Posts:
    21
    Has anyone solved this yet? I'm making a Crystalis-style game and my object pool works fine with simpler stuff like projectiles. But when I add enemy soldiers back from the pool, their animator behaviors are bizarre. I stop all my coroutines and everything, reset any member variables to their initial states, but it doesn't help. I'll click on one soldier that seems to be stuck and ignoring his patrol path, and the gizmos will highlight a completely different soldier, but when I move it, it's the non-highlighted soldier that moves. Some even walk backwards to their old patrol positions. Is there no fix for this yet?

    I'm honestly starting to wish I hadn't used Mecanim; we shouldn't have to resort to convoluted hacks (which are inherently prone to bugs, especially when considering cross platform performance) to get Animators to work with object pooling. Scripting my own simple animator with sprite indices and coroutines probably would have been more straightforward to begin with.
     
    Last edited: Jan 26, 2020
    yyylny likes this.
  30. yyylny

    yyylny

    Joined:
    Sep 19, 2015
    Posts:
    55
    If anyone ever finds a solution to this, please share with us. I've been wasting days trying to find a solution but so far I couldn't come up with anything that was reliable enough.

    The only thing that seems to have worked is calling Rebind(), then placing the object out of the screen for half a second with a co-routine (instead of dis-activating it right away), then dis-activating the object. However, I'm not sure it's always going to work on every platform and it is a waste of resources to use a co-routine.
     
    Last edited: Feb 19, 2020
  31. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    1,773
    I was able to reset the animated values to their defaults in Animancer by having my OnDisable method call animator.Rebind() and ensuring that my script is above the Animator in the Inspector so that my script gets notified before the Animator does when disabling the entire GameObject. I don't know if the same would work with an Animator Controller, but it's probably worth trying.
     
  32. yyylny

    yyylny

    Joined:
    Sep 19, 2015
    Posts:
    55
    Thanks Kybernetik! That's pretty much what I did and it doesn't always work. Maybe because my script is a subclass and I put the code in the OnDisable() of its base class. Anyway, I've already implemented my solution of Rebind() and a half second delay before deactivation and it seems to work so I'll stick to it meanwhile.
     
  33. EGA

    EGA

    Joined:
    Sep 12, 2014
    Posts:
    100
    Any update on the best solution for Unity 2019 or 2020?
     
  34. kira911911

    kira911911

    Joined:
    May 29, 2015
    Posts:
    7
    Hello everyone! Any update?
     
  35. Mitch_Heinsenberg

    Mitch_Heinsenberg

    Joined:
    Sep 29, 2020
    Posts:
    15
    I am running into an issue with game objects from a pool playing a sprite animation on death, and when they get activated again, they are "stuck" on the last frame of the animation with the actual object sprite being hidden.

    Is this a known issue and what is the way to fix it?
     
  36. yyylny

    yyylny

    Joined:
    Sep 19, 2015
    Posts:
    55
    Yes, this thread is full of posts from people who have experienced that problem in the past. As was written in one of the posts, it happens because: "Every time an animator is disabled, all resources owned by this animator are free". What I did to solve this is to call Animator's Rebind() method, wait about half a second and only then disable the object. If you need to hide the object immediately, just move it off-camera instead.
     
    KalOBrien likes this.
  37. Mitch_Heinsenberg

    Mitch_Heinsenberg

    Joined:
    Sep 29, 2020
    Posts:
    15
    This worked like a Charm! Thank you .
     
    Last edited: Jul 13, 2021
    yyylny likes this.
  38. Sagap

    Sagap

    Joined:
    Feb 24, 2019
    Posts:
    2
    Running into the same issue in Unity 2020.3.13f1 while working on a monster spawner that makes use of an object pool.
    In my case, if a monster is killed (deactivated) while, for example, jumping, it will randomly snap back into keyframes from the jump animation once respawned.

    Still hoping for an elegant solution to come up :)
     
  39. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    717
    I am so confused with the so many ways of resetting the state of the object.

    The default state I have is a state without animation.
    Whenever I switch states it enables certain game objects in the animation clip.
    Whenever they exit their state it automatically disables them (like toggling the Preview button in the animation tab)

    Now that I disable the whole game object, it seems to keep it's last state. Game Objects that were activated by the last state are still active after re-enabling.

    So what does one need to do really?
    There's
    Rebind()
    ,
    WriteDefaultValues()
    ,
    keepAnimatorControllerStateOnDisable

    Set/call it OnDisable, OnEnable, Start?

    Sure I can create a "Default" animation clip where I set the initial values of ALL animated properties... but that's a lot of work and whenever someone adds a property in another clip in another state it has to be set in the Default as well?...

    Then there's also the Write Defaults boolean on every state
    On Disable of the game object I want it to clear the current state, I want it to reset to its initial state in the Animator and reset the state of the hierarchy / components. Game objects that have been activated throughout the different states should be reset back. Properties of components that have been animated should be reset back.
    So that when you re-enable the game object it starts off clean. Ready to be manipulated again.

    Edit:
    In my case I don't want to keep the current state on disable.
    I want to keep the initial state as it started with and reset to that on disable.
     
    Last edited: Aug 5, 2021
unityunity