Location: Assets/Plugins/Animancer/Examples/01 Basics/05 Basic Character
Recommended After: Basic Movement and Transitions
Learning Outcomes: in this example you will learn:
How to combine simple behaviours into more complex ones.
This example combines the Basic Movement and Transitions examples on a single character to demonstate how you can avoid several common problems you might encounter when implementing more complex behaviours.
Summary
- Implementing a character with multiple different animations is notably more complex than just the one or two seen in earlier examples because you need to consider how they can transition into and interrupt each other.
- Animancer plays animations when you tell it to and only when you tell it to, so if the wrong animation is playing then most likely your scripts are telling it to play the wrong animation.
- Enums are an effective way to keep track of the states things are in for relatively simple scenarios, but for more complex situations it's often better to use a proper Finite State Machine system.
- The Characters example re-implements the same logic as this example using Animancer's Finite State Machine system.
Overview
The code structure looks similar to the Transitions example, but now it has three animations:
Methods
This example has two methods we want to call which are first copied directly from the previous examples, but then will be modified to suit the needs of this example.
UpdateMovement
is a copy of the Update
method from the BasicMovementAnimations
script in the Basic Movement example.
private void UpdateMovement()
{
float forward = ExampleInput.WASD.y;
if (forward > 0)
{
_Animancer.Play(_Move);
}
else
{
_Animancer.Play(_Idle);
}
}
UpdateAction
is a copy of the Update
method from the PlayTransitionOnClick
script in the Transitions example.
private void UpdateAction()
{
if (ExampleInput.LeftMouseUp)
{
_Animancer.Play(_Action);
}
}
Logical States
Animancer doesn't impose any limits on when you can play any given animation, it simply plays whatever you tell it to. This means your scripts are responsible for determining how your animations can interrupt each other.
In this case, we have 3 animations and we want to implement the following rules:
- Idle can play anything.
- Walk can play anything.
- Action can only play Action (to allow Rapid Fire).
Since Idle and Walk have the same rules, we effectively want two logical states: "Acting" and "Not Acting". These logical states are entirely separate from the animation states that Animancer uses internally for each animation (AnimancerState
s). A logical state can have any number of animation states associated with it:
- Acting has one animation (Action).
- Not Acting has two animations (Idle and Walk).
- A logical state could even have no associated animations. For example, an effect that freezes the character could simply call
_Animancer.Playable.PauseGraph();
to pause the character in their current pose instead of playing a new animation.
State Enum
One of the simplest ways to implement logical states is to use an Enumeration Type (commonly called an enum
). An enum
needs a name like any other Type in C# so since the class is called BasicCharacterAnimations
we could call the enum
BasicCharacterAnimationState
. But that's rather long and would be annoying to repeat all over the place so instead we can make use of the C# feature Nested Types to declare the enum
inside the class
, which means that it can simply be called State
.
public sealed class BasicCharacterAnimations : MonoBehaviour
{
private enum State
{
NotActing,
Acting,
}
If we made it a public enum
then other scripts would be able to use it via BasicCharacterAnimations.State
, but since this one script is the only one it will be used in we just make it private
.
With the enum
declared, we also need a field to store the current State
that the class is in.
private State _CurrentState;
Method Changes
UpdateMovement
and UpdateAction
are still basically the same as in the Methods section above, but now we want them to set the _CurrentState
.
UpdateMovement
always sets the state to State.NotActing
because it always plays one animation or the other.
private void UpdateMovement()
{
_CurrentState = State.NotActing;
float forward = ExampleInput.WASD.y;
if (forward > 0)
{
_Animancer.Play(_Move);
}
else
{
_Animancer.Play(_Idle);
}
}
And UpdateAction
only sets State.Acting
when the mouse is clicked.
private void UpdateAction()
{
if (ExampleInput.LeftMouseUp)
{
_CurrentState = State.Acting;
_Animancer.Play(_Action);
}
}
Initialization
The startup code needs to change a bit now.
PlayTransitionOnClick (Old) |
BasicCharacterAnimations (New) |
---|---|
Previously, the
|
But now, we need to set the
|
Update
The last thing we need to do is write the Update
method to call UpdateMovement
and/or UpdateAction
depending on the _CurrentState
.
private void Update()
{
if (_CurrentState == State.NotActing)
{
UpdateMovement();
UpdateAction();
}
else
{
UpdateAction();
}
}
That would do what we want, but if we had a few more states to check it would become a repetitive series of If Statements which is generally considered a bad practice because Switch Statements can do the same job more cleanly.
private void Update()
{
switch (_CurrentState)
{
case State.NotActing:
UpdateMovement();
UpdateAction();
break;
case State.Acting:
UpdateAction();
break;
}
}
Ordering
One last thing to note is that the order in which State.NotActing
calls those methods is important.
If you were to put UpdateAction
before UpdateMovement
like this:
case State.NotActing:
UpdateAction();
UpdateMovement();
break;
When the user Left Clicks, the following would happen:
- Unity calls
Update
. Update
reachescase State.NotActing
.Update
callsUpdateAction
.UpdateAction
checksExampleInput.LeftMouseUp
which istrue
so it plays the_Action
animation and setsState.Acting
.Update
is still running and still insidecase State.NotActing
so now it callsUpdateMovement
.UpdateMovement
plays either_Move
or_Idle
and setsState.NotActing
.
The problem here is that even though State.Acting
was set properly in step #4 (along with its animation), it gets immediately set back to State.NotActing
in step #5 so you will never actually get to see the character perform the action. That is why we call UpdateMovement
before UpdateAction
.
Conclusion
Here's what the full script looks like.
using Animancer;
using UnityEngine;
public sealed class BasicCharacterAnimations : MonoBehaviour
{
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private ClipTransition _Idle;
[SerializeField] private ClipTransition _Move;
[SerializeField] private ClipTransition _Action;
private enum State
{
NotActing,
Acting,
}
private State _CurrentState;
private void Awake()
{
_Action.Events.OnEnd = OnActionEnd;
}
private void OnActionEnd()
{
_CurrentState = State.NotActing;
UpdateMovement();
}
private void Update()
{
switch (_CurrentState)
{
case State.NotActing:
UpdateMovement();
UpdateAction();
break;
case State.Acting:
UpdateAction();
break;
}
}
private void UpdateMovement()
{
_CurrentState = State.NotActing;
float forward = ExampleInput.WASD.y;
if (forward > 0)
{
_Animancer.Play(_Move);
}
else
{
_Animancer.Play(_Idle);
}
}
private void UpdateAction()
{
if (ExampleInput.LeftMouseUp)
{
_CurrentState = State.Acting;
_Animancer.Play(_Action);
}
}
}
If you enter Play Mode, you have a character that can:
- Stand still by default.
- Walk when you hold
W
. - Shoot as fast as you can Left Click.
What Next?
That's the last of the Basics examples. Hopefully, by now you have some idea of how you could start using Animancer in your own projects, but the Help page lists various options for contacting the developer if you have any questions or suggestions. There are also many more examples covering the more advanced features of Animancer and ways you can use it. In particular, there are a few that relate to the topics covered in this example:
Example | Topic |
---|---|
Characters | Re-implement the same logic as this example using Animancer's Finite State Machine system. Relatively simple behaviours can be easily implemented in a single script like we did in this example. But in more complex situations, spending some extra time early on to properly structure your code will often save a lot of time later on when you need to expand it, change it, and debug issues. |
Locomotion | More detail about movement animations and actually moving characters around the scene. |
Events | More detail about End Events and other events you can use to run code at specific times during an animation. |
Layers | Playing multiple separate animations simultaneously on different body parts, such as to allow the character to walk and shoot at the same time even though we don't have a specific "Walking And Shooting" animation. |