Animation State Machine Transitions Implementation

Started by
13 comments, last by Dirk Gregorius 7 years, 3 months ago

I think the only problem I have is to use two state machines.

"have to" is a bit strong :)

There is a computation known as "synchronous product of automata" that can merge several synchronous automata into one. The caveat is that the resulting automaton is hard to understand, not easily human-editable anymore, and may contain lots of states (if you merge a automaton with N states with one with M states, the product may contain up to N*M states, ie you may get a state explosion). You want tool-support to generate such a product. All this makes the product less useful for an article, which is why I didn't mention it.

For the example, where the Idle/Walk automaton has just 2 states, and the synchronizing events cause state changes in only one automaton, it's quite doable by hand, as shown below:

[attachment=34368:walk_product.png]

You make a copy of the entire animation state machine for each state in the idle/walk state machine. In the picture, the upper dotted box represents the combination "WantIdle&<animation-state>", the lower dotted box represents the combination "WantWalk&<animation-state>".

Next you copy the edges from the Idle/Walk state machine to every corresponding pairs of states between the round boxes, the thick cyan and magenta arrows are the "desired_speed == 0" and the "desired_speed > 0" edges.

Finally, the "WantIdle" state forbids the WantWalk edges, so remove them from the upper box. Similarly, remove the WantIdle edges from the lower box. I also removed the Init edge from the bottom box, assuming you want to start in idle state.

All the 8 states are reachable, so you can't remove any of them. In this way you can have a single state machine with combined behavior, if you don't mind having many states.

I now describe the entire animation in a blend tree.

I didn't know these, so I had a quick look. After reading the general description, it looks to me more like a way to re-use parts of an animation, but I may be under-estimating its power.

It is probably a matter of choice and both work fine.

I think so too. How you represent state in run-time is not very relevant, a variable with a value is as much state as a pointer to a "current state" object.

Advertisement

What I do in EMotion FX is have each state output a pose.

A state can be a simple motion, or state machine or blend tree (or anything else that generates a pose).

Between the states are transitions with the conditions on them, so exactly as you describe.

The state machine holds a current state and target state and current transition.

When a transition is active, the transition pointer points to the transition being active.

The transition object outputs a pose as well and can mark itself finished.

I then output the pose of the transition. But the state machine is still in the current source state as current state.

Once the transition is fully completed the state machine's current state becomes the transition's target state.

There is a separate Update and Output call in the graph. The Update call would update the node and transition timers. So performing a tick and performs the actual current state changes once transitions finish etc.

The Output will calculate the output pose of the root state machine, so processing everything hierarchically.

You only update and output the current and possible target state inside the state machines.

Hope that helps.

Hi John! Awesome, thanks for sharing this information. This is a bit more in the line I am thinking about this system. Please let me ask you two more quick questions:

1) Can you elaborate a bit more on the Update() function. Does a state update itself? E.g. do you simply iterate over all transitions of the state update checking the associated conditions? Then out of all possible transitions you choose the one with the highest priority? Or does the transition happen from the outside and is triggered by some controlling game code.

2) Does the active transition calculate and cache the current pose for the Output() call when updating? Or does all evaluation happen exclusively in Output()? Also why does the current state holds the target state and the transition. Is this only rhetorical? I would imagine this to be redundant since the transition would store the target as well. Or maybe is there a case where this is necessary and the two targets can be different?

Thanks,

-Dirk

Hi Dirk :)

1) Can you elaborate a bit more on the Update() function. Does a state update itself? E.g. do you simply iterate over all transitions of the state update checking the associated conditions? Then out of all possible transitions you choose the one with the highest priority? Or does the transition happen from the outside and is triggered by some controlling game code.

The update of states (which are essentially emfx graph nodes) update things internally depending on the node. For example a motion node could update its current play timer. It is a bit more complex than this though, with hierarchical synchronization etc. But that is essentially what it does. It outputs non-pose values as well inside the updates. For example math nodes require their values to be calculated already during update stage, as we optimize the graph output by not processing nodes that aren't needed. For example if the weight of some IK node is 0 (calculated by some math nodes), then we do not calculate the full IK on the pose.
The Output would really do the heavy per bone calculations, outputting a real pose that gets blend etc.
Regarding conditions being checked, these are only the ones on the transitions originating from the current state. And also wild card transitions. If multiple transitions can be taken, we pick the one with the highest priority value. We also allow transitions to be interrupted and some other features.
We can also trigger them manually. But that's mostly just used in the editor. Or to force the game in some given state. But normally all is automated.

2) Does the active transition calculate and cache the current pose for the Output() call when updating? Or does all evaluation happen exclusively in Output()? Also why does the current state holds the target state and the transition. Is this only rhetorical? I would imagine this to be redundant since the transition would store the target as well. Or maybe is there a case where this is necessary and the two targets can be different?

The evaluation happens inside the Output. Update only updates things like timers etc, and what transition is active. There is no caching of that pose. I pool all poses though, so transitions and nodes do not store poses as that eats more and more memory the more nodes you have. Also I can share the animation graphs among different instances, each having their own parameter values and internal states of the nodes. For example each instance can be in another current state or transition in a given state machine.

The active transition pointer can be null. In that case you still want to know the current state. It is probably why it is stored twice in EMFX. It has been like 10 years ago since it got written, so I cannot remember the exact details right now. But surely it is not perfect, so perhaps not all would be needed :)

Cheers,

- John

Supi, that is exactly what I was looking for. Thanks a lot, sir! :)

This topic is closed to new replies.

Advertisement