Search Unity

StateMachineBehaviour OnStateMachineEnter / Exit broken in 5.0.2 / 5.1?

Discussion in 'Animation' started by nafonso, Jun 7, 2015.

  1. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    Hi,

    I'm trying to use StateMachineBehaviours, and the OnStateEnter messages trigger fine, however the OnStateMachineEnter / Exit never get called, even though the Animator is moving between sub state machines. Is there some little trick I'm missing or is this broken?

    Regards,
    Nuno Afonso
    http://www.fluxeditor.com
     
  2. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Hi Nuno,

    Not that we are aware of. Did you put this SMB on a state machine? it won't work on a state.
    If yes then can log a bug with a project please?

    Thanks
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I've had this experience too: if I put a script with OnStateMachineEnter on a sub-state machine, that method is not fired when that state machine is entered.

    The automatic comment generated for OnStateMachineEnter is this:
    "// OnStateMachineEnter is called when entering a statemachine via its Entry Node"

    The docs, on the other hand, state that the method is:
    "Called on the first Update frame when a transition from a state from another statemachine transition to one of this statemachine's state."

    From testing, it's clear that the version from the comment is the one that's actually used.

    The version from the docs is usefull, as it can be used to check if a sub-state machine has been entered, no matter how it was entered.

    The actual version is close to useless - if you ever enter a state in a sub-state machine through something else than the entry node - ie. a direct transition from another state, a transition from the "any state" node, or a forced transition from a script - the method won't be called.
     
  4. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    I'll try to get one to you one of these days, but today I noticed that in the Default Layer they were being triggered, so maybe they problem is either with a) having more than 1 layer or b) that the states I hooked up were not in the layer 0.

    Whenever I get a repro (or fail to) I'll let you know.

    Regards,
    Nuno Afonso
    http://www.fluxeditor.com
     
  5. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    I think I found why it was happening, but it's behaviour I didn't expect. In the docs about OnStateMachineExit it says:
    Called on the last Update frame when one of the statemachine's state is transitionning toward another state in another state machine​

    However, it is only called if the transition is going through the Exit node, if I connect a state directly to another state machine / state, OnStateMachineExit doesn't get called.

    Is this expected behaviour?! IMO it should work, but if it doesn't I think that the docs should explain that.

    Regards,
    Nuno Afonso
    http://www.fluxeditor.com
     
  6. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    Also, very weird that you can attach them to Layers, but it doesn't seem that they do anything when attached to Layers. What I expected was that it would either call OnStateMachineEnter/Exit when it went to a state in that layer (i.e. basically seeing that layer as a state machine) or on all state machines of that layer, but it does neither of those.

    What's the point of being able to attach them if they don't do anything there?

    EDIT: BTW the OnState functions do get called, so this is quite confusing.
    EDIT 2: I'm already using 5.1, so this behaviour is in 5.1 too.

    Regards,
    Nuno Afonso
    http://www.fluxeditor.com
     
    Last edited: Jun 18, 2015
  7. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    OnStateMachineEnter and OnStateMachineExit are both special message that are called in sync with the animator, they should be used to control which transition from the entry/exit node should be taken. Both message interrupt the state machine evaluation to allow you to choose your next transition.

    Yes exactly, the doc is wrong and will be updated.

    You don't attach them to layer but StateMachine, when you select a layer in the animator tool under the hood we select the root statemachine for this layer, all sub statemachine are visible in the graph view but not the root one.

    Yes this is expect to, if you set a StateMachineBehaviour that does define OnStatexxx on a StateMachine, all state from this statemachine will also call those message. This is useful if you want all state from the same statemachine to inherit the same behaviour.

    With this behaviour if your StateMachineBehaviour define both OnStateEnter/OnStateExit and you place this behaviour on a statemachine you will get a OnStateEnter everytime you have a transition from another statemachine to this one but also when you have an internal transition, a transition within the same statemachine.
     
  8. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    But that's really unfortunate. A lot of times you want to leave a state from one SM to another but not through the entry point, you want to go straight into the middle of something.

    Does this mean that to get around this I have to either (a) have different variables to then say "ok, get into it but go to this other state" or (b) do checks on states instead of SM?

    Something that also bothers me is that there's no way to cache anything. By default, it is said that for each SM Behaviour one instance is spawned, so since that instance is spawned it would be great to have an "OnEnable" kind of event at the start (and when AnimatorController gets assigned to another Animator) where I could get some variables and cache them (e.g. in my case I always need to call animator.GetComponentInParent<NPC>() and Animator.StringToHash(), so it would be great to be able to cache these things). Any chance this can happen?

    That's what I assumed, but since I never got any OnStateMachineEnter/Exit it felt broken. It also doesn't propagate to children SMs like it happens with OnStatexxx functions, but shouldn't it? Or OnStateMachinexxx only get called on the SM they are attached to? In any case, more docs on this would be great.

    I get what you suggest, however that doesn't work in my case. I need to know which state I left and OnStateEnter doesn't have the information of where you're coming from, only where you are.

    You could say "well, but then just use OnStateExit", and that's true, but the reason why I don't like it is because I need to check multiple states, so either (a) I add a specific SMBehaviour script to each state I may leave from (which I don't like because there's a lot of SMBehaviour files for a single separate thing, just feels like it's bloating number of code files for no reason) or (b) I need to check for multiple string state hashes (which can't be cached like I mentioned above, so feels like it bad performance coding).

    Hence why OnStateMachinexxx functions where so useful, only care that I'm leaving that state, but the problem is that I needed to exit straight to another state and not through Exit node.

    EDIT: BTW, yeah, I can just have something that says "if( _npc == null ){ cache stuff }", just feels wrong :p

    Regards,
    Nuno Afonso
    http://www.fluxeditor.com
     
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I've got a bunch of _npc = _npc ?? animator.GetComponent<NPC>(), but an OnEnable would be really nice.

    I'm also not getting this whole design. As I see it, there's no way to create a method that's called whenever you enter/exit a sub state machine, unless you're using the entry/exit node. Intuitively, the best place to put a behaviour that needs to be called on any state within the state machine is on the Any node, not on the machine itself.

    Also, I'd really like to be able to drag-and-drop statemachinebehaviours onto states, like you do with MonoBehaviours on game objects. Also, selecting "add behaviour" should put the caret in the field where you write the name of the script. These are minor things, but really, it'd be great.
     
  10. pierrepaul

    pierrepaul

    Unity Technologies

    Joined:
    Jun 19, 2012
    Posts:
    162
    Hi.

    Animator.GetCurrentAnimatorStateInfo(layerIndex) can give you that information no ?
     
  11. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,660
    That doesn't sound right to me - I think the default is that you get one SMB instance per Animator component, and if you want to share an instance across multiple Animators then you need to use SharedBetweenAnimatorsAttribute.

    Have you considered doing something like this from a script on the same object as the animator:

    Code (csharp):
    1.  
    2. // Have your SMBs implement this interface
    3. public interface IInitializableBehaviour
    4. {
    5.    void CacheValues(GameObject srcObj);
    6. }
    7.  
    8. // then run this code in Awake/Start/OnEnable/etc
    9. foreach(var smb in GetComponent<Animator>().GetBehaviours<StateMachineBehaviour>())
    10. {
    11.    var initializable = smb as IInitializableBehaviour;
    12.    if (smb != null) smb.CacheValues(gameObject);
    13. }
    14.  
     
  12. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    OnEnable would be more convenient - you might not need a script on the object for any other reason. It'd also be consistent with design, since pretty much every other kind of component - MonoBehaviours, ScriptableObjects, Editors, EditorWindows, etc. etc. has an OnEnable.
     
    ASymShade likes this.
  13. nafonso

    nafonso

    Joined:
    Aug 10, 2006
    Posts:
    377
    True, some times you're just so deep into the problem that you forget to take a step back.

    @superpig, that's what I meant, when I said one instance is spawned it was that each one would have it's instance, hence why OnEnable was useful.

    In any case, I think that atm what @superpig suggested is probably the best option, i.e. manually call an initialisation step on the animator's SMBehaviours.

    I just think that going through the exit node is annoying, forces me to do 2 transitions now instead of one: one to exit node, and then another from the SM to the other place I really want to go. But if it's not going to change, then so be it.

    Regards,
    Nuno Afonso
    http://www.fluxeditor.com
     
  14. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    By the way OnEnable and OnDisable is supported on StateMachineBehaviour, in 5.1 there is one drawback those method are called even when not in play mode, but in 5.2 we are changing this to only call those method while in play mode.

    We are also looking at adding StateMachineBehaviour.OnStart(Animator animator)
    OnStart is called when all StateMachineBehaviour script are instantiated by the /animator/, which happens just before any State is played on this StateMachineBehaviour's parent AnimatorController.
    When the game loads, the Awake and OnEnable function are called on all StateMachineBehaviour from the same Animator controller, then the OnStart function is called for all those StateMachineBehaviour.
     
  15. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I've got another problem that might be a bug: when a substate machine is exited due to a transition from an Any state node, the OnStateMachineExit method is not called.

    So the setup is like this: I've got a character model with a sub state machine named "crawling". There's a SMB on the crawling machine that essentially does this:

    Code (Pseudocode):
    1. OnStateMachineEnter: slow down character to half speed
    2.  
    3. OnStateMachineExit: return character to normal speed
    that works great. BUT: I also want the character to automatically exit the crawling state machine in some situations - like when the character takes damage. I can implement that just fine by setting up the transition AnyState->Damage->NormalMovement on a damage trigger, but then my character will be stuck in half speed if it was crawling, due to the OnStateMachineExit method not being called on the crawling state.

    I find this very strange; OnStateMachineExit sounds like it should be called whenever the state machine is exited. The docs for the method also claims that it is "Called on the last Update frame when one of the statemachine's state is transitionning [sic] toward another state in another state machine". In my book, this means that the method should fire when the transition from the any node happens.

    @Mecanim.Dev , is this intended behaviour? In that case, is there any good way to set up the concept "I want this to fire whenever this sub state machine is exited, regardless of how it was exited" without resorting to checking the state hash on every frame? I tried adding this to the state machine behaviour on the SMB:

    Code (csharp):
    1.     public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    2.         if(animator.GetAnimatorTransitionInfo(0).anyState) {
    3.             Debug.Log("exits a state from an any state node");
    4.             //Speed up character
    5.         } else {
    6.             Debug.Log("exits a state internally");
    7.         }
    8.     }
    But the "exits a state internally" message happens no matter how the state is exited, so maybe the anyState bool is broken? I dunno.

    I could post a bug report, but since the docs for these features are severely lacking, I have no idea what's intended behaviour and what's a bug.
     
    Bezzy likes this.
  16. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    This is expected, OnStateMachineEnter/Exit is called when transitioning from/to a StateMachine. This is not called when transitionning into a StateMachine sub-state. Basically the control flow must pass by an entry node or exit node to get the OnStateMachineEnter/Exit call back.

    The doc is wrong and will be updated
     
  17. Bezzy

    Bezzy

    Joined:
    Apr 1, 2009
    Posts:
    75
    Hi all, (Small world, Nuno! It's Aubrey!)

    So, I am definitely digging using state machines for a bunch of things, but I'm having exactly the same issue/concern/assumption about sub state machines. In principle, they're a great way to wrap up state behaviours, and I'm even using some of the state machine system to drive colour palette systems, and menu traversal. But all that lovely modularity is lost when I have to do big work arounds to take the interruptions into account.

    So, if I want to be able to do this, I should make an extra trigger to first exit the current sub-state (to force it to go through the exit node), and THEN trigger my "from AnyState" transition?

    It seems a lot of work, when you intuitively expect OnStateMachineExit to trigger when the State Machine is Exited.

    To my mind, I would really just like OnStateMachineExit to be called whenever it's exited, regardless of going via an exit node, but also have the reason for the exit (i.e. transition data) passed in as well? That's certainly how I've seen/used/made them in the past. (I do accept that this might be a bit more complicated because of transitions having durations and cross fades and whatnot).

    It's also quite confusing to know what to make of "AnyState" triggering in a sub state vs. the base layer. Which would trigger first if you had an AnyState transition in a sub layer AND base layer? Or is it a sort of singleton when you inspect it?

    ------------------
    I also found that stateInfo.normalizedTime was always reporting "1" even if the state had been interrupted, but then, the other day, some Debug stuff lit up to suggest that this has been fixed? I'm not sure. I might be misremembering things. It was this bug: http://fogbugz.unity3d.com/default.asp?717923_3h8u2390h4p5aqp5
    ------------------
    One other small point: Say you inherit a StateMachineBehaviour for something generic, like setting an animation to playback to a desired duration, and you want to use that in multiple places. I'm finding it hard to use GetBehaviours<>() to distinguish which state instance I'm getting. Is there no member on the StateMachineBehaviours which can just point to the state it's attached to (in the same way that every MonoBehaviour has a .gameObject member)? Or should I otherwise be going via some kind of hashed lookup?

    In the end I have to make little stub inherited types which do nothing but give themselves a unique class name so that I can use GetBehaviour<>(). Feels like a work around.

    ---------------------
    Plus, a "hash to string" util would be pretty amazingly useful for debugging.
     
    Baste likes this.
  18. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Hash to string would be amazing. The cost of having an internal hashtable around that you could check would be minimal.

    So how am I supposed to notice that a sub state machine has been exited?
     
    bererton and Bezzy like this.
  19. kilik128

    kilik128

    Joined:
    Jul 15, 2013
    Posts:
    909
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    Code (CSharp):
    1.  
    2.  
    3.  
    4. override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    5.  
    6.  
    7. if (onetime == false){
    8. onetime = true;
    9. if (atk == true)
    10. animator.transform.root.BroadcastMessage("ready_to_fight", true, SendMessageOptions.DontRequireReceiver);
    11. Debug.Log ("SSSSSSSSSSSSSSSSSEEEEEEEEEENDDD");
    12. }
    13.  
    14. }

    i got 2 debug on this ?
     
  20. mr_blahblah

    mr_blahblah

    Joined:
    Jan 15, 2016
    Posts:
    38
    I'm necroing this thread as it's the only one I could find, and it hasn't been solved.

    OnStateExit is broken - or its functionality is so arcane that it may as well be.

    I have a State (call it A) that transitions from 'Any State'. State A has an smb that logs out a message in OnStateExit.

    This message is never sent. I have no idea why - Mecanim really needs a rethink if this is intended behaviour. Using 5.3.4.

    Edit: *sometimes* OnStateExit fires, and sometimes it doesn't. There's no logic to it AFAICT.

    Is this normal behaviour???
     
    Last edited: Jun 26, 2016
  21. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I've used state machine behaviours a lot more since the last time I posted in this thread, and let me tell you, it's hands down the worst API in Unity. That being said, I don't think I've seen OnStateExit not sending. OnStateMachineExit is broken by design, but the normal exit is reliable enough.

    Doing a brief check to update my memory, Exit is played both when transitioning directly to another state, when transitioning from the exit node, and when calling animator.Play.

    What exactly does your state machine look like?
     
  22. mr_blahblah

    mr_blahblah

    Joined:
    Jan 15, 2016
    Posts:
    38
    Scary...

    It's a pretty simple setup, see thumbnail.

    aCapture.JPG
     
  23. mr_blahblah

    mr_blahblah

    Joined:
    Jan 15, 2016
    Posts:
    38
    Ack, ok. It does fire. However, I couldn't tell, as my Debug.Log command was calling a variable from the animator ref in OnStateExit that was based on the animator's gameobject. It didn't yield an error, but it also didn't display - leaving me to believe it was completely broken.
     
  24. mr_blahblah

    mr_blahblah

    Joined:
    Jan 15, 2016
    Posts:
    38
    This raises another question - why can't i set variables on the gameObject that the animator belongs to? Bizarre.
     
  25. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    You can - you have to go through the animator component though. So:

    Code (csharp):
    1. public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    2.     GameObject go = animator.gameObject;
    3.     go.name = "This works";
    4.  
    5.     MyScript ms = animator.GetComponent<MyScript>();
    6.     ms.Foo();
    7. }
    Or doesn't that work?

    And could you post the code that's failing? That doesn't reflect my experiences with the system.
     
  26. grossimatte

    grossimatte

    Joined:
    Mar 15, 2013
    Posts:
    43
    Sorry guys but i am facing the same issue, and even after reading the whole thread i still can't figure to make it work.

    What am i supposed to do if i wan't check OnEnter and OnExit state on every state in an animator controller?

    I am using the following code but Unity seems to skip some states Enter/Exit:

    Code (CSharp):
    1.  override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    2.     {
    3.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("FIRST"))
    4.         {
    5.             Debug.Log("First Enter ");
    6.         }
    7.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("SECOND"))
    8.         {
    9.             Debug.Log("Second Enter ");
    10.         }
    11.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("THIRD"))
    12.         {
    13.             Debug.Log("Third Enter");
    14.         }
    15.  
    16.     }
    17.     override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    18.     {
    19.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("FIRST"))
    20.         {
    21.             Debug.Log("First Exit");
    22.         }
    23.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("SECOND"))
    24.         {
    25.             Debug.Log("Second Exit");
    26.         }
    27.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("THIRD"))
    28.         {
    29.             Debug.Log("Third Exit");
    30.         }
    31.     }
    Every transition has an Exit Time (duration .1, Exit time .9)
    My console look like this

    upload_2016-12-7_21-32-29.png

    When i am expecting

    First Enter
    First Exit
    Second Enter
    Second Exit
    Third Enter
    Third Exit

    Am i doing something wrong?

    Thanks!
     
  27. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    @grossim
    You're SMB is only looking for the current state info(animator.GetCurrentAnimatorStateInfo(0)) which is wrong because when you are transitionning from state FIRST to state SECOND, SECOND is the next state.

    Since from the SMB perspective is impossible to know if the SMB state is the current or next state that why we do send you the AnimatorStateInfo stateInfo for the SMB state as a parameter.

    Code (CSharp):
    1.  
    2. override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    3.     {
    4.         if (stateInfo.IsName("FIRST"))
    5.         {
    6.             Debug.Log("First Enter ");
    7.         }
    8.         if (stateInfo.IsName("SECOND"))
    9.         {
    10.             Debug.Log("Second Enter ");
    11.         }
    12.         if (stateInfo.IsName("THIRD"))
    13.         {
    14.             Debug.Log("Third Enter");
    15.         }
    16.     }
    17.  
    Also since your transition is not instantaneous the message should be interleaved.

    First Enter at T=0
    Second Enter at T=0.9
    First Exit at T=1
    Third Enter at T= 1.8
    Second Exit at T= 1.9
    Third Exit at T= 2.8