Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

StateMachine Behaviour review

Discussion in 'Animation' started by Mecanim-Dev, Aug 4, 2016.

  1. Mecanim-Dev

    Mecanim-Dev

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

    I would like to take some time to talk with you guys about StateMachine Behaviour and your experience with them

    If you have any suggestions, comments or thing that really annoy you it time to speak up.
    My goal is to fix critical issue for 5.5 like

    - OnStateExit not called when an interrupted transition occur.
    - OnEnable/OnDisable called only at runtime (already in 5.5).
     
    yektasarioglu likes this.
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Hi! I didn't see this thread when it was posted! I'll get around to writing some feedback right now, just let me gather my thoughts.

    EDIT: you should probably tag these with [official] and (maybe) sticky them.
     
    Last edited: Sep 1, 2016
    theANMATOR2b likes this.
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    So, StateMachineBehaviours!

    I was really exited back when these were announced. Adding behaviours and callbacks to the animator would be a huge upgrade. I also wanted to use the system as a fast way to create non-animation state machines.

    Sadly, the system has been a pain to work with, and could need some serious improvement.

    There's two main classes of problems:
    - When the messages gets fired during a transition
    - What messages gets fired due to a transition

    I'll go into detail, but the high level overview is that the system seems to be designed to write code that changes the animator's details. This design has caused the system to be hard to work with to do other things.

    Problem 1: when messages gets fired.
    StateMachineBehaviour.OnStateEnter gets fired when a state starts playing.
    StateMachineBehaviour.OnStateExit gets fired when a state has stopped playing.
    StateMachineBehaviour.OnStateUpdate gets fired while a state is playing.

    This means that when the animator transitions from state A to state B, the order of messages are:

    A.OnStateUpdate
    -- transition starts --
    B.OnStateEnter
    A.OnStateUpdate, B.OnStateUpdate (during the transition)
    -- transition ends --
    A.OnStateExit
    B.OnStateUpdate

    This means that using OnStateEnter/Exit to set state will give unexpected results. As an example, say there's a bunch of animations that should effect the speed of the player. It seems like this script should handle that:

    Code (csharp):
    1. public class ModifySpeedBehaviour : StateMachineBehaviour {
    2.  
    3.     public float speedMultiplier;
    4.     private PlayerController playerContoller;
    5.  
    6.     public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    7.         playerContoller = playerContoller != null ? playerContoller : animator.GetComponent<PlayerController>();
    8.         //set the correct speed for this state
    9.         playerContoller.SetSpeedMultiplier(speedMultiplier);
    10.     }
    11.  
    12. public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    13.         //reset the speed
    14.         playerContoller.SetSpeedMultiplier(1f);
    15.     }
    16. }
    And it does, until you get a transition between two states with this script on them. Say you transition directly from Run to Crawl, with a non-zero transition time. Run has ModifySpeedBehaviour with the speedMultiplier 1.25, while Crawl has the multiplier 0.75. Since Run.OnStateExit happens after Crawl.OnStateEnter, the player controller will be crawling around with a speed of 1!

    This is a very simple example, but this problem shows up a lot. OnStateEnter/Exit feels like they should be a SetUp/CleanUp pair, but they're not.

    One solution is to make the methods always come as pairs. Guarantee that every A.OnStateEnter is followed by an A.OnStateExit before any other OnStateEnter gets called. This would also imply that A.OnStateUpdate would only get called between A.OnStateUpdate and A.OnStateExit. This would turn the system into a reliable state machine.

    The natural frame at which A.OnStateExit and B.OnStateEnter should be called is the frame where the animator stops returning true for GetCurrentAnimatorStateInfo.IsName("A");

    An alternative is to make a different method set or a different class which works in this way. Finally, we could be able to set both of those events in the transition preview, though that would require some sensible default values, and the ability to set that default.


    Problem 2: What messages gets called

    Aka. OnStateMachineEnter/Exit are crazy.

    As you know, they only get called when you're using the entry and exit nodes. This means that if you use them, you have to only transition using those nodes. This has several problems:

    - Nobody expects that! These methods are horrendously named. If you insist on this functionality (which you shouldn't, it's bad), at least rename the methods to "OnEntryNodeEntered" and "OnExitNodeEntered". I know it's (finally) in the docs, but people won't read those.

    - It dissallows using the Any State node. If you want to get a message every time you leave a substatemachine, using something like a "damage" trigger to transition to a damage animation from any state can't be done anymore. Now every state needs to have a dedicated damage transition. Or, you have to live with not getting the OnStateMachineExit call when you exited the state machine.

    - It requires using the enter/exit node! I never want to use the exit node. Generally, I know which state I should transition to, and using the exit node requires me to duplicate the transition conditions two places – on for the state into the exit node, and one for the transition from the state machine into the target state.

    The idea of having a self-contained sub state machine, where you only ask the system to exit, and then let the containing machine figure out the rest is good, but I haven't yet run into a situation where that's acually viable. I need to use direct transitions, and then OnStateMachineExit won't get called.

    I don't get this design! Knowing when a state machine is entered or exited is important information. I cannot fathom a situation where I want to know if a substatemachine was entered, but only if it happened through the entry node! If somebody has made a behaviour that relies on that, I'll eat my shoe.


    Those are the main pieces of feedback. I'd like to hear back about these things – especially the StateMachine stuff. I've mentioned it several times, and I can't really remember getting anything back on why this works like it does.


    Two more things:
    - I've been talking with the editor team about some feedback, and I sent them some general Unity feedback as well. There's some bits there about the animator (not relating to StateMachineBehaviours). If you're interested, I can send those things your way
    - I found a bug while creating some tests: If a substatemachine contains a machine with the same name, the Make Transition -> StateMachine menu only shows one instance of that name, and creating the transition creates one to the inner state. This means that if SubStateMachine A contains another SubStateMachine A, you can only make a transition to A/A, making a transition directly to A is impossible.
     
    mr_blahblah likes this.
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    @Mecanim-Dev?

    You're probably busy, but I figured I'd remind you, since you asked for comments. A talk is generally two-way, and I'd really like to hear your input on what you think and why the design is like it is.
     
  5. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    @Baste

    For your Problem #1:

    I'm not sure how we could deal with this one, OnStateEnter and OnStateExit are callled while the state is still been evaluated and GetCurrentAnimatorStateInfo.IsName("A"); will always return true until OnStateExit is been called.(just tested to be sure)

    In this case you must be aware that Mecanim doesn't use the state machine as a logical Statemachine but as an animation state machine which can have up to two state running concurently. If we stop calling Update while two state run concurently there is a lot of thing that gonna break for existing project.

    You're user case is super interesting but I'm wondering if you are not using right tool to do the job.
    In this particular case it like if you would like to blend the speed multiplier for both state, Have you try to animate the speed parameter with a curve? mecanim will take care to blend the value for you between both state.
    You can either create a curve directly on any animator parameter or in your case animate the float value in your script for playerContoller.SetSpeedMultiplier(speedMultiplier);
    blendcurve.png


    Problem #2:
    yes I agree the name OnStateMachineEnter/Exit is really confusing, it should have been called OnEntryNodeEntered/OnExitNodeEntered. I think it should be renamed but we need to check this out with the scripting team first because we must keep backward compatibility for existing project.

    There is a small twist with these two callback, as soon as you define them the state machine evaluation cannot be multithreaded anymore because they are called in sync with the statemachine evaluation to allow user to change parameter to choose which state to transition too. This was the original design for this callback.

    I agree, but thoses callback are not design for this, the name is only confusing.
    But did you know that you can put Statemachine behaviour(SMB) on a statemachine, all state inside this state machine will inherit from this SMB so you can get a OnStateEnter and OnStateExit for all state?
    Simply select the background in the animator window and it shoudl select the statemachine, then click on Add Behaviour in the inspector.


    Yes send me this feedback. as for the bug if it already logged it will make his way to us.
     
  6. mr_blahblah

    mr_blahblah

    Joined:
    Jan 15, 2016
    Posts:
    38
    @Mecanim-Dev I wouldn't recommend using OnStateEnter and OnStateExit - they're not reliable. They don't always fire, thus it's unusable in it's present state without writing brute-force workarounds.

    Wow - just wow. This is just broken, man. I've lost days of productivity trying to work around it - I urge whoever is responsible for this system to fix it.
     
    yektasarioglu likes this.
  7. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    Yes we are aware that they're not reliable hence why I did the post, I more interested in how and when it does happen.

    In which case you are expecting them to fire but it didn't happen?
    We know that interrupted transition does'nt fire OnStateExit, but maybe there is other case?
     
  8. lunoland

    lunoland

    Joined:
    Aug 4, 2016
    Posts:
    12
    Hey @Mecanim-Dev! Thanks for all your hard work.

    I just found this thread while looking for reasons why OnStateEnter might not fire. I'm using 5.3.4 and I believe I have found just such a case/bug where OnStateEnter doesn't fire, but I'm have trouble reproducing it. It doesn't happen every time, but when it does happen it's always the very first time an AnimatorController enters the state associated with the StateMachineBehavior. Sometimes OnStateEnter doesn't fire that very first time, but after that it seems to work as expected.

    I first noticed it when I would periodically get a null error because my code in OnStateExit was relying on values that were supposed to be initialized in OnStateEnter. I found a similar thread (http://gamedev.stackexchange.com/qu...s-called-before-statemachinebehaviour-onstate) that seems to hint at the fact that maybe OnStateEnter is not always called first?

    If this seems like something you could investigate, let me know what information I can provide from my project/where to file the bug report. My StateMachineBehavior code is very simple, I'm just setting a flag indicating the state is in progress in OnStateEnter, and then clearing it in OnStateExit. It seems like you should always be able to count on some state's OnStateEnter always being called before its OnStateExit, but maybe I'm misunderstanding something?
     
  9. lunoland

    lunoland

    Joined:
    Aug 4, 2016
    Posts:
    12
    Addendum: It's possible this may be due to a feature I added to create some stop frames when a character gets hit. I disable the animator for some seconds, and then re-enable it. It seems to work, the animator is paused when disabled and always resumes from the correction position of whatever state it was in when I re-enable it, but maybe this is affecting the StateMachineBehavior?
     
  10. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    That shouldn't affect the SMB. While the animator is disabled everything is paused.

    If you can get a repro step you can log a bug report from unity menu/Help/Report a bug ...

    Yes of course, the current discussion was more about what should happen while we are transitionning(blending) two state togheter.

    Let's take this simple example, Run transitionning to Jump.

    smb.png

    I did add a black line to show when callback should occur for the Run state and a Red line for the Jump state
    Right now what Mecanim does is
    mecanim@smb.png

    @Baste is suggesting that OnStateEnter and OnStateExit should never overlap.

    baste@smb.png

    My issue with this is as soon as you enter another state you need to call OnStateExit on the previous state first and stop calling OnStateUpdate on the run state afterward but the state is still been evaluated.

    One thing that we are going to add is OnTransitionEnter/OnTransitionExit callback but I'm still not sure it's going to solve this issue.

    Maybe we could use the concept or current state and next state and add a callback when we switch next to current
    OnBeginCurrentState
    OnEndCurrentState
    OnBeginNextState
    OnEndNextState
    new@smb.png

    This way if you need a setup/clearup pair one can define only OnBeginCurrentState/OnEndCurrentState on all his SMB and everything should cascade correctly
     
  11. chenjingsuzhou

    chenjingsuzhou

    Joined:
    Nov 24, 2014
    Posts:
    7
    Meet the same problem in 5.4
    I dont seen this in unity5.5 release notes
    Has OnStateExit fixed yet, thanks?
     
    Last edited: Dec 8, 2016
  12. Mecanim-Dev

    Mecanim-Dev

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    unfortunatelly this bug didn't make it for 5.5, it was fixed in 5.6.0b1