As our first game, we decided to aim big; a full-fledged third-person action-RPG, no less! However, unlike other bigger studios that create action-RPG, we are a small team. We have a grand total of 1 animator. Because of this, we have to waste as little time as possible while building our game; which means being as smart as we can be. Being smart also means working more up-front, so we can work less down the line.
Something specific to the type of game we are making - action-RPG - is that the characters, player and enemies alike, can equip a wide variety of items (swords, firearms, shields, spells, abilities, etc.), and each of those have different animations.
A good example is the Dark Souls series. It has a huge number of weapons. Can you imagine putting all the animations of all those weapon inside the same animator file? I sure would not like it.
You can do it the Recore's way, but you will end up with a massive unmanageable animator with a lot of different layers and you will most likely decide to drastically limit the number of items you can use. There's also the issue of memory management, as you honestly don't want all the animations of all the items loaded at all time, since an animator doesn't allow you to load and unload animation you believe are used or not.
The solution is to dynamically inject animations in as you need them. When a character equip a weapon, we load its animations and inject them in its animator. While Unity allows that, it is definitally not friendly to it. However, the advantages of injecting animations are far too great to do it in any other way. The end result is that the number of pieces equipment your characters can use is unlimited without forcing whoever is making the animators files into a deep depression. Creating a new weapon is also very straight forward and requires no modification in the animator!
The cornerstone of the system are the placeholder animations. Unity has a nice file called Animator Override Controller. In short, it allows to replace animations in an existing animator controller with something else. Doing that in the editor is fairly straight forward; just drag and drop animation in the override controller!
However, in our case, we want to do that at runtime. Simply, our character can hold a shield and a sword, and we simply don't know which until the player actually decide to equip them. We also have firearms and spells that also have their own animations. Obviously, we cannot create an override controller for every potential combinaison! There would be billions of them!
To be able to inject animation at runtime, you need to know which animation you want to replace. An override controller is a kind of dictionary of all the original animation as keys, and the override as values. Everywhere we knew we would inject animations, we created empty animation files, and used them in our animator.
Knowing the placeholders and the animation we want to inject, we simply do:
The second step is to store the real animations of your items somewhere. Since we had melee weapons that perform combos, we decided the best way to visualize the attack flow is with a node-graph.
It looks crazier then it actually is. The green nodes are the character entry-state, while each grey node is an attack. Each link is a potential combo from one attack to another. Many combos can loop on themselves, allowing the player to flow from one attack to another.
An attack is also seperated into three parts which is three different animations; an anticipation, the swing, and the recovery which returns the character to its idle stance. This allows us to reuse animations between attacks if the pose fit.
When a character equips a weapon, its related attack graph is loaded, and all the referenced animations as well. The graph is then charged to parse all the animations and inject them in the override controller at the right places. When the character attacks, it looks up where in the graph the combo is, and the index of the next attack. That index ends up being the position of the animation in a 1-dimensional blend tree.
The Animator Doesn't Like It
Not one bit actually. The first thing you will notice if you try to inject an animation at runtime, is that the animator reset itself. Not just at which frame it's currently playing, but everything; which state, which animation, which frame, and it reset all the parameters. Even worse, for a single frame the animator is flagged as uninitialized.
If your idea is to make an realtime action-RPG where the player can switch weapons while rolling away to avoid an attack, having it do a t-pose for a frame and reset back everything is not something you want to see. It should continue to roll while the new weapon attack animations are being injected.
The solution is to do the following steps:
Copy every properties from the animator (parameters, states, layers, frames)
Inject the animations
Force the animator to play a frame to initialize itself
Apply the copied properties
Force the animator to play the current frame
Sadly, there is no easy way to do this. For example, copying every properties of animator looks like this:
In short, it's not fun. You may also notice there is no way to know if a trigger is on or not. We just have to live with that. There's also the "animator.parameters" part that we do only once and cache, because it is a very expensive action to pull that data at runtime. However, since the list of parameters cannot change at runtime, we just need to do it once.
Another issue, is that if you attempt to pass a parameter to the animator while an injection is occuring, the animator will ignore it. To counter that, you should hold that parameter change until the next frame.
And another issue - I told you the Animator doesn't like it - is that every StateMachineBehaviour within the animator are also re-initialized. Meaning, they lose all their properties, or in our case, lose all their delegates registered in some events. To fix that, we had to create static dictionary holding the animator as key and the delegate as value. Being static, the dictionary is not re-initialized on an injection.
However, once all those issues are ironed out, injecting animation is something you can not live without.
The biggest advantage is how slim your animator will stays, once you see nodes as placeholder for injection. Our character can do a lot of things. In code, it has over 30 different states. And yet, our animator stayed very simple:
Injecting animations also works for many other situation, such as:
Quick-time events, like a boss grabs the player or an enemy backstab
Contextual idle breaks, like shivering because its cold
Contextual interactions, like a button, a switch, a lever, a secret passage
Contextual deaths, like drowning, being crushed, expulsed in vaccuum
There is dozen of other situations where injecting an animation is useful. We would simply be unable to make Hellpoint without it.
Setting up animations injection the first time is a painful process that lack in documentation. We sure would love if Unity had this pipeline a bit more straight forward. However, once you've done it properly a first time, adding more injection points is very easy and allows you to have a character that can use an unlimited number of animation, contextual to any number of condition you need.
You can try the pre-alpha demo of Hellpoint on Steam.