Wait, I’ve changed my mind! State Machine Transition interruptions
I recently investigated a tricky bug reported by a user involving the combination of empty states, override layers, and transition interruptions. As I was digging in, I found that our documentation about transition interruptions in the animation system was a bit… minimalist. A long conversation with my team later, we concluded that a blog post was in order.
So let’s dive into some intricate details of State Machine Transitions and interruptions!
By default in the animation system, transitions cannot be interrupted: once you start going from one state to the other, there’s no way out. Like a passenger on a transatlantic flight, you’re cozily nestled in your seat until you reach your destination and you can’t change your mind. For most users, this is fine.
But if you need more control over transitions, Mecanim can be configured in a variety of ways to meet your needs. If you’re unhappy with your current destination, you can hop in the pilot’s seat and can change plans midway through your flight. This means more responsive animations, but also many opportunities to get lost in the complexity.
So let’s walk through a few examples to sort that out. We can begin with a fairly simple state machine with four states, labeled A to D, and triggers hooked to every transition on the state machine.
By default, when we trigger the A->B transition, our state machine transitions towards B and nothing can keep it from reaching its destination. But if we go on the A->B transition inspector and change the interruption source from “None” to “Current State”, our journey from A to B can be interrupted by some triggers on state A.
Why only “some”? Because the “Ordered Interruption” checkbox is also checked by default. This means only transitions on state A that have a higher priority than the current one are allowed. Looking at the inspector of state A, we can see that this only applies to the A->C transition.
So if we activate the A->B trigger, then shortly after the A->D trigger, our transition remains uninterrupted. However, if we press the A->C trigger instead, then the transition is immediately interrupted and the state machine starts transitioning towards C.
Internally, the animation system records the pose at the time of the interruption, and will now blend between that static pose (X) and the new destination animation. Why a static pose, instead of a possibly smoother blend between the current and new transitions? Simply put: performance. When a game faces a cascade of interruptions, keeping track of several dynamic transitions taking place simultaneously would quickly made the animation system unscalable.
Now, if we uncheck that “Ordered Interruption” checkbox, then both A->C and A->D can interrupt the transition. However, if they are both triggered on the same frame, A->C will still take precedence because it has a higher priority.
If we change the interruption source to “Next State”, A->C and A->D can no longer interrupt the transition, regardless of their order. However, if we press the B->D trigger, we will immediately start transitioning from A to D, without completing the transition towards B.
Transition order matters on state B too. The “Ordered Interruption” checkbox is not available anymore (any triggered transition on B can interrupt the transition because they do not have a priority ranking relative to A->B), but the order of the transitions on B will determine which transition wins if both are triggered within the same frame. In this case, if B->D and B->C are triggered in the same frame, B->D will be selected.
Finally, for complete control, we can set the Interruption Source to “Current State Then Next State”, or “Next State Then Current State”. In that case, the transitions will be analyzed independently on one state, then the other.
So, let’s assume we have the following configuration.
During the A->B transition, a very excited player triggers four transitions within the same frame: A->C, A->D, B->C and B->D. What happens?
First, “Ordered Interruption” is checked, so we can ignore A->D right away: it has lower priority than A->B. The current state gets resolved first, so we do not even have to look at state B to know that transition A->C wins.
However, with the same configuration, if only B->C and B->D get triggered, transition B->D will take place (it has greater precedence than B->C).
Now, this is only for one transition… All other transitions can also be interruptible too, with their own specific rules. So if we make transition A->C interruptible from the next state, we can have transition A->B interrupted by A->C which in turn could be interrupted by C->D.
One important thing to keep in mind: regardless of interruptions taking place, the source state remains the same until the transition is complete, and Animator.GetCurrentAnimatorStateInfo() will always return that source state.
In short, transition interruptions settings are powerful and offer a lot of flexibility, but they can quickly become very confusing. So use transition interruptions wisely, and when in doubt, test it out in the Editor.