Why Replace Mecanim?

Unity’s Mecanim Animator Controller system was designed to be easily accessible to people without much programming experience. It achieves that goal quite well, but doing so creates the illusion that animations should define the way your game works, when in fact the opposite is true. Games are highly complex and dynamic so a naive brute force approach where every animation goes into one huge static state machine it not ideal for various reasons:

  • It forces you to rely on Magic Strings.
  • It results in Duplication of Effort where you define some parts of the behaviour of your characters in the Animator Controller and other parts in your scripts, which wastes time and makes debugging much harder because any bug could be in your script or the Animator Controller or a combination of the two.
  • It prevents you from managing Dependencies. Any script could play any state or set any parameter and you wouldn't know about it without reading the entire script. Any changes to an Animator Controller might break a script and vice-versa, but you don't get a clear indication of which scripts use which states.
  • It inhibits any attempts to compartmentalise or encapsulate individual areas into easily manageable or reusable groups. Instead, all Responsibilities are just thrown into a single Animator Controller.
  • It greatly limits your Runtime Control since everything needs to be defined beforehand in the Unity Editor.
  • The states don't even align with the background grid.

These factors make it difficult to achieve an effective development workflow. It's not impossible, just more difficult than necessary.

This monstrosity was taken from an official Unity Blog post ironically titled "Building for Reuse". The sheer number of states and parameters turns it from a meaningful visualisation of a complex system into a convoluted mess that would be extremely difficult to work with. Programming patterns and other techniques that could normally be used to effectively develop such a system simply cannot be used when everything is constrained by this user interface.

If you are interested in a practical comparison between the two systems, the Animator Controllers/3D Game Kit example is dedicated to the process of converting an Animator Controller based character to use Animancer instead.

Magic Strings

A "magic" variable is an arbitrary value that is used to activate certain functionality in a program without being derived or retrieved in some way, it simply appears from nothing as if by magic. Likewise, a "magic string" is a string that is used to control something that it has no real connection with. Take the following code for example:

[SerializeField]
private Animator _Animator;

public void PlayAttack()
{
    _Animator.Play("Attack");
}

In this case, "Attack" is a magic string. It's hard coded into the script without any explanation of what it is supposed to do or any validation to make sure it will actually work as intended. That line of code probably worked when it was written, but what about now? Maybe you're reading some code you wrote a while ago or someone else's code. Consider the following questions:

  1. Has a state with that name actually been set up in the Animator Controller? Are you sure it's not "attack" or "Attack01" or "Light Attack"? Better go check.
  2. What other animations are there to choose from? Better go check.
  3. What will happen when that animation finishes? Nothing? Maybe a transition? Maybe there's a parameter you need to set to determine which transition it will use? Better go check.
  4. How can you make sure everything is spelled correctly? Better go check.
  5. What else do you need to modify if you want to rename it to "Light Attack" because you are about to add a "Heavy Attack"? Better go check. The state in the Animator Controller of course, but what else? What if there's another script relying on a string that was entered in the Inspector so a Ctrl + F to find the text "Attack" in your scripts won't even find it?
  6. Is this script being used for multiple different Animator Controllers? Better go check. If it is then lucky you, now you get to re-check all those questions for each controller.

There's a clear pattern here: every time you want to verify an aspect of its behaviour, you have to figure out where that behaviour is actually defined and go check it manually.

So how could this be improved? Well, you've probably already guessed that the answer is "by using Animancer". With it, the code could look like this:

[SerializeField]
private AnimancerComponent _Animancer;

[SerializeField]
private AnimationClip _Attack;

public void PlayAttack()
{
    var state = _Animancer.Play(_Attack);
    state.OnEnd = OnAttackEnd;
}

This time we have slightly more complexity in the code, but less overall complexity because that code does not require an Animator Controller to be set up separately. So what does that do to those questions from before?

  1. Irrelevant. The code defines the animation it will play. It will automatically create a state for the _Attack animation the first time it is passed into the Play method.
  2. In this case there are no other animations to choose from because we only have one AnimationClip field. But if for example you set up a Weapon class with various fields for all of the animations it can have, then your IDE would be able to give you a list of those fields to choose from and you could probably jump to that class with a single keypress if necessary (F12 in Visual Studio).
  3. It will call the OnAttackEnd method.
  4. If you make a spelling mistake it will cause a compile error which points to the problem so you can fix it immediately.
  5. You don't need to modify anything else, your IDE can rename all references to that field automatically. Though you could use a [FormerlySerializedAs] attribute to ensure that any references previously assigned to the old field name will be kept by the new one.
  6. Irrelevant. This script defines its own behaviour instead of needing to fit in with the way Animator Controllers have been set up.

Duplication of Effort

This state diagram taken straight from the Unity Manual actually illustrates one of the problems with the system quite effectively. To transition from the Walk state to the Fall state, a script must detect that the character is no longer on the ground and a transition must be set up between those states. Then when the script detects that the character is back on the ground it can try to go back to Idle, except there is no transition from Fall to Idle so nothing will happen. Development time was originally spent creating the state machine and the code logic separately, and now even more time needs to be spent figuring out whether the problem is a bug in the ground detection code or in the state machine (or both) and then fixing it without breaking anything else.

That problem would never occur with Animancer. You still need to detect when the creature leaves and returns to the ground, but when you tell it to play an animation it will simply play that animation.

Dependencies

Mecanim has a detrimental effect on your dependencies, i.e. the additional things that a script needs in order to do its job. The fewer things you need to depend on, the better. Yet Mecamin forces additional dependencies on you when they aren't really needed.

Consider the following question in general terms:

What do you need in order to animate a character or other object?

The answer: you only require two things (in addition to the object itself):

  1. Something to determine what the animation does. This is what AnimationClips are for.
  2. Something to determine when the animation needs to be played. This is where Mecanim makes its mistake.

Mecanim uses an AnimatorController to determine when the animation is allowed based on its states, transitions, and parameters. But that on its own won't actually do anything useful, it still relies on scripts to determine when the animation needs to be played. This single requirement has been split into two parts which are very heavily dependant on each other, yet they are developed separately and have no way to specify or validate their dependencies upfront:

When a script sets a parameter, it becomes dependant on the AnimatorController having a parameter with that name (and type), however:

  • When you look at the parameter in the AnimatorController, it gives no indication of which scripts intend to use it, if any. It doesn't even let you enter a description of what it is used for (not that it would help much because scripts wouldn't be able to see the description anyway).
  • Likewise, when you look at the script, it doesn't give a clear indication of which parameters it intends to use either. You need to read the whole script to find out its requirements.
  • You can't easily check if the parameter exists when you add your script to the object, or even on startup. The only indication you get that it doesn't exist is when you run the game and try to actually set the parameter. This means that instead of getting a clear indication immediately when something has an issue, you have to test everything extensively. To be clear: extensive testing is good, but it should be your last line of defence against bugs, not your only line of defence.

So you have these two separate things - scripts and AnimatorControllers - which are inter-dependant, yet you have no proper way of visualising, validating, or enforcing those dependencies.

Animancer avoids these problems by not splitting up the requirement in the first place. A script determines when the animation needs to be played and simply tells it to play by passing the AnimationClip to the AnimancerComponent without the need to depend on anything extra outside of the script. No dependency on pre-defined states, transitions, or parameters. Looking at the script - in the Inspector or in code - can clearly show you which AnimationClips it wants and you can use code comments and [Tooltip] attributes to give more information.

Responsibilities

Programmers have a concept known as the "Single Responsibility Principle" which states that every class or function should have responsibility over a single part of the functionality of the overall application. This is extremely helpful for developing complex systems in a way that is easy to maintain as the application grows, yet Animator Controllers do not support that concept. Instead, these God Objects are responsible for all the animations a character can perform, regardless of which systems actually use those animations.

Animancer gives you the freedom to define individual aspects of a character wherever they make the most sense, which often means creating your own logical groupings and wrappers for them so that multiple characters can share the same structure.

These groupings can be defined using a different approach depending on the circumstances:

  • MonoBehaviours are attached to a specific GameObject in a scene or a prefab. This is useful if each group will generally be associated with one specific prefab or scene object so that you don't need a separate asset for each of them.
  • ScriptableObjects are generally saved as isolated assets inside the project. This is useful for sharing animation groups among multiple prefabs and scene objects.

Some examples:

  • Instead of each individual Attack and Skill script needing to acquire its own references to the appropriate animations for your current weapon, the Weapon itself could hold those animations. An array of attacks for a combo, alternatives for heavy attacks, a backstab, a custom idle or walk to hold the weapon correctly, and so on. Or rather than simply referencing an array of AnimationClips for the attacks, the weapon could have an array of AttackData which contains the clip, damage value, damage type, force multiplier, etc. If you do use an AttackData class to wrap other statistics with the animations, a MonoBehaviour based approach would allow each weapon to have different stats, even if they have the same set of animations as another.
  • Instead of scattering your animation references among each individual locomotion script (walk, climb, swim, etc.) you could group them together into a LocomotionData class which allows you to define movement speeds and other parameters alongside the animations while remaining entirely separate from their attacks and other actions. If you find it likely that you will want to reuse a particular set of animations many times with different stats, a ScriptableObject based approach could allow you to define LocomotionAnimations separately from LocomotionStats.

Using Controller States can also be an effective way of grouping certain animations together because they allow you to use multiple smaller Animator Controllers instead of a single large one.

Runtime Control

Animator Controllers need to be set up in the Unity Editor, then their structure is mostly fixed in place and either cannot be changed at runtime or requires some additional setup to make changes possible:

  • You can't manually control animations in the Inspector.
  • You can't add new states. You can only use Animator Override Controllers to replace existing animations, meaning you often need to create dummy AnimationClips and states with the sole purpose of being replaced.
  • You can't add, remove, or rename parameters.
  • You can't change transitions.
  • You can't control animation speeds unless you link them to a parameter.
  • You can't control animation time, only normalized time.
  • You can't directly control animation weights.
  • You can't fade layer weights without updating them yourself over time.
  • You can't move animations to a different layer.
  • You can't change layer masks.
  • You can't enable or disable Inverse Kinematics (for example, to improve performance).
  • You can't access any information about animations that aren't currently playing.
  • You can't access state names for debugging. You have to use hash codes.

All of those things can easily be done with Animancer.