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 trying to debug a problem or reading some code you wrote a while ago or someone else's code. Consider the following questions:
# | Question | Answer |
---|---|---|
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 | 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. |
5 | Is this script being used for multiple different Animator Controllers? | Better go check. |
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. And if the answer to that last question turns out to be yes, then lucky you, now you get to re-check all those questions for each Animator Controller the script is used with.
Here's what that code could look like with Animancer:
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private AnimationClip _Attack;
public void PlayAttack()
{
AnimancerState state = _Animancer.Play(_Attack);
state.Events(this).OnEnd ??= OnAttackEnd;
}
This time we have slightly more complexity in the code, but less overall complexity because the logic is all in one place and does not require an Animator Controller to be set up separately. So what does that do to those questions from before?
# | Question | Answer |
---|---|---|
1 | Has a state with that name actually been set up in the Animator Controller? | 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. If you make a spelling mistake it will cause a compile error which points to the problem so you can fix it immediately. |
2 | What other animations are there to choose from? | 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 let you go to them with a single keypress if necessary (F12 in Visual Studio). |
3 | What will happen when that animation finishes? | It will call the OnAttackEnd method. |
4 | 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" ? |
Nothing, because your IDE can rename all references to that field automatically. Though you could use a [FormerlySerializedAs] attribute to ensure that any serialized references previously assigned to the old field name will be kept by the new one. |
5 | Is this script being used for multiple different Animator Controllers? | Irrelevant. This script defines its own behaviour instead of needing to coordinate with a separate system. |