When we started designing the architecture of our animation graphs, we, of course, looked at other analogs, in particular at Unity Animator. However, we wanted to make a more universal solution. Unlike the same Unity, we have customization of animation states through the controller interface. But first, it's worth figuring out what an animated state graph is. If you have already encountered this, it makes sense to skip the introductory part and move on to the implementation features.
So what exactly is this animated state graph?
An animation state graph allows you to graphically represent transitions between different animation states.
Take, for example, character animation:
We have a three-dimensional model of a man and there are several animations of him:
- idle - stands still;
- walk - goes forward;
- sitting
- hello - waves a hand.
The classic approach to managing animations is as follows: if you want the object to stand - turn on idle, walk - walk, sit - sitting. But there are certain difficulties with this.
First, you need to manually control the duration and sequence of animations. For example, for a person to sit down, you first need to play the animation as he sits down, and then start playing a looped animation where the person is already sitting. Customizing the joints of these animations in the code is difficult and inconvenient.
Secondly, the joints between animations become noticeable if the ends of the animation do not match, or we need to include another animation in the middle of the current one. In this case, it is simply impossible to match the animations perfectly. Remember the old games where character animations switched instantly.
The animation graph is designed to address these issues. With it you do not need to operate animations manually, now you operate with states. How an object will be animated to achieve this state is the work of animators and designers. Now the programmer does not think about the timings and sequence of the animation, he simply indicates what state the object should go into.
Also, with the animation graph, the problem of joining animations disappears. In the transition between states, we can make a smooth transition of one animation to another. This is done using weights. Weight is a mixing factor from 0 to 1, where 0 means that the animation does not affect the object in any way, and 1 completely affects it.
For example, the transition between walking (idle) and standing (idle) is very demanding on setting up the process. At any point in the walking animation, the character can stop. Therefore, the transition is not carried out instantly, but for some small period of time. At this time, the weight of the walk decreases from 1 to 0, and the standing weight increases from 0 to 1. It is important that the sum of the weights is equal to one, otherwise artifacts may appear.
How does it all work?
A graph consists of states and transitions. A state is a set of animation controllers, each of which can play some kind of animation on an object or execute some kind of logic. The controller has entry and exit points - these are the moments when the graph turns on the state with this controller and turns it off accordingly. The controller also has an update function, where, in addition to the time interval from the last frame, the weight of the transition comes. To mix animations, it must be taken into account.
Controllers have a single interface. Additionally, developers can add their controllers. For example, you can make a controller that runs some kind of logic or sets text on a popup, etc. This simple customization allows you to use the animation graph very flexibly.
We also have variables. These variables can be set externally, including from code, and then read them in controllers. So, for example, you can switch some kind of animation for a character on the same state. In general, you can even repeat the paradigm of transition between states through variables and conditions, like Unity. In conjunction with customizable controllers it turns out quite conveniently.
Transitions can be any number. Many transitions can come into state and exit in the same way unlimitedly. Transitions determine the possibility of reaching states. For example, if there is no transition directly between states A and F, but there is a chain A → B → C → D → E → F, then when you request a transition from A to F, the graph itself will understand that it needs to go through intermediate states B, C, D , and E.
Transitions have start interval settings and durations. With the duration, everything is simple - this is the time for which the transition will be made. But the interval is already more complicated: it determines the acceptable period of time for the animation when the transition can be started.
For example, in order for a character to sit down, you first need to play the animation as he sits down, and then start the seating animation. In this case, the transition interval from “sits down” to “sits” should be “sits down” at the end of the animation so that we can see how it sits down, and then at the end quickly but smoothly go into the animation of the seat.
Another example: a character is walking and he needs to stop. In this case, the transition start interval should be the entire length of the animation, because the character can stop at any time.
The animation graph does all the related work:
- plans a way to the necessary state;
- Updates the currently running states
- makes a smooth transition between states;
- regulates the weights in them.
Interesting features
There are many different types of animations in the Playrix engine: 3D models, Spine, Flash, particle effects, skeletal animation. Each type has a specific controller.
In addition to simple animation controllers, we have several auxiliary ones. For example, a randomized controller. It may include a list of other controllers and the likelihood of their choice. Each time an object enters a state with such a randomized controller, random selection takes into account the probabilities, and the selected controller begins to function. The rest are asleep and inactive, waiting for their moment.
But sometimes on one state we need to switch animations. For example, if several characters have the same graph, and all have some kind of action animation. One character needs to get his broom and start his revenge, another to get a camera and start taking pictures, the third is eating ice cream. For such situations, there is a special controller, which also contains a list of controllers, but, unlike randomized ones, here it selects a controller depending on the variable.
Variables are set in the graph and can be changed externally, for example from code. In this example, a string type is used, and each type of action corresponds to a certain value of the variable. When a character is created in the game, then this variable is set for him, depending on the desired behavior.
We also have a controller that can mix multiple animations. For example, you can mix walking animations left, right, and forward. Thus, when cornering, you can adjust the weights between them so that the character’s legs do not slip and walking looks natural.
We need to go deeper
There are a lot of advantages to the fact that we make our Unity. One of them is that we can do as we want and what we want. And we wanted an unlimited opportunity to expand the animation graph.
We have a controller interface, there are several controllers “out of the box” and there is the ability to implement the interface and do anything in it (and not necessarily an animation):
- change the text on the button;
- interact with other objects in the hierarchy;
- and even manage another animation graph.
We used this approach in visitors to the zoo in the game Wildscapes. Each visitor has two graphs: one for animating the model, the other for animating the behavior.
The first graph is quite simple, it controls walking, can play some separate character animations.
The second graph is much more complicated and has some behavior scenarios. For example, at first the character goes, then sits on a bench, greets someone, takes pictures and goes on. This is a separate state branch.
This logic could be placed on the first column, but then the animations would be duplicated many times. But with two graphs, everything is much simpler. The control graph contains chains of states, including states from the first graph, which runs in parallel.
What's next?
Our graph already knows a lot, but there is still much room for development. The plans make a grouping of several states, with nesting. This will greatly simplify quest columns. The plans also include work to improve the display of graphs and links. Now the connections on large graphs resemble spaghetti (even the color is similar), and sometimes it’s easy to get confused.