There's a programming concept known as Separation of Concerns which encourages you to separate your code into distinct sections which each cover different concerns (things the program needs to do). For example, if your game contains a player and enemies which can move around similarly but are controlled differently, instead of simply having one script for "everything the player does" and another for "everything an enemy does", you could separate the functionality for "moving a character" from the logic of "deciding where a character wants to move". This would allow you to modify the way characters move and have that change apply consistently to all characters or you could add a new type of enemy that uses different logic for deciding where to move without needing to implement the same movement code again.
In theory, Mecanim attempts to utilise this concept by separating the structure of states, transitions, and parameters in an Animator Controller from the rest of the game logic in scripts. But in practice, it is separating the wrong things. Those concerns often need to be coupled far too tightly for that forced separation to provide any benefit so you just end up wasting time coordinating between things that should not be separate in the first place.
In addition to forcibly separating your concerns in the wrong way, it also prevents you from separating them in useful ways. Imagine if you could split a large Animator Controller into several smaller ones with more specific responsibilities. Well, you can actually do that with Animancer since it can play Animator Controllers just like any other state.
Example
This state diagram taken straight from the Unity Manual actually illustrates one of the problems with Mecanim quite effectively. To transition from the Walk state to the Fall state, a transition must be set up between those states so that a script can detect when the character is no longer on the ground and trigger it. 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. The code is useless without the specific Animator Controller structure it expects and the Animator Controller is useless without code that specifically knows how to control it. Both sides are very tightly coupled to each other, but Mecanim forces them to be separate so you have to waste time getting them to work together and ensuring that changes to one do not break the other.
That problem would never occur with Animancer. You still need to detect when the character leaves and returns to the ground, but when you tell it to play an animation it will simply play that animation. The script wants something done, so it tells Animancer exactly what to do.
Parameters
Another aspect of this problem is the way parameters work. In theory, they provide a useful abstraction between commands and actions. For example, to have a character perform a series of attacks as long as the player keeps pressing the attack button, you might create a trigger parameter which causes each transition to the next attack and have your script set that trigger every time the button is pressed. But in practice that creates a multitude of issues:
- Triggers stay active until a transition uses them, so if the player tries to attack while casting a spell or getting knocked over or performing some other action, they will wait until that action is done and then attack, possibly several seconds after they pressed the button (meaning the player does not necessarily still want to attack, so the controls would feel unresponsive).
- This is generally solved by having your script manually reset the trigger after setting it, which means you need to wait for at least one frame and thus greatly increases the complexity of what would otherwise be very simple code.
- You also need to make sure to reset the triggers for all other actions whenever you perform a new one. So when you attack, you need to reset the triggers for jump, dodge, block, interact, cast spell, dance, etc.
- Code almost always needs to know what state the character is in to determine things like whether they can move or whether other characters can talk to them. The script can't simply tell the Animator Controller to "Attack", it also needs to check if the current state is "Attack 1" or "Attack 2" or "Attack 3" or "Attack 4", up to however many attacks the character has. So the theoretical separation of the "Attack" command from its effects is now ruined because the script needs to explicitly know about all the states involved anyway.
- The user interface is missing many features that are standard in all code editing programs (IDEs like Visual Studio):
- You can't rename a state or parameter and have it automatically update everything using it.
- You can't view a list of all transitions that use a particular parameter.
- You can't view a list of all scripts that use a particular parameter.
- You can't select two transitions to view them side by side.
- You can't easily ensure that similar animations have similar state and transition settings (left/right or armed/unarmed pairs, up/down/left/right groups, etc).
- When you look at a script, it won't give a clear indication of which parameters or states it intends to use. You need to read the whole script to find out its requirements and then you still need to figure out which
AnimatorController
s it is actually used with.
- Scripts can't easily validate their requirements upfront, such as checking if a required parameter exists when you add the script to an 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 defence.
In Animancer, the same mechanic could be implemented using a simple array of animations and an index to remember the current one. All the details you need about a character's attacks are right there in your script so you can directly access anything you need. The 3D Game Kit sample contains a practical demonstration of this idea.