In previous lessons we have created a world that is full of objects, with a character that can move around and enemies to avoid. But all that is very static so far. Let’s add animation to our characters.
Recommended Unity Versions
To play animation on a GameObject, Unity uses a component called an Animator.
Go into Prefab mode for the Robot Prefab (reminder: double click on the Prefab in the Project window or on the arrow on the right of your Robot in the Hierarchy window), click the Add Component button and find Animator.
The most important setting on the Animator component is the Controller setting.
A Controller is the asset responsible for choosing which animation to play based on rules we will define (like making the character go from a standing to a running animation when their speed is greater than 0). We will look at these rules later on. For now, let’s just create a new Controller and set it up on our Robot Animator.
There is a folder called Animations in your Project that contains pre-made animations to speed things up later on. Inside that folder, right click and select Create > Animator Controller, and call the controller Robot.
Now in the Animator you added on the Robot Prefab (make sure you’re in Prefab mode again if you exited it), assign your Controller in the Controller setting (either click on the little circle and find the Controller in the window that opens, or drag and drop the Controller from the Animation folder into the Controller setting).
Remember to save your changes to the Prefab with the save button on the top right corner of the Scene View.
Now that we have an Animator Controller, we need to create the animations that the controller will use. Like everything else in Unity, animations are Assets that you store in your Project folder. To create animations inside Unity, use the Animation window.
Open the Animation window by selecting Window > Animation > Animation. If you select the Robot in the Hierarchy or are in Prefab mode on the Robot Prefab, the Animation window will display this message: “To begin animating [Prefab/GameObject name], create an Animation Clip”. Click the Create button.
Now select the Animations folder as the place to save the animation clip and name it “RobotLeft.anim”.
The Animation window is split into two sections, with the properties that are animated on the left, and the timeline showing the keyframes for each animated property on the right:
We can animate any properties from any Component in our GameObject over time with the Animator. It could be the Sprite Color that we change over time, or the size. In our case, we want to change the Sprite used by the Sprite renderer.
By changing which Sprite the Sprite renderer uses over time, we can create the illusion of movement. You can find all the Sprites for our robot in the folder called Art > Sprites > Characters in a Sprite Atlas called MrClockworkSheet (also referred to as a sprite sheet). As you can see, there are multiple Sprites on a single image, just like the tileset we saw earlier. In this example, we have already split the image into different Sprites for you. If you click on the arrow next to the image, you will see all the Sprites:
Multi-select the 4 Sprites that form the left walking animation (by holding shift while you click on the 1st and last Sprite), and drag and drop them into the Animation window. This creates an animation using those 4 Sprites over time. Press the play button on the Animation window to preview the animation:
As you can see, the animation is going way too fast. That’s because our animation has a sample size of 60, which is set in the Samples setting in the Animation window, above the animation properties. Notice that the timeline has 60 vertical lines between the 0:00 and 1:00. This makes the animation run at 60 frames per second, which means Unity renders the Sprite 60 times each second. And you can also see that our 4 Sprites are on the first 4 entries in the line, which means that each only stays on the screen for 1/60 (so 0.016) seconds.
To fix that, simply set Samples to 4 to make the animation change only 4 times each second, so each of our Sprites stays on the screen for ¼ of a second. Your animation should now run at a proper speed. Feel free to play with that value to make it run at the speed you want.
That’s one animation done! We need to do 3 more to do the full set. To create an animation, click on the current Animation’s name in the top left of the window, select Create New Clip, enter a name and choose a folder:
You may wonder how we’re going to do the walking right animation, because there is no Sprite for the robot walking right. That’s because we can simply flip the walking left animation. The Sprite Renderer has a flip property, and remember that we can animate all properties.
So to start, recreate the Left animation by creating a new clip, dragging the 4 Sprites and setting Samples to 4.
Then click Add Property and click the triangle next to Sprite Renderer, and then click the + icon next to Flip X:
Now you can change the value of that for each frame. The current frame that you’re setting the value for is shown in the box next to the forward button on the top bar of the animation window, and is also represented in the left side with the vertical white bar.
Set it to true by checking the toggle next to the property name while on the frame 0 and frame 4, so the Flip stays checked for the entire animation:
Notice that diamonds have appeared on the same line as the Flip property for both the 0 and 4th frame. The diamonds mean there is a change in value at that frame. If there is no diamond, then that frame will use the last value that was set.
Now you can repeat the operation (create a new clip, drag the frames, set the sample to 4) for both the running up and running down animation.
Now we have all our walking animations for the robot, it’s time to set up the Controller we created earlier.
Building the Controller
The Controller defines how the animations are related, as in how to go from one animation to another.
To edit the Controller, open the Animator window (menu: Windows > Animation > Animator). Be sure to have the Robot prefab or your Robot Animator selected in your project folder. The Animator has 2 parts, with the Layers and Parameters on the left side and Animation State Machine on the right side:
Layers are useful for 3D animations because they allow you to use animations on different parts of the character. Parameters are used by our scripts to give information to the Controller.
An animation state machine is a graph of all the states of your animations and how they transition from one to another.
Right now we have the 4 animations we have created, and the first one, Left_run, is linked to Entry, which means that animation will play when the game starts.
We could create links between all animations to say, for example, “change the robot’s direction from left to right when the player makes the robot turn to the right”. But that would mean each animation will have 3 links, one to each of the other animations.
An easier way is to use a Blend Tree, which allows you to blend multiple animations depending on a parameter. Here, we will blend our 4 animations based on the direction that the robot is moving, so the Controller will play the right animation.
Start by deleting all the animations by selecting them and pressing delete, or right-clicking on them and choosing Delete. Then right-click somewhere in the graph and select Create State > From New Blend Tree.
Now when the game start, the controller will play the Blend Tree. But right now it’s empty, so it won’t play any animations. Double click on the Blend Tree to open it.
Now click on that “Blend Tree” node and the Inspector will display its settings:
The Blend Type setting defines how many parameters the Blend tree will use to select which animation to play. We want our blend tree to use 2 parameters to control the horizontal and vertical direction changes, so set that to 2D Simple Directional. So, for example, when the robot has a horizontal movement of 0 and vertical movement of -1, the Blend Tree should select the Down_run animation, and when the robot has a horizontal movement of 1 and a vertical movement of 0, the Blend Tree should select the Right_run animation.
Under the Blend Type setting, you will now have two Parameters settings. The Controller uses these values to select which animation to play. Right now, the Controller uses the auto-created Blend parameter, but we’re going to replace this with 2 parameters called Move X and Move Y that will have the horizontal and vertical movement amount.
To create new parameters, just go to the Parameters tab in the Animator window, on the left side of the window:
We need two float type parameters because we want a parameter that can go from -1.0 to 1.0. First, rename the Blend parameter (which is a float type) by selecting it and then clicking on it, and then naming it Move X. Next, click the + icon next to the search bar, choose Float and name the new parameter Move Y.
Now when you select your Blend Tree, you can choose those 2 parameters in the dropdowns at the top of the Inspector, to tell your Blend Tree it should watch those 2 parameters to choose which animation to play.
Then click on the + at the bottom of the Motion section and select Add Motion Field 4 times, because we want to blend 4 animations. Your Inspector should end up looking like this:
Now drag and drop your 4 animation clips into the 4 slots named Motion, and set their Pos X and Pos Y values to the corresponding direction, like this:
Left : Pos X = -0.5 Pos Y = 0
Right : Pos X = 0.5 Pos Y = 0
Up : Pos X = 0 Pos Y = 0.5
Down : Pos X = 0 Pos Y = -0.5
Your inspector should look something like this :
The image represents the blending, where each blue diamond is one clip, and the red dot is the position given by the value of the 2 parameters. Right now it’s in the center, as if Move X and Move Y are equal to 0. But you can see that if Move Y is set to 1 for example, the red dot will go to the top of the square (as Y is the position vertically) and the tree will then select the top animation (running up) because that’s the closest. You can see the value simulated for Move X and Move Y in the Animator on the Blend Tree node:
Or you can move the red dot directly. At the bottom of the Inspector, you’ll see a simulation of which animation the Controller would select for those given value. Press the play button to see it in motion.
Congratulations! You have finished setting up your animation Blend Tree! Now we just need to send the data from our EnemyController script to the Controller to get this working properly in our game.
Sending parameters to the Animator Controller
Open the EnemyController script. The component we want to interact with is the Animator, and like we did the other time we need to interact with a component on the GameObject, we will retrieve it in the Start function with GetComponent and store it inside a class variable.
Now we need to send the parameter values to the Animator. We can do this through the SetFloat function on the Animator, because we’re using float type parameters. The SetFloat function takes the name of the parameter as first parameter and as second parameter the current value of that parameter, here the amount of movement in a given direction, so in the Update function, we add:
In the first part of the movement, inside the if(vertical) block, we add:
Now you can press play and check that the right animation is chosen by the Animator for the Robot!
Setting animation for the main character
To speed up the process (this has already been a long lesson!), we’ve provided the Controller for Ruby in your Project.
You just need to add an Animator to your Ruby Prefab, and assign the RubyController Animator found in the Animation folder to the controller slot.
If you open the Animator with your Prefab selected, you will see Ruby Animator Controller that look something like this
Ignore the Launch state for now, this is a small spoiler for the next lesson about launching a projectile!
For now, just note that we have 3 states that are 3 Blend Trees:
Moving: plays when Ruby runs.
Idle: plays when Ruby is standing still.
Hit: plays when Ruby collides with a Robot or a Damage Zone.
The white arrows between the states are Transitions. They define the movement between states. For example, there is no Transition between Hit and Launch because Ruby can’t throw a projectile when she is damaged!
Click on the downward arrow between Moving and Idle, and look at the Inspector to see the setup for that Transition:
Most of those settings, like the bars on the graph, are useful for 3D where animation can be blended. In 2D, only a couple are useful for us.
Note that Has Exit Time is unchecked. That means the moving animation won’t wait to finish before the State Machine goes to the Idle animation, it will instantly change.
The most important part is the Conditions section at the bottom. A Transition will happen in 2 cases:
If there is no condition, the Transition will happen at the end of the animation. Here, it can’t work because our animation is looping, but if you look at the Transition going from Hit to Idle, that’s what is happening: Has Exit Time is ticked, and no condition is set, meaning the hit animation will play once then transition back into Idle.
Or, we can set a condition based on our parameters. Here, if the Ruby’s speed becomes less than 0.1, then the State Machine will transition from Moving to Idle.
You can click on the other Transitions to look at them and see how they work.
You may wonder why the Hit parameter looks different in the Parameters list. That’s because it’s not a float. Being hit is not a “quantity” like movement or speed, it’s a one-time event. That type of parameter is called a “trigger”. If you use that parameter as the condition for a Transition, the Transition will happen if the trigger is activated from code (in our case, when the character gets hit.)
In fact, now that we have seen how the Controller was set up, let’s modify our RubyController script to send those parameters to the Controller.
Just like for the robot, add an Animator variable and, in the Start function, use GetComponent to retrieve the Animator and store it in that variable. We will also add a Vector2 variable called lookDirection initialized to (1,0):
Vector2 lookDirection = new Vector2(1,0);
Why are we storing the look direction? Because compared to the Robot, Ruby can stand still. When she stands still, move X and Y are both 0, so the State Machine doesn’t know which direction to use unless we tell it. We make lookDirection store the direction that Ruby is looking, so we can always provide a direction to the State Machine. Indeed, if you look at the Animator parameter, it expects a Look X and a Look Y parameter.
We will send those Look parameters and the Speed from the Update function. We just need to change a bit of our code. Right now, it look like this :
position = position + move * speed * Time.deltaTime;
First instead of doing x and y independently for the movement, we store the input amount in a Vector2 called move. It is applied by doing position + move * speed * Time.deltaTime. It will do exactly the same as what we did before, but in a single line, handling x and y at the same time.
We check to see whether move.x or move.y isn’t equal to 0. We use Mathf.Approximately instead of == because the way computers store float numbers means there is a tiny loss in precision. So you should never test for perfect equality because an operation that should end up giving 0.0f could instead give something like 0.0000000001f instead. Approximately takes that imprecision into account and will return true if the number can be considered equal minus that imprecision.
If either x or y isn’t equal to 0, then Ruby is moving, so we set our look direction to our move vector and Ruby looks in the direction that she is moving. If she stops moving (move x and y are 0) then that won’t happen and look will remain as the value it was just before she stopped moving. Note we could have done lookDirection = move but that show another way of assigning x and y of a vector.
Then we call Normalize on our lookDirection to make its length equal to 1. As we said before, Vector2 types store positions, but they can also store directions!
(1,0) stores the position being 1 unit to the right of the center of our world, but it also stores the direction right (if you trace an arrow from 0,0 to 0,1 you get an arrow pointing to the right).
The length of a vector defines how long that arrow is. So, for example, a Vector2 equal to (0,-2) has a length of 2 and points down. If we normalize that vector, it will become equal to (0,-1), so still pointing down but of length 1.
In general, we normalize vectors that store direction because we don’t care about the length, just the direction. You should never normalize a vector storing a position because as it changes x and y, it changes the position!
animator.SetFloat("Look X", lookDirection.x);
animator.SetFloat("Look Y", lookDirection.y);
Then we have the 3 lines that send the data to the Animator, which is the direction we look in and the speed (the length of the move vector). If Ruby doesn’t move, it will be 0, but if she does then it will be a positive number. Length is always positive, remember from the example above, a vector (0,-2) has a length of 2!
Then you can press play and try moving Ruby around to test all the animations.
The only bit left is triggering the hit animation. Sending a trigger to the Animator is done through animator.SetTrigger(“Trigger name”). So in the ChangeHealth function, inside the if(amount < 0) block, we just need to add :
That was a long lesson! But now you have seen how animation works in Unity. Animation clips are the Assets that store the data for the animation. The Controller stores the State Machine that defines how those animation relate to each other. And the Animator plays the animation that the Controller assigns to it and tells it to play. We can send data to the Controller through the Animator in our script to select the right animation based on gameplay!
In the next lesson we will take a look at launching projectiles with Ruby to finally fix those broken robots!