How to Move Characters in Unity 3D: Animated Movement Explained [Built-In Character Controller #2]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Now that we have an understanding of the properties and methods available when using Unity’s built-in character controller, we want to get our character moving and tie that together with character animations! Not a problem! Today we are going to go through the full process of adding movement and animation to our character! And we’ll do so using Unity’s new input system! My name is Nicky and this channel is iHeartGameDev, my channel all about game development! I’m so excited to announce that this video is supported and made possible by all of YOU with the channel’s recently announced Patreon! Now, let’s get started! This project will be broken into three distinct sections: setting up the animated character with its animation controller, setting up the new Input system, and programming the movement with the animations using the new Input System’s code! Feel free to skip around with the timestamps and adjust the playback speed! And Patrons can now access today’s project files directly through Patreon! Thank you so much for your support! Let’s start today’s project by heading over to Mixamo.com and downloading 3 different animations! An idle animation, walking animation, and run animation. We will go ahead and use our usual settings, that have been previously explained in the Mixamo to Unity tutorial. We’ll also need to be sure to check the in-place checkbox. Opening a brand new Unity project, we’ll now import the packages that we need. In the package manager, we’ll select Unity Registry and find, download and import the new input system. Once that’s complete, we’ll switch to “MyAssets” and we’ll also download the Jammo Character model featured on MixAndJam. A link to Jammo on the asset store will be in the description so you can add it to your assets too. Next let’s create an animations folder, and then import our recently downloaded animations! And as shown, I’ve gone ahead and made a small environment that will be great for testing our character movement! Now in order to use the imported animations with other characters, we need to convert them to humanoid. Selecting all 3 of the animations, finding the Rig Tab in the inspector, switching the animation type to “humanoid” and pressing “Apply”, all 3 of these animations will be converted to “humanoid” animations. What this means is that all of the bones in each animation have been remapped to the reusable humanoid structure of Unity’s mecanim animation system. And We’ll also want to properly set up each animation in the animations tab. Let’s first be sure that each animation is properly titled. Occasionally, the animations from Mixamo are titled “Mixamo.com” so we can name them accordingly and press apply. We’ll also want to be sure that “Loop Time” is checked, as this is what has the animations repeat. Unity sometimes modifies the direction the character is facing. To fix this, we’ll modify root transform rotation, checking “bake into pose” and setting “based upon” to "original” for the three animations. Great - animations are humanoid and ready to be used! Let’s add Jammo to the scene! In the asset window, we’ll find the Jammo model in the Models folder of the Jammo package. The Jammo model will need to be converted to humanoid just like our animations. This will generate an avatar from the model, storing the properly formatted bones and allow the model to use the humanoid animations. Now we can drag jammo into the scene and center the character at the world origin. Just to test, we can press play and we’ll see Jammo inanimate and just standing there. Let’s get Jammo animating! By default, models that have been converted to humanoid will come the animator component already attached and the avatar inserted. As a refresher, this component is required for animated gameobjects and hosts the animator controller that will control our three movement animations. Next, In the animations folder found in the project window, we’ll create a new animator controller by right clicking going to “create” and selecting “animator controller”. We’ll title it “characterAnimationController”. We’ll then drag that into the reference box of Jammo’s animator component. In play mode, Jammo still doesn’t animate just yet. We need to add the animations to our new animator controller. Double clicking on the animator controller will open up the animator window. Here we can create our first animation state by right clicking, selecting “create state” and “Empty”. We’ll title this animation state “idle”, and then make two more titled “walk” and “run” as these will handle the respective animations. We can change the default state to idle by right clicking and selecting “setAsDefaultState”. We will then want to create transitions between these three states by right clicking each, selecting “create transition” and connecting them. Currently, we don’t have anything that will cause these three animation states to transition from one to another. We need to create our animator parameters. For this tutorial, we just need two boolean parameters: “isWalking” and “isRunning”. Once created, we’ll modify the transitions. Let’s disable “hasExitTime”, which will ensure that the transition between each state is immediate. And then we’ll set our transition conditions: “isWalking” needs to be true for the transition from the idle state to the walking state. And isRunning needs to be true for the transition from walking to running. We can set the same transitions in the opposite direction, but the required condition will be “false” instead. And lastly, we need to add the proper animations to each animation state using their own reference boxes. Entering play mode with our character selected in the hierarchy, we can now modify the isWalking and isRunning booleans in the animator and see the results! Step Two in this tutorial is to set up our project with the new input system. We’ve already downloaded the New Input System package, so we are ready to get started. In our project window, we’ll right click, go to create and select “Input Actions”. Let’s title it “Player Input”. This asset is how we can customize player input for our entire game: from UI interaction to vehicle controls to character controls. Double clicking will open the Input Actions window in the editor. Here there are three sections: Action Maps, Actions and Properties. All three of which are interconnected. Action maps can be seen as collections of actions. Each action being some type of input from the player that will return data for us to use. And each action has properties, which are modifiable in the properties section when actions are selected. This will be easier to understand with today’s example! Pressing the plus button, we will title the Action Map “CharacterControls”. Now, we can create actions that will be stored within the CharacterControls action map. Let’s title this new action “Move”. Now that we have the action created, how do we use it? When selected, we’ll see its current properties divided into three submenus: Action, Interactions and Processors. The Action Type property allows us to choose between “value”, “button” and “pass-through”. Each option determines what controls we can possibly assign to the action, and ultimately what the return value for the action will be in our code. For the move action we’ll select “value” and we’ll then be presented with a “control type” with an additional dropdown with many options. Typically character movement is handled with “wasd” or a joystick and these values can both be stored within a vector2, so let’s set that for the control type. Interactions will impact how we want the player to use the action, for example: how long the button needs to be pressed to be triggered. But we won’t be needing these today. And “Processors” can be used to modify the return value of an action. For today, we’ll need to use Normalize Vector 2. This will prevent a frequent issue where our diagonal movement can be faster than our forward and side movement. This processor ensures that the max vector length is 1. We’re now at the point where we can specify what controls we want to tie our “Move” action to. Ideally we are able to use “WASD” or the left joystick on a controller. The new input system makes this super easy with “Bindings”. We’ll notice that when we created the “Move” action, there was also an item right below it titled “No Binding”. Selecting it, we will see the properties window change. Now we have the option to select the binding! Pressing on the binding dropdown, we can either search for the input we’re looking for or we can use the “Listen” button which will find any options dependent on the next button pressed! Oh and it’s worth noting that all of the control options will be filtered to options that return vector2’s because of the “option type” we chose earlier for this “Move” action. Now, if we connect a controller, we can press “listen”, and move around the left stick. We’ll see and select the “left joystick” option to set the binding. Awesome! We’ll also notice that the binding has it’s own “interactions” and “processors” options which are specific to this binding. But again, there is no need for interactions today and the processor is inherited from the action. With the left stick binding set up, we’re almost there. How about we also get the binding for keyboard controls: “wasd”. To do this, we’ll create a new binding underneath our “move” action by pressing the plus button next to the action. Here we’ll find the option for the “Vector 2 Composite”. A “Vector 2 composite” allows us to map any buttons we want to the four options available in a vector 2’s up down left right d-pad configuration. For up, we’ll select and bind the “W” key and go ahead and do the same for down left and right with the “S”, “A” and “D” keys respectively. And that’s the process of setting up actions and bindings! Let’s make one more one real quick. Pressing the plus button, we’ll title this new action, “run”. For this, we’ll leave the action type as a button! Buttons return boolean true or false values depending on if the key or gamepad button is pressed or not. Let’s set up two bindings, one for keyboard: “left shift”. And one for controller: “left bumper”. And just like that we have our “move” and “run” actions set up! We’re just one last step away from being able to use the new input system in our code. Back in the project window, we’ll press on the Player Input Input Actions asset. When selected, we’ll see the option in the inspector to “Generate C# Class”. Let’s check the box and press apply for Unity to generate a script of our PlayerInput Input Action! This makes it much easier to access in our code. Ok! We are ready to code! If you are brand new to coding, it’s important to understand everything we type is case-sensitive and needs to be exactly the same to get the same expected results. But don’t worry: with more experience you’ll be able to modify and refactor where you see fit. We’ll try to be as beginner friendly as possible, and this can serve as a solid starting point! To start, We’ll go ahead and attach a new script to Jammo! Selecting Jammo and pressing on the “Add Component”, we can type in “Script” and select “New Script”. Let’s title our script “AnimationAndMovementController” and then double click to open the script in our code editor! Now, let’s start getting control of our character over to the players. We’ll declare a new reference variable “playerInput” of type “PlayerInput” inside the scope of the class. “PlayerInput” being the class that we generated with the checkbox in the inspector. *This won’t work if that step was skipped* Now a declaration doesn’t do much. We’re basically just saying “this variable that we’ve titled player input” is going to be this kind of class. We need to actually set this variable to an instance of PlayerInput so that we can use it! Let’s set this variable inside of a new function -- the “Awake” function. The Awake function is another life-cycle method, like Start and Update, that is run even earlier in the life-cycle than the “Start” function! It’s the perfect place to set our reference variables to instances, like we are doing here! And while we are at it, we won’t need the “Start” Function today, so let’s just delete it! With this reference in place, we can now access our action maps and actions from earlier! How can we properly do this? In this tutorial, the answer is “Callbacks” functions. Instead, a “Callback” is set up to be executed when certain events take place. For example: when a key or button is pressed, or the analog stick is moved, then the callback function will be invoked. We will set up our callbacks in the Awake function below where we just set the playerInput reference. We’ll start by writing “playerInput dot characterControls dot move dot started”. This code navigates into our “CharacterControls” action map, into the “move” action and says “ok Unity, listen for when the player initially starts using this action”. Based on the bindings we set up earlier, would occur either when the player presses the “W” “A” “S” or “D” keys or starts to move the analog stick. Ok, next, we’ll add “plus equals ctx” or “context”. This will essentially gives us access to the current input data when the “started” callback occurs. And finally “equals greater than” and some brackets. Inside of these brackets, we’ll be able to use that context argument and start checking to see what keys are being pressed. Let’s give this a test: typing Debug dot Log and passing in context dot readValue with “Vector2” as the argument we can show the input in the editor console. We use Vector 2 because that’s the option type we chose when setting up the “Move” action! Heading over to play mode and trying to press our keys, we’ll immediately notice that it’s not working. But why? With the new input system, each “action map” actually needs to be enabled and disabled. Back in our code, we can easily fix this using MonoBehavior’s `onEnable` function. We’ll write “playerInput” dot “CharacterControls” dot “Enable”. And we can do the same in an onDisable function so that if this script is ever disabled, our game won’t continue listening for the player’s input. Now in play mode, we’ll see that when we press our WASD keys, the vector 2 will be logged to the console! W is 1 and S is -1 on the vector2’s Y value, and A is -1 and D is 1 on the X value. With access to this vector2 that corresponds to the player’s input, let’s put the data to use! We can go ahead and declare three new variables in the scope of the class. A vector2 called “CurrentMovementInput”, a Vector3 called currentMovement and a boolean “isMovementPressed”. Inside of the brackets of the callback, we’ll write “currentMovementInput is equal to context dot read value vector 2”. Now, we are storing these values in our code! And immediately after, we are going to set currentMovement’s x value equal to currentMovementInput’s x value. And currentMovement z to the currentMovementInput y value. We assign to X and Z because the player controls the movement on the x and z axis. As for the isMovementyPressed boolean, we know that if the player is pressing any of the keys to move Jammo, the x or y value of the vector 2 is going to be greater than or less than zero. So what we can do is set the isMovementPressed boolean equal to that exact logic, writing “isMovementPressed is equal to currentMovementInput dot x is not equal to zero or currentMovementInput dot y is not equal to zero”. Great! We have the current player input accessible via our new variables. How do we get our character actually moving? Obviously, this series is covering Unity’s built-in character controller. And after learning last episode about the Move and Simple Move Functions, it’s time we actually used them.To do so, let’s head back to the editor and attach the CharacterController component to Jammo. Be sure to re-center the collider to fit Jammo properly With that, we can jump back into the code and add a new variable. Keeping it simple, we will declare a variable characterController of type characterController. And in the “Awake function” we will access this component when Jammo is first initialized by setting “characterController” equal to “Get Component” passing in the “CharacterController” class type as the argument. GetComponent will find the attached CharacterController component! Ok! We have both the player input stored and access to the character controller. We have what we need to start getting Jammo moving! In the update function, we’ll write characterController.Move passing in our constantly updated currentMovement variable! Entering play mode and pressing any of our keys and we’ll see Jammo immediately fly off screen. Let’s take this back to our knowledge of the Update function and our Move function. Update is called every single frame, so if our game is running at 60 frames per second, it’s called 60 times. And in every call, we have our Move function moving the distance of the Vector 3 we are passing in. So if we press forward and the Z value of the Vector 3 is 1 point 0, then our character will move 60 units forward per second! To solve this issue, we can multiply our Vector 3 by Time.deltaTime. As a reminder, Time.deltaTime is the amount of time since the last frame -- in other words a fraction of a second. Testing this new solution we’ll see our character now slow down to a consistent and expected speed! Awesome! But wait, when we let go of the keys, they Jammo isn’t stopping. What’s up with that? Let’s look at our callback. “Started” is specifically called when the input system first receives the input, but that’s it! If we want to track when the key is let go, we need to use the “Canceled” callback. So if we copy and paste our callback and all of its logic, then rename “Started” to “Canceled”, we’ll now be able to see when the player releases the key! This works great for keyboard, but controller’s have a little more available motion. There are in-between values from 0 and 1. For this we can use one more callback: “Performed”. “Performed” will continue to update the changes in the player input! Now rule #1 of programming is “Don’t Repeat Yourself”, and here we have the same code being used 3 times. Let’s create a function that will handle this logic! We’ll call it “onMovementInput”. It will accept an argument called context of type InputAction.CallbackContext. And then inside of the function we can paste the logic we had used in our callbacks. We will need to add the namespace “UnityEngine dot InputSystem” for Unity to recognize the CallbackContext type. And now, instead of writing that logic three times, we can simply pass this function! Back in play mode we will now see Jammo move and stop as we continue to press the different keys… but, as we can see, movement and animation are two different problems to solve. Jammo seems to be stuck in the idle state despite the characterController moving. This is because we need to tell the animator when we are walking and when we aren’t. Luckily, we already know that! So let’s grab access to the animator component attached to Jammo and tell it the info! Below characterController, we can declare a new variable “animator” of type “Animator”. And again, in the awake function we can set Animator equal to get Component of type Animator. With access to the animator we can check and update the animation states. Just like the character controller movement, we are going to want to monitor and update the animation states every frame, so we can do so inside the Update function. But if we aren’t careful, our code can start to get really messy. Let’s write a new function that will handle all of the animation state changes. We’ll call it “handleAnimation”, and we can invoke it inside of the Update Function. At the top of this new function, we can check to see the current values of the two boolean parameters we set up earlier: isWalking and isRunning. We know that these parameters control the transitions and switching of animation states. Using the Animator’s GetBool method and passing in the stringified version of the parameter name, we can now locally store their current values! And now we can set up some animation logic! To first get Jammo walking, we will write an “if” conditional: If isMovementPressed is true, meaning a player is pressing in any direction and the animator’s isWalking value is false, then we are going to want to set the animator parameter to true so Jammo starts walking. We’ll set the value using animator’s “SetBool” method. And then we’ll write another with the opposite logic. If isWalking is true but the player is not trying to Move Jammo, then we need to have Jammo stop walking. Testing this in play mode and… success! Jammo is now walking when we press forward! And he stops when we let go! However, it isn’t perfect. Jammo only walks in the direction that it’s facing. How do we turn it? Well there are actually multiple ways this can be done. For today’s tutorial, we are going to use what’s known as a Quaternion. Quaternion’s are a type of number system that we’ll find super helpful when dealing with rotations in 3d space, and Unity has some awesome Quaternion functionality built in. Writing a new function “handleRotation” we can declare three new variables: a Vector3 called PositionToLookAt, a Quaternion we’ll call CurrentRotation, and a float titled “rotationFactorPerFrame” declared in the scope of the class. The positionToLookAt is essentially where we are moving next. In other words, it’s our constantly updated currentMovement variable except we are going to want to extract specifically the x and z variables. Next we can set the currentRotation to transform.rotation. And the rotationFactorPerFrame, we will just set to 1 for now. Below that, we will define a Quaternion called target rotation, and we will set this equal to Quaternion dot Look Rotation passing in our positionToLookAt variable. The built-in LookRotation method will create a new rotation for us to use based on where our player is currently pressing. So let’s add that condition. Right below that, we will use another built-in Quaternion method called Slerp, passing in the currentRotation from above, our new targetRotation and the rotationFactorPerFrame. Slerp stands for spherical interpolation. What this will do is use the quaternions passed as arguments and return a new Quaternion rotation based on the value of the third argument. The third argument should be a number between 0 and 1, and the closer it is to 1, the faster the spherical interpolation will be. Let’s add handleRotation to the update function and test in play mode. So for our example where we have rotationFactorPerFrame set to 1, it will immediately switch to the new direction. Of course, if we multiply by Time.deltaTime when setting the variable, that number will become a fraction instead. And now it will now take multiple frames to complete the full rotation! If we change 1 to a larger number, it will increase the rotation speed! Now in play mode, Jammo is able to walk around and will rotate accordingly! We have just a little more to go! Let’s get Jammo running! Having already created the “run action” with the new input system, we can create a new callback that targets its values. We will write “playerInput dot characterControls dot run dot started” and this time we will pass a new handler function called onRun. onRun will expect the callback context and will update a new variable called isRunPressed in the scope of the class. And because we set the run action as a button action type, we can use “callback context” built in “ReadValueAsButton” method to set the isRunPressed boolean! And just like before, we need to know when the button is let go, so we can duplicate this new callback and just change “started” to “canceled”. Now, where can we use isRunPressed? We’ll first want to adjust the movement speed depending on if the player is running or not. For this, we are going to need a new Vector3. We will title this last vector3 currentRunMovement, and we will set its value the same as we are with currentMovement, except we will multiply the x and the z values by a runMultiplier with a value of 3. We can then go back to the Update function where we have our Move function being called and add the condition that if run is pressed, then we invoke “Move” with the currentRunMovement, otherwise we keep the current implementation! Testing in play mode and Jammo now moves faster when the run button is pressed! And finally we just need to add the running animation states. But before we do this, let’s do a quick refactor. In previous tutorials on this channel, we learned about animator parameter hashing which is a performance optimization. So let’s go ahead and replace our string versions with two new hash variables. We’ll declare them as isWalkingHash and isRunningHash of type integer in the class scope. And then assign them in Awake using the animator dot stringToHash function with string names we used before. Then we’ll replace the strings used in handleAnimation. Ok! Right below where we set the walking conditionals, we can add two new conditionals for running. We’ll check if the player is moving and that run is pressed, and that they are not already running. Inside this condition, we can set the parameter to be true! And in our second conditional, we can check if either the player stops moving or lets go of run, and that the parameter is currently true. In this case, we turn off the run animation! In play mode, we will now see Jammo is able to stand, walk, run and rotate! And because we have the built-in character controller, we get all the benefits that it has to offer! The very last thing we’ll cover today is gravity! Right now if Jammo walks off a ledge, it will not fall. This is because we’ve dealt exclusively with the x and z axes. For today’s project, all we need to be concerned with is setting the y value of our currentMovement and currentRunMovement to a negative number. Let’s write a function, handleGravity, that checks to see if the character is grounded or not. If the character is grounded, because of how the built-in character controller works, we’ll still need to apply some downward force otherwise our isGrounded parameter will switch between true and false. We can call this small downward value: groundedGravity and set it to -.05. And if the character is not grounded we can set the gravity equal to a much higher value, like Earth’s gravity! Now we’ll test in play mode and we will see Jammo falling off ledges! Fantastic! Awesome job today! If you want to vote on the next tutorial on this channel covering the Built-In character controller, you can check out the Ultra Supporter tier on my Patreon! And when we have finished up this series on the built-in character controller, you’ll decide what we learn next! As always, if you want to join and awesome growing community, we would love to have you in the iHeartGameDev channel discord. The links are in the description! But that’s all for today, thank you so much for watching and I’ll see you in the next video!
Info
Channel: iHeartGameDev
Views: 156,123
Rating: undefined out of 5
Keywords: How to Move Characters in Unity 3D, Built-In Character Controller, How To Move Characters In Unity, How to move animated character in unity, animated character movement unity, unity3d movement tutorial, animated character unity tutorial, unity3d character movement explained, unity new input system tutorial, unity character movement full walkthrough, how to move a character in unity3d, how to use character controller unity, character movement in unity3d, Animated Movement Explained
Id: bXNFxQpp2qk
Channel Id: undefined
Length: 27min 43sec (1663 seconds)
Published: Sun May 30 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.