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:
public class ModifySpeedBehaviour : StateMachineBehaviour {
public float speedMultiplier;
private PlayerController playerContoller;
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
playerContoller = playerContoller != null ? playerContoller : animator.GetComponent<PlayerController>();
//set the correct speed for this state
playerContoller.SetSpeedMultiplier(speedMultiplier);
}
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
//reset the speed
playerContoller.SetSpeedMultiplier(1f);
}
}
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.