Genshin Impact Movement in Unity | Full Video - Movement System

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Genshin Impact A game that most of you likely have heard by now or even played. In this game, there are around 4 to 5 main ways of roaming around the map. We'll be focusing on the first 3 of these systems: Moving, Gliding and Swimming. Of course, we'll start with their base: The Movement System. Because it is the base, it also means it will take the longest. Now, I planned on splitting the tutorial in 1 video per system, but as I was writing the first tutorial, I noticed it would take quite longer than I expected. So, I made a "Poll" in the "Community" tab and while not by a long shot, most people voted to split it into smaller videos, much like my Dialogue System, so I re-wrote it to make more sense that way. However, because it was quite close in votes, I attempted to re-write it in a way that made sense when put together, so whenever I release the last video of the first part, I'll also release one video with all of those videos together as soon as possible, for those that prefer it that way. Of course, because the video will be quite long, it will likely take hours to render it and would take quite a while to re-watch it as well, so I'll just hope that it renders with no audio artifacts. If there happens to be any, I apologize for it now. Before we head into our tutorial, lets first take a look at what are we going to be using: We'll be using Cinemachine, which is an Unity Package that easily allows us to add a Camera System with pre-made algorithms. The New Input System, also an Unity Package, is the new way of creating and reading Input. The Animator, Animator Controllers and Animations allow us to add animations to our Character. We'll be taking a look at Reusable Sub-State Machines, but won't be taking a look at Blend Trees. Physics-Based Movement simply means Movement using a Rigidbody. We'll not be using the built-in Character Controller. A Floating Capsule is a technique that easily allows our player to move up or down slopes as well as steps without any weird jumps or problems. And finally, State Machines are an easy way of adding, removing and changing states. In our case, they will be our Player States. All of these will be introduced in the Base Movement System Part of the series, which is the main reason why it will take the longest. I'll try to explain as much as I can for what we need, but if you're interested in knowing more about these topics in whatever areas I don't cover, I'll be leaving a few links in the description. Regarding State Machines, it is very likely that you'll see different ways of using them throughout other tutorials, so don't worry if mine is somewhat different from theirs, as that's normal. If you have already finished the series, if you did enjoy it, leaving a like and subscribing would help me out a lot as it allows me to reach more people. Feel free to also leave any feedback whatsoever so that I can improve my future videos. With that out of the way, I really hope you enjoy this series. Lets then start by creating our Project. All we need for this System is a 3D Project. Unless there's any current problem with using HDRP or URP with Cinemachine or the New Input System, we should be able to choose any 3D Template we want. I'll go with a simple normal 3D Template. For the Unity version I'm using, I'm currently using 2020.3 but you should be able to use any you want. I do recommend at least 2020 as we'll be using serialized properties, but, if you are using 2019, that should be fine as well, simply swap the properties with public variables instead or use non-automatic properties. For the name and folder of the Project, choose the one you desire. We'll start by setting up a base namespace to make sure no name conflicts happen. To do that, we go to "Edit > Project Settings > Editor > Root Namespace". I'll name mine "GenshinImpactMovementSystem". Of course, give it a name you like. For simplicity, this will be the only namespace we'll be adding in this tutorial. I also went ahead to Kenney's website and downloaded his Prototype Textures. You can use normal materials if you prefer, in case you don't feel like downloading them. I'll have mine be inside of a folder named "Materials". Next, lets download Cinemachine. To do that, we go to "Window > Package Manager > Packages: Unity Registry" and search for "Cinemachine". Make sure you select the latest version here, which by the time of this video is "2.8.4". This is simply because there are some bugs happening in the older versions so we're just making sure we've got a working one. If by the time you watch this video the default version is at least "2.8.4", then you should be fine. When that's done, press "Install". When Cinemachine is done installing and importing, go ahead and search for "Input System". Select it and feel free to Install it with the default version. A warning should show up asking you if you want to disable the old Input System. While it's not necessary, we don't need the old Input System for this tutorial so I'll go ahead and press "Yes". Do note that this will restart your project. If you chose "No", make sure that in "Edit > Project Settings > Player > Other Settings" your "Active Input Handling" is set to "Both" or "Input System Package (New)". Your project will also restart when you change this option. That's all for our Packages, so we'll now go ahead and start downloading our Character Model and its Animations. We'll of course be using a free model and free animations, so do not expect them to look as good as Genshin Impact Animations. To do that, we'll be using a website owned by Adobe named "Mixamo". So go ahead and open up "https://www.mixamo.com/". You'll need to login with an Adobe account to download the assets but you can create an account using Google or Facebook if you do have them. When you're logged in, go ahead and open the "Characters" tab. This is the list of character models we can choose from. You can choose the character you want, but I'll go ahead and search for "Y Bot" and select it. Once you have your character model selected, press "Download" at the top right. For the "Format", make sure you choose "FBX for Unity". I'll leave the pose as "T-Pose". When that's done, press "Download" again. I'll be renaming the file to "CharacterModel". Then, in Unity, I'll create a new folder named "Models", followed by "Characters" and "Player" inside, and then drag our Character Model here. With our Character Model in Unity, we of course need its animations. So back into Mixamo, go on to the "Animations" tab. We now have a list of animations we can download. For now, we'll only be downloading animations for our Base Movement System. Note that the only important setting in the "Download" tab is the "Format", which should be set to "FBX for Unity". I'll also be renaming the files but I'll leave the new names in the screen. Here is the list of Animations I'll be downloading: "Idle". "Walking", with an "Overdrive" of "15" and the "In Place" option enabled. "Running" around the bottom with the "In Place" option enabled. "Running" above a bit, with the "In Place" option enabled. "Idle To Sprint". "Run To Stop" at the right. "Stop Walking", with a "Starting Trim" of around "45". "Stop Walking" again but with an "Overdrive" of "25", "Character Arm-Space" of "55" and "Starting Trim" of "50". "Jumping" on the second tab at the right, with a "Starting Trim" of around "37". "Falling To Landing" with a "Starting Trim" of around "22". We have all of the animations we need, so it's time to add their files into Unity. Before we do that, make sure you have the following names in your Animation Files. Back into Unity, select all of our Animation Files and drag them into our Models Folder. Now, these are Models that come with an animation. We already have our base model so we of course only want the animation clips, so we'll have to extract them out of the assets we've just imported. If we go ahead and open up one of the assets by clicking on the arrow icon we can see a bunch of other assets inside. The one we want is the last asset, which is an animation clip. Now, if you kept the "ybot@" part of the file name, your animation clip should have the same name as the text that follows it. This is why I've asked you to rename the files. If you did rename the files to not contain the "ybot@" part or whatever name your character model has, then the animation clip will come named as "mixamo.com" and you'll have to rename them through Unity instead, so I recommend just renaming and redragging the assets with the correct names. To get these animations out of our assets, we simply open up every asset, ctrl click each of the animation clips and then press "Ctrl + D" to duplicate them. When that's done, we won't be needing the downloaded assets anymore so feel free to remove them. Do not remove the "CharacterModel" asset though. We now have our animations, so lets add them to a new folder. In the Assets folder, create a new folder named "Animations", followed by "Characters", "Player", "Clips" and "States". I'll drag every single animation to the "States" folder. Then, inside of this "States" folder, I'll create a few more folders: "Moving", "Stopping", "Landing", "Grounded" and "Aiborne". I'll add the "Walk", "Run" and "Sprint" animations to the "Moving" folder. Then, I'll add the "Light", "Medium" and "HardStop" animations to the "Stopping" folder. The "LightLanding" animation will go to the "Landing" folder. When that's done, drag the "Jump" animation to the "Airborne" folder. Whatever remains here except the "Airborne" folder will be moved onto the "Grounded" folder. It's alright if you have no idea what these folder names mean as it will become clear later on, we're just adding them now for organization purposes and to not need to do it later. Before we keep going, there were some animations that we've downloaded that didn't really have the "In Place" option. This means that they will move the player forward without our input, which is something we do not want. We will change that in the following animations. Start by right clicking in the Project tab and add a new "Animation" tab. I'll dock it to the right for a bit and resize it. Then, double click on the "Dash" animation on the "Grounded" folder. The Animation Window should now have all the keyframes of this specific animation. Open up the "Hips: Position" tab and select all "x" and "z" keyframes. Then, press the "Delete" key to delete them. Repeat these steps for the "LightStop", "MediumStop" and "HardStop" animations in the "Stopping" folder as well. Next, we'll need to some some of the Animations to keep looping after they run once. We can do that by enabling the "Loop Time" option in the Animation Clip. We'll do it for the "Idle" Animation and the "Walk", "Run" and "Sprint" Animations. Our animations are now set-up properly. We have our packages, character model  and animations imported into Unity, but, to be able to try out our game later on, it's best for us to have a small test map. We'll go ahead and create one now,  but don't worry as it's easy and fast. If you do not feel it's worth it though, I'll leave a link down below where  you can download the final map. We'll be using Pro Builder to easily build it so go to "Window > Package Manager > Packages: Unity Registry". In here, search for "ProBuilder", with no space in between. When it shows up, select the default version and Install it. When it's done installing, go to "Tools > ProBuilder > ProBuilder Window" and dock it to the same tab as the Animator tab. We'll start by creating the Ground by pressing  on the "New Shape" square with the plus (+) icon. This will open a Shape Tool to  select our Object Shape and its size. Do note that for this to show up, you need to  press directly on the plus (+) icon square. You can also open this Window by pressing "Ctrl + Shift + K". We now have a preview of our Shape in blue. Leave it as a "Cube" and set its  size to be "2000" by "100" by "2000". Press "Build" when you're done and close the window. Then, I'll go to the Kenney Prototype Textures folder and open up the "Dark" PNG Folder and drag the "texture_05" to the Ground. This will create a material so now press  on the ProBuilder "Material Editor" Window and assign this new material to the "Alt + 2" shortcut. If we now select an object and press "Alt + 2", our material will be applied to it. I'll add this ground to a new parent Game Object named "Environment" and also rename it to "Ground". I'll set the Environment Transform Position to "-1000,  -100, 1000" to center it. I'll also drag our Character Model into the Scene. When that's done, add a parent Game Object  to the Character Model and name it "Player". Reset both Game Objects Transforms. This will make it so that our Player has  a different object for its main component and its graphics. It doesn't matter too much but you can  also set the camera to "0, 1.5, -1". Next, we'll add one small ramp and one  big ramp with a small area to walk on. To do so, press "Ctrl + Shift + K" and choose the "Cube" shape. I'll have the size of the small  ramp be "5, 1, 15" and "Build" it. Close the window and select the "Edge Selection" icon. Select the top-end Edge and also the Move Tool. Then, hold "Ctrl" and pull the arrow  upwards until you got around 6 squares. Then, select the top-start Edge without holding "Ctrl" and then move it down until it's on the same level  or close to the same level as the bottom ledge. We now have a ramp, but lets add our  base to walk on by going to its back, select the "Face Selection"  option and select the back face. Then, press "Ctrl + E" to extrude and move  it with the "Ctrl" key held around 5 squares. When that's done, add this object  to the "Environment" Game Object and rename it "SmallRamp". Then, choose the "Object Selection" icon and  move the whole object somewhere else you'd like. I'll also press "Alt + 2" to give it the dark texture. I would like for the top of the ramp to have  another color though, so go to the "Orange" folder and drag the "texture_02" into the ramp  and then press "Ctrl + Z" to undo it. This created a new material so open the "Material Editor" Window and add  this new material to the "Alt + 3" shortcut. Then, select the "Face Selection" icon and  then select the top face and press "Alt + 3". Lets also expand this small ramp to have  a big center by extracting its side faces. Make it as large as you want,  I'll have each side be 5 squares. Although we won't use it in the first part  of the tutorial, our Water will stay here. When you're done, lets add a  ramp that goes down to the Water. I'll select the bottom edge of  the side I want to add a ramp on and press "Alt + U". This creates an edge on the opposite orientation, so now Drag the newly created edge  to be 5 squares from the wall. Then, select the new Face and extrude it with  "Ctrl + E" and drag it a bunch of squares. Now, select the top Edge and drag it down. Then, select the top Face and give it  the orange material with "Alt + 3". Lets build another ramp by pressing "Ctrl + Shift  + K" and make the "Z" be "50" and "Build" it. Make it a child of the "Environment"  Game Object and rename it to "BigRamp". Move it to a place you desire, I'll place it to the left of our "SmallRamp". Then, select its top-end Edge and drag  it up as many squares as you want. When that's done, drag the top-start  Edge down until there's a small step, which will serve as a test for our Step Climb. I'll also extrude the Back Face around 5 squares. Select the whole object now and  give it the "Alt + 2" material and then select the ramp face and  give it the "Alt + 3" material. With that done, we'll now add a new Cube  with a size that fits our Water area. Make it a child of the "Environment"  Game Object and rename it to "Water". Then, go to our "Materials" folder and a  create a new folder named "Environment". Inside, right click the project  window and go to "Create > Material". I'll name it "Water". Change the "Rendering Mode" to "Transparent" and make its "Albedo" color  a blue with a lower Alpha. Then, drag the material into the Water Game Object. Next, remove its mesh collider and add a  new Box Collider and set it to "Trigger". We'll also add 2 small rectangles  to test something later on so press "Ctrl + Shift + K" and build  two "Cubes" with a size of "4, 0.9, 10". Name them "Obstacle" and add them as child  objects of the "Environment" Game Object. Then, move them somewhere close to our Player and separate them, leaving just  a small gap in between them. I'll give these 2 the "Red" folder "texture_04" material. And that's our base map created. Feel free to close or dock the  Tabs somewhere else if you want to. If you don't want to close the "ProBuilder" Window, make sure the "Object Selection" option is selected. We are all set but before we go ahead and start coding I think it's best for you to learn  about States and State Machines first. When opening up and loading a game, the first thing you might notice is that our player is "Idling". This is what we call a "State". In this example, the current "Player State" is the "Idling State". If we were to move, lets say by pressing "W", our player would start "Running". "Running" is yet another "Player State". We've simply changed or transitioned from  the "Idling State" to the "Running State" by pressing a movement key. This gives us two states: "Idling" and "Running". Both of these states are implemented as  their own classes with their own logic. This means that the "Idling State" doesn't  care about what the "Running State" does, but only that it should transition to it  whenever we press a movement key, like "W". The "Running State" is the one that takes  care of giving the player some desired speed. However, that transition needs to somehow happen and the current state logic needs to somehow run as well. That's what "State Machines" are for. They hold the current state, a method to change that current state and, if the state isn't public, one or more methods to run its logic. This brings us to one of the State Machine  System Advantages: Each state has its own logic. An example on why this is a good thing is  the typical "Jumping" and being "Grounded". Lets have an imaginary situation  where our player is "Idling". Whenever we press on the "Space" key, our player should "Jump". One common problem when doing this  without State Machines is that if we keep on pressing the "Space" key, our player will keep on Jumping. The common way of fixing this problem  is by adding an "isGrounded" check. If when the player presses "Space" there  is ground right underneath the player, then the player can "Jump". Otherwise, our Player shouldn't be able to "Jump". While this is alright, not only are the "Idling"  logic and the "Jumping" logic in the same script, the "Jump" is also caring about whether we are Grounded or not while it should only care  about adding an upwards force. However, lets take a look at the same  example using States and State Machines. In our "Idling State", we have  the logic of transitioning to the "Jumping State" whenever we press the "Space" key. When we do so, the State Machine changes  the current State to the "Jumping State". In the "Jumping State", the only  logic we have is to jump upwards, there is no logic telling us that we  should "Jump" whenever we press "Space". This simply means that our player won't  keep Jumping if we keep on pressing "Space". Of course, if we did want to double or triple  jump, we would simply add a count of used jumps and add a new upwards force  whenever we pressed "Space". Something to note as well is that we do not need  to check whether the Player is "Grounded" or not. The reason for that is quite simple: Our player  can only be "Idling" when it is in the "Ground", as otherwise, our Player would be  in another state like "Falling". One other big advantage is that it's quite  easy to add, remove or change a state. With one or a few scripts, we would have  multiple states tangled within each other and it can be a bit of a mess to  add or remove an existing state, at least if you end up having quite a lot of them. With State Machines, we add a new one by creating  a new class and add its own logic to that class and we can change that state implementation  by updating that isolated logic. To remove one, we simply remove the file, the state transitions and whatever  code was necessary for that transition. This does however bring us to a  disadvantage: Each State is its own file. If we end up having 200 States, we'll end up having 200 Files. This can easily add up a lot of files which  makes it harder to keep things organized. However, it does sound better to me than  having those 200 States in just a few files. Another disadvantage is that the Player  can only be in one State at a time. If we're Moving, then we can't be Shooting, for example. Or, if we're Shooting, then we can't be Moving. The first fix you might think is to have  the movement logic in the "Shooting State" so that you can Shoot and Move at the same time. This works but "Shooting" shouldn't really be related to "Moving" as they are  two complete different systems, although it's fine if you decide it to be that way. The actual fix though, is quite simple:  Simply create two State Machines. This means that you'll have a State Machine  for Movement and a State Machine for Combat, which allows you to be in two States at the  same time, in case that's what you desire. As far as I know, we can't move while attacking in Genshin, so they either doit in the same State Machine or disable the other one. One last disadvantage is that State Classes  do not inherit from the "MonoBehaviour" Class. This means that we cannot add them  as components in our Inspector, which also means that any variable  we want to set in the Inspector needs to be done through another script. An example of this would be having a  Player Script that holds all of that data and then passes that data to the  State Machines and their States. Furthermore, because we need access to those variables, we'll either need to make them public or somehow reference them by passing in every single variable. To keep things as simple as possible, we'll  be making each variable a public property and alternate between private, omitted and public setters. Now that we have an idea on what States and State Machines are, lets take a look at what States our Player will end up having for our base Movement System. Our very first State will of course be the "Idling State". As we've seen before, it's a simple  State where the Player stands still. From the "Idling State" we can start "Moving". In Genshin Impact, this is the equivalent of 3 States: "Walking", "Running" and "Sprinting". The main difference between these states  is the speed at what the Player moves at. In Genshin Impact however, we can't  directly go from "Idling" to "Sprinting" but instead need to go to "Dashing" first. "Dashing" is a low-time but  faster movement speed State. When we stop movement in any of these 4  States, they will transition to 3 other States: "Light", "Medium" and "Hard Stopping". These simply decelerate the Player until it comes to a Stop, then transitioning to the "Idling State". We have 2 last States, which are the "Jumping" and "Light Landing". "Jumping" is a simple upwards force while "Light Landing" happens when the Player falls from a small height. There are 2 other "Landing States" that will  be introduced with the "Gliding System", as we'll also introduce "Falling"  there, but for now this one suffices. These are all of the States we'll be  needing for our base Movement System. We, however, can go a bit further. Each one of these States can be part of a Group. And by Group, I basically mean "inherit"  from a Base Class that groups common logic. This is what's called  "Hierarchical State Machines". Much like State Machines though, there  are several ways you can use them, so if you ever watch another video, it's quite possible that they'll have  a different implementation than mine, but, we'll go with the simplest of  them all, which is "State Inheritance". To make things easier, we'll start  Grouping States from the Bottom to the Top. The first States we want to consider are  "Walking", "Running" and "Sprinting". These States can be considered "Moving States", as they're the States the player will be when "Moving". With that same logic, our "Light", "Medium" and "Hard Stopping" States  can be considered "Stopping States". While there's only one of them right now, our "Light Landing" State can also  be considered a "Landing State". All of our current States, besides the "Jumping" State, are States that the Player will be when he's on the "Ground". This means that we can group all of them into a "Grounded State". Following the same logic, our "Jumping" State can be grouped into a State for when the Player is in the air, which we'll be calling an "Airborne State". As we already know, the Movement System  is the base of the other 3 systems. The reason why is because Gliding and Swimming all  use the same movement but with different settings. This means that we can group every  existing State into a Base State, to which we'll call "MovementState". This will simply take care of Moving our Player around. And this is how our State Hierarchy will look like. As I've said before, each of these  States will have to run their own logic. This means that every single one of them needs to have common methods that run that logic. Because of that, we'll be defining an  Interface that defines those methods and implement that Interface into our Base State. Because every other State will end  up inheriting from our Base State, they will be able to use those methods and  also override them with their own logic. To create that Interface, we'll go into Unity and create a new folder named "Scripts" in the "Assets" folder. Inside of this new folder, we'll create  yet another one named "StateMachine". Here, we can now create a new C# Script  by right clicking in the Project Window and going to "Create > C# Script". I'll name mine "IState". Open it up and start by removing the default  methods and the MonoBehaviour inheritance as well. To make it an Interface, simply swap the  "class" keyword with "interface" instead. There are a few methods in State  Machines that people commonly use. The first two are a simple "Enter" and "Exit" methods. The logic in these methods is supposed to run  whenever we transition from a "State" to another. "Enter" will run whenever this State  becomes the current Player State. "Exit" is the opposite and will run whenever  this State becomes the previous Player State. This is good for setting and resetting  data whenever we "Enter" or "Exit" a State. Lets then define a method for each of these. To do that, simply type in "public void Enter();" and "public void Exit();". Remember, we're only defining the  methods here and not implementing them. If you did want an implementation, you  would likely use an Abstract Class instead. It does seem that Unity 2021 does support C#  8, which supports default interface methods but I'm not entirely sure what's the line between  Abstract Classes and Interfaces in that case. Regardless, we now have two method definitions. Of course, we can't really run any constantly  on-going logic with these two methods. For that logic, we often have 3 other methods: "HandleInput", which will allow us to  run any logic regarding reading Input, "Update", which will allow us to  run any non-physics related logic and "PhysicsUpdate", which will allow  us to run our physics related logic. You are likely familiar with the  "Update" and "PhysicsUpdate" methods, which are the equivalent of the MonoBehaviour  "Update" and "FixedUpdate" methods. The only new one here is the "HandleInput" method, which is us simply separating it from the "Update" method. We'll be adding a few more methods here as we need them, but these will work fine for now. That's our State Interface done, so now,  we'll be creating our base State Machine. We'll be using an "Abstract Class". So, back into Unity, in the exact  same folder, create a new C# Script. I'll name it "StateMachine". Open it up and remove the default methods  and the "MonoBehaviour" inheritance. To make this an "abstract class", we simply need  to type in "abstract" before the "class" keyword. We'll start by creating the variable  that will hold the current state. To do that, type in "protected IState currentState;". Note that this is the class that every  State Machine we create will inherit from, so we don't want the "current Player State" but the "current State" of the context we're getting it from. "Protected" is here to make it accessible  in classes that inherit from this one. We now need a few things: The first one is a method that allows us to change  the State Machine current State for another State. The second one, because our current state variable isn't public, are methods that access the current state logic methods. If you don't understand why, don't worry, you'll do later. Simply put though: we'll need them so that we  can call them from the "MonoBehaviour" methods. Lets start with the first one by  typing in "public void ChangeState()". We'll pass in "IState newState" as a parameter. What we need to do here is quite simple. We'll start by calling in "currentState.Exit();". This resets any Data that needs to  be reset before changing states. Then, we set the "currentState"  to be the "newState". When that's done, we call in "currentState.Enter();". This will set any Data that  needs to be set on the new state. That's really all we need to change states. However, we currently have a problem: Because this will need to be called once to set the initial state, our "currentState" variable will be null. This of course means that our "Exit" method  won't be called as it will throw an error, as "null" doesn't contain an "Exit" method. The simplest fix for that is to add an if  statement checking if the currentState is null and if it isn't, we can then call in the "Exit" method. C# however has an handy operator which  is the "null-conditional" operator. Its use is quite simple: Simply add a question mark (?) right  after the code that can return null. In this case, we add it right after the "currentState". Now, if "currentState" returns "null",  C# will not call the "Exit" method. Our "Enter" method doesn't need it as our "currentState" is set to the "newState" right before we call it. It would throw an error if we were  to pass in a "null" "newState", but you probably want to know if that's the case. That finishes up our "ChangeState" method. We should now create a method for  each of our State logic methods. To do that, start by typing in "public void HandleInput()" and inside, call in "currentState?.HandleInput();". That's all we need to do, so duplicate this method twice and swap the name with  "Update" and "PhysicsUpdate". We now have a way to call the current  state logic from a MonoBehaviour class, so our base "State Machine" is done. With our abstract class done, we can now create our Player Movement State Machine. This State Machine will of course take  care of all of the Player Movement States. To create it, in Unity, go back to the "Scripts"  folder and create a new folder named "Characters". Then, inside, create another one named "Player". Inside of that "Player" folder, we'll have  yet another folder named "StateMachines", followed by another folder named "Movement". In there, create a new C# Script to which  I'll name "PlayerMovementStateMachine". Open it up and remove the default methods and swap the "MonoBehaviour" inheritance with "StateMachine" instead. This State Machine can now be used to  change between Player Movement States and run their logic. Of course, we don't really have any State  yet as we still need to create them. Before we do that though, lets  first take a look at the differences between Caching States or Instantiating New States. Whenever we call our "ChangeState"  method, we need to pass in our new State. We can do this by either creating a new instance  of the new State Class every time we Change States or create it once and cache that instance in our State Machine. Creating a new instance every time we change  States ensures that if a state isn't being used, resources won't be wasted unnecessarily, as it will be removed. Caching the instance will always have  resources, like memory, being used, as the instance is still needed and won't be automatically removed but, you won't need to  instantiate new States everytime. One thing to note here as well is that creating a new instance always creates the variables again so their values will be reset to their initial default values, while caching the instance will  always have them the way they were. Although, I don't think it  will matter for us too much. Now, which we should use depends in our use case: When a State will be entered quite a lot,  then caching it might be a good idea. Lets take the example of the Player Jumping. It's extremely common for someone to keep pressing  the Jump button while roaming around the map. This means that the Player State will be  changed to the "Jumping State" quite a lot, which makes it a good reason to cache that  State instead of instantiating it every time. Now, lets take an example of  an NPC which has 2 or 3 states that only get changed every 30 minutes. For example, one would be "Idling",  another one would be "Walking" around and another one would be  "Talking" with another NPC. Because they only change every so often,  there isn't much reason to keep them cached and would probably be better for us to  create a new instance at every state change, or, every 30 minutes. This would also make it if there were 100 NPCs in  the map, we wouldn't have 300 States cached in, although we would have 100 States  being instantiated every 30 minutes. Of course, you can mix both usages if you prefer. For our Player however, we'll be caching in all of our States. Now that we know that, lets start by  creating our first Movement States. We'll start with the "Moving States" without the  Group State, but including the "Idling State" and the "Movement State" as well,  as that will be our Base State. So, back in Unity, in the same folder,  create a new folder named "States". Inside, we'll create a new C# Script, to  which I'll name "PlayerMovementState". When that's done, create another folder  named "Grounded", for our "Grounded States". Inside, create a new C# Script  named "PlayerIdlingState". When you're done doing that, create yet another  folder named "Moving", for our "Moving States". Inside, we'll create 3 new C# Scripts:  "PlayerWalkingState", "PlayerRunningState" and "PlayerSprintingState". When you're done creating them, go back 2 folders  and open up the "PlayerMovementState" script. Remove the default methods and swap the "MonoBehaviour" inheritance with an "IState" interface implementation. If you're using an IDE, it should  show an error that says that we should implement the interface methods, so I'll press "Alt + Enter" and implement them. To make it easier for us to know what  State the Player is currently in, we'll log the current state  class name in the "Enter" method. To do that, type in "Debug.Log();" and pass in ""State: " + GetType().Name". This will show the name of the correct Class Type. Other options like "typeof()" or "nameof()"  would always show the parent class name. When that's done, we want to make sure we can override these methods with  their own logic in each state, so we'll need to add the "virtual" keyword  to every single one of the methods. Then, we'll open up all of the  other 4 States we've created and remove their default methods, making sure we swap their inheritance from  "MonoBehaviour" to "PlayerMovementState" as well. We now have our Initial States  so we can start caching them. To do that, head back to the  "PlayerMovementStateMachine" script. Remember that we'll only need to cache in  States that are not considered "Group States". This is because the player will never be  in the "Player Movement State" but instead on the "Idling State" or "Running State", as the "Movement State" simply  exists to group common logic. So, start by typing in "public PlayerIdlingState IdlingState". I'll make it a property with an ommited set. In case you are wondering, here are the differences between writing "private set;"  or simply "omitting" the "set". If you don't want to think about it, simply make it "private" for all properties  that don't use a "public set". Duplicate this property 3 times and swap  their names to be "Walking", "Running" and "Sprinting". We now of course need to instantiate each one  of them, so create a constructor by typing in "public PlayerMovementStateMachine()" and inside simply type in "IdlingState = new PlayerIdlingState();". Do the same thing for all of the other 3 States. Our States are now Cached. That's great, but you have likely  noticed we aren't yet calling any of the State methods in any "MonoBehaviour" class, which means their logic will never  run when we start playing the game. To do that, we need to first  create that "MonoBehaviour" class. So, back in Unity, go all the way back to the "Player" folder and create a new C# Script. I'll name it "Player". Opening it up, remove the default methods. All we need to do now is to create an instance  of our state machine and call its methods. To do that, create a new "private" variable of type "PlayerMovementStateMachine", to which I'll name "movementStateMachine". We now have the "Constructor", "ChangeState", "HandleInput", "Update"  and "PhysicsUpdate" methods to call. We'll call them in the "Awake"  method, the "Start" method, the "Update" method and the "FixedUpdate" method. In the "Awake" method we'll need to create  a new instance of our state machine, so type in "movementStateMachine =  new PlayerMovementStateMachine();". In the "Start" method we need to  define the player starting State. We'll start our Player in the "Idling State". To do that, type in "movementStateMachine.ChangeState();" and pass in "movementStateMachine.IdlingState". Our player should now enter the "Idling  State" whenever the game starts. In the "Update" method we'll need to handle our  input and run our non-physics related logic, so call in "movementStateMachine.HandleInput();"  and "movementStateMachine.Update();". The order is important as we want to make  sure we've got our "Input Data" updated before we do anything with  it in the "Update" method. In the "FixedUpdate" method we'll  need to run our physics related logic, so call in  "movementStateMachine.PhysicsUpdate();". We're now running our current State  logic through our Player Script. Of course, this will never happen unless we  add this script as a component of the Player, so head back into Unity, select the "Player" Game Object and add the "Player" script as a new component. If we now go ahead and enter play mode, a Debug.Log should be called saying that  the player is in the "Idling State". We are now able to run our State logic  but of course we don't still have any. Before we go ahead and start adding some logic  though, we first need to create our Player Input. The reason why is that we're  using the new Input System, so we'll need to create  the Player Input ourselves. Thankfully, it's quite simple to do that. Lets start by creating a folder in our "Assets"  folder, to which I'll name "InputActions". Inside, we'll create 2 new folders, one inside of the other: "Characters" and then "Player". Inside the "Player" folder, we'll create its Input  Actions by right clicking in the Project Window, going to "Create" and "Input  Actions" at the very bottom. I'll name them "PlayerInputActions". When you're done doing that,  double click on the asset file to open up the Input Actions Window or press on the "Edit asset" button. I'll dock the Window right next to the Game tab. Now, if you never used the new Input System,  you likely have no idea what is what. While I won't do a deep dive  into what each option does, I'll at least try to explain what  we need to know for this tutorial. The first thing we want to create  is what Unity calls an "Action Map". This is nothing more than a group of Inputs. You can divide your Inputs in whatever way you desire, but we'll create a "Player Action Map" that groups every Player Input. So, lets go ahead and press on the plus  (+) icon, which adds a new Action Map. I'll name it "Player". When we select an Action Map,  Unity shows us the existing Inputs, known as Actions, of that Action Map at the right side. It should come with a default Action already but  feel free to right click on it and press "Delete". Right now we'll only be needing two inputs. One is for our "Movement", which  we'll be making it be our "WASD" keys and the other one will be to toggle  between our "Walk" or "Run" States, which will be bound to the "Left Ctrl" key. To do that, press the plus (+) icon  and name the first one "Movement". Then, press it again and name  the second one "WalkToggle". Select the "Movement" Action again. At the right side we now have a "Properties"  area for the currently selected "Action". This area allows us to choose a few things, such as the type of value that we want our Input to return, what type of interaction is needed for the Input to be called and if we want to process that data,  such as Clamp the returned value. For our "Movement" Input we want it  to be constantly reading for changes so that we can get an updated  value in our "HandleInput" method. The way we do that is by setting  the "Action Type" to "Value". The "Value" type is an Action Type that  continuously tracks changes on that action. This is different from the "Button" Action Type that only tracks changes whenever we press or un-press an Input. Now, we want our "Movement" Input to return  something that we can use to move horizontally. If you've ever used the old Input System  that would be the "GetAxis" or "GetAxisRaw" of the "Horizontal" and "Vertical" axis, in which you would then set the "Vertical" axis value to move on the "z" axis, as "x"  and "z" are our Player Horizontal Axis in 3D. So, all we need here is a "Vector2",  as it holds the 2 values we need. We can choose that returned value  type in the "Control Type" dropdown. We now have the type of Input and Value we want to receive. However, we haven't really set any key to this Action. To do that, we use "Bindings". When creating an Action, Unity already adds  a default Binding to it with no Key attached. However, we want our Movement Input to be the "WASD" keys. Currently, our Binding is just one key, so we can't do that. Thankfully, there are other Binding Types  depending on the chosen Action Type, so go ahead and right click in the  current Movement Binding and "Delete" it. Then, press on the plus (+) icon  at the right side of the Action and choose "Add 2D Vector Composite". I'll name it "Keyboard". This now allows us to bind one key for each direction. To bind a key, we need to use the "Path" dropdown. This has quite a lot of keys to choose  from and you could go ahead and go to "Keyboard" and search for the Key you want. However, we'll do something simpler and faster  and that's to "Listen" for our own Input. If we go ahead and press "Listen"  and then press "W" in our keyboard, an option saying "W [Keyboard]" should now appear. Go ahead and select that one. Our "Up" direction is now bound to the "W" key. All we need now is to do the exact  same thing for our other 3 directions, so go ahead and do just that. Just in case you didn't understand, binding  a key to a direction here means that "W", for example, will get the value  of "Up", which is "1" in the "z" axis, which of course in our "Vector2"  will be "1" in the "y" variable. If we press lets say "WD", then we'll get a value  in the "x" and in the "y" variables of the Vector2 and that value will already be normalized, as in our "Keyboard" Binding we have  the "Digital Normalized" Mode selected. That's it for our "Movement" Input,  so lets now do our "WalkToggle". In Genshin, whenever we are  Walking or Running, we can switch between both by pressing the "Ctrl" key. This Input is reading like a button, waiting  for us to press on it and nothing else. There is no reason for us to continuously track this Input value as we don't really need to use it anywhere else besides when switching between these two States. Because of that, we'll choose the "Button" Action Type. For our Binding, we'll be "Listening"  for the "Left Control" key and choose it. We now have the necessary Inputs to start  moving so all we need to do now is to save them and somehow read them. To save these Input Actions, simply  press on the "Save Asset" button above. If you wanted every change you do to automatically save, then you would enable the "Auto-Save" at the right. I will however not do that. We now have our Inputs saved but  we still need a way to access them. If we select the Input Actions asset  file we can see in the Inspector that we have an option to "Generate a C# Class". This class is automatically generated by Unity and provides us a way to access these  Input Actions we've just created. So, feel free to enable it. Now, there are a few fields you can update but  we'll leave everything to their default values and instead just press "Apply". This should create a new C# Script  in the same folder with the same name as our Input Actions asset file, as well as automatically generate code of our Action Maps and Actions, including  their Bindings, Properties, etc.... That's great, but we of course still need an  instance of this class to be able to use it. To do that, we'll create our own Player Input Class. So, go back to the "Player" Scripts folder. In here, create a new folder named  "Utilities", followed by "Input". Inside, we'll create a new C# Script,  to which I'll name "PlayerInput". Open it up and start by  removing the default methods. We'll leave the "MonoBehaviour"  inheritance as we'll need it later. We need a few things to start  using our Input Actions. The first is of course an instance  of our "PlayerInputActions" class. To do that, create a new "public" property of type "PlayerInputActions", to which I'll name "InputActions". I'll give it a "private set;". I'll also create a reference for our Player  Action Map so that we can access it right away. To do that, type in "public PlayerInputActions",  which is our generated C# class, ".PlayerActions". I'll name it "PlayerActions" as  our Action Map is named "Player". The ".PlayerActions" we've added  is automatically generated by Unity and represents the struct  type of our Player Action Map. If you've named your Action Map something different, then it should be your Action Map name  with "Actions" attached to it at the end. With that done, we can start by creating  a new instance of our Input Actions. We'll do that in the "Awake" method. In here, simply type in "InputActions = new PlayerInputActions();". Then, set our "PlayerActions = InputActions.Player;". Note that while "PlayerActions" is the  name of the Action Map struct type, "Player" is the name of the  variable of that struct type. We now have a reference for our Inputs but there's  a mistake I see some people make quite often, which is trying to read their Inputs now. While we indeed have an instance of our Player  Actions, Unity requires us to first "Enable" them. To do that, we'll use the  MonoBehaviour "OnEnable" method and also the "OnDisable" method to disable them, as we don't want them to be enabled if the  Player Game Object is no longer enabled. So, add in the "OnEnable()" method and inside type in "InputActions.Enable();". Then, add the "OnDisable" method and call "InputActions.Disable();". We are now able to read our Inputs. Of course, we still need to add this  class as a component of the player, but we'll also need to add a  reference to it in our "Player" script so that we can access it in our States. So, go back to our "Player" script. In here, start by creating a new "public" property of type "PlayerInput". I'll name it "Input". Make sure you type the same  property "set" as I do, but I'll most of the time make  it "private" unless we need to be able to set the value from other classes. Then, in the "Awake" method, we'll  get its reference by typing in "Input = GetComponent();". We can now add this "PlayerInput"  class as a component of our Player, but lets first add the  "RequireComponent" attribute. Above of our "Player" class, type in  "[RequireComponent(typeof(PlayerInput))]". Now, every time we add the "Player"  component to a Game Object, it will automatically add the  "PlayerInput" component with it as well. Save and go back to Unity and  select the "Player" Game Object. If Unity didn't add the "PlayerInput"  as a component automatically, add a new component of its type yourself. We are now able to read Input  through our "PlayerInput" class, which makes it almost possible  for us to start moving our Player. "Almost" because we still need to set a few  small things before we are able to do it. The first one is of course adding a "Rigidbody". Without it, our Physics-based Movement won't work. That's quite simple to do, we just  select our "Player" Game Object and add a new component of type "Rigidbody". You could of course use the  "RequireComponent" here as well if you'd like. We now have a Rigidbody which means  our player can move using Physics, but it also needs a collider. This is because if we go  ahead and enter play mode, our Player will start falling due to  Gravity but won't collide with the Ground. To add one, exit Play Mode and add a new  "Capsule Collider" Component to the player. If we Inspect the player, we can see that  our Capsule Collider is more like a Sphere. The reason why is that we're not adding it to  the Character Model but to an empty Game Object, which doesn't really have a defined size. We'll have to get the "CharacterModel" height ourselves and then define the  values of this Capsule Collider. The way we know our "CharacterModel" size is by  using its "Mesh Renderer" bounds size variable. I've already got it myself and I know that  my "Character Model" has an height of "1.8", so we'll select the "Player" Game Object  again and give our "Capsule Collider" an "Height" of "1.8". Of course we do have a new problem here, which is the Capsule Collider  being half way into the ground. That's actually correct as  it's centered in the origin, but because we need it to fit our Character Model, instead of moving the Character Model Object down, we'll update the center of  our Capsule Collider to go up. We simply need it to go up half of the  height of the collider, which is "0.9". It should now be in the correct  height but it's still looking quite thick, so lets lower the  radius to be around "0.2". If we now enter Play Mode, our player  should no longer fall through the Ground. Of course, if we were to rotate it a bit, which can also happen when we  collide with another Object, it would fall as we're not restricting any rotations. To fix that, exit Play Mode and  in the "Rigidbody" Component, open up the "Constraints" area and freeze all rotations. We'll also set "Interpolate" to "Interpolate" and "Collision Detection" to "Continuous" for our Player. Interpolation removes some possible jittering while Collision Detection makes it so our  player detects other colliders better, which makes it less likely to go through those colliders. These are recommended to use on your Player but  most often only there as they are less performant. That's basically it but we'll need a reference  of our Rigidbody to be able to use it, so go to the "Player" Script and when you're here,  add a new public property of type "Rigidbody", to which I'll name "Rigidbody". I'll give it a "private set;". Then, in the "Awake" method, type in  "Rigidbody = GetComponent();". We now have every required  Component to move our Player, but we don't really have a way to  access that Player in our States. Thankfully, that's quite easy  to do as we just need to pass in our Player to the State Machine constructor. We have two options here: Pass it into the  State Machine and then into each State, which makes it so we can  access a variable right away, or leave it in the state machine itself so that  we can access through the "stateMachine" variable. I don't really see any worthy advantage  in any of them besides in one not needing to type in "stateMachine"before the Player variable and in the other one not needing to pass  in the Player to the States constructors. Other than that, it should really be the same. I'll leave the Player reference in the State Machine as I'm going to use the State Machine to hold reusable data. So, in the "Player" script, go to the State Machine initialization and pass in "this", which represents the Player class. Then, in Visual Studio, I'll press "Alt + Enter" and choose "Add parameter to  PlayerMovementStateMachine()". Our Movement State Machine constructor  should now have a Player parameter. Next, create a public property above of type "Player", to which I'll name "Player". I'll omit the set as well. When that's done, in the Constructor, set  this "Player" to be the "player" parameter. Our State Machine can now reference the Player, but, States don't yet have a  reference to our State Machine. To do that, lets first go to our "Movement State". In here, add a constructor for this class by typing in "public PlayerMovementState()" and accept a  parameter of type "PlayerMovementStateMachine" named "playerMovementStateMachine". Then, create a "protected" variable above  of type "PlayerMovementStateMachine" named "stateMachine". When that's done, simply set it in  our constructor to be the parameter. We now need to generate a constructor for  each of our States that inherit from this one. We'll actually do that by going back  to our State Machine and pass in "this" for each of the constructors. When you're done doing that,  press "Alt + Enter" and choose "Generate constructor in State". Do it for all of the 4 States. With that done, we are finally  ready to start moving our Player. Our movement logic will stay at the  "PlayerMovementState" base class. So, start by opening it up. The first thing we want to do is  to read the Player Movement Input. We'll save the Input value into a variable, so above create a new "protected"  variable of type "Vector2", which is our Input Action Control  Type, and I'll name it "movementInput". Then, we'll set its value  in our "HandleInput" method, so in there, call in a new method  named "ReadMovementInput();". For organization purposes, I'll be adding this  method to a new region named "Main Methods". I'll go ahead and also add the other methods  to a new region named "IState Methods". You don't need to do this if you don't want  to, or, if you're not using Visual Studio, it's possible that you can't do it. In this new method, we'll  read our Input by typing in "movementInput =  stateMachine.Player.Input.PlayerActions", which gets us our "Action Map". Through this "Action Map",  we can get our "Actions". Our Movement Input was named "Movement", so  type in ".Movement" and to read its value we use the ".ReadValue();" method. This method accepts the type of  the value we're trying to read, which in our case is the "Vector2" type. When that's done, lets go to our "PhysicsUpdate"  method and call in a new method named "Move();". I'll also add it to the "Main Methods" region. Inside, start by checking "if  (movementInput == Vector2.zero)", if that's the case, we "return;",  as it means we aren't moving. We can now start moving our  Player in case there's any Input. The way we'll do that is quite simple: Whenever there's input, we'll add  a force to our Player Rigidbody towards that direction, at a certain speed. We are already reading our Input, so lets also  create a variable that holds our speed value. So above, create two new variables: "protected float baseSpeed;", defaulted to "5f" and "protected float speedModifier;", defaulted to "1f". This second variable makes it so we can edit  our speed without modifying the base speed. Back to our Move method, add to the  if statement "|| speedModifier == 0f". We can now add a force to our Player. To make it more understandable, I'll be creating a new "Vector3" named "movementDirection" and set it to be equal to a new method  named "GetMovementInputDirection();". We'll be returning a new "Vector3" which  simply holds the value of our movement input "y" variable in the "z" axis. So, type in "return new  Vector3(movementInput.x, 0f, movementInput.y);". I'll also be making this method "protected" and I'll add it to a new region named "Reusable Methods". We now have our Movement Input in 3D coordinates,  as "x" and "z" are our 3D Horizontal Axis. Back to our Move method, we'll now  get our movement speed by typing in "float movementSpeed = GetMovementSpeed();". Inside, we'll simply "return baseSpeed * speedModifier;". I'll also make it "protected" and add  it to the "Reusable Methods" region. All that's left to do now is to  add a force for our player to move. Now, commonly, what I see in other tutorials is people setting the Rigidbody  "velocity" variable right away. However, when going into Unity's Documentation, they do not recommend doing that and  recommend using "AddForce" instead. From my experience while doing this  System, I've found that while that works, I did need to set the  "velocity" variable sometimes. The reason why was because adding  a force isn't instantaneous and will only happen in the next Physics Update, while setting the velocity right away  will be an instant change of the force. We'll understand more on the  reason why when we get there, but we'll be using the "velocity"  when resetting our velocity or whenever we can have more than one  "AddForce" running at the same time and use "AddForce" for the remaining  forces, as so far, it seems to work fine. That of course means our Movement  will use the "AddForce" method, so in our "Move" method, under our  "movementSpeed" variable, type in "stateMachine.Player.Rigidbody.AddForce();". For the force, we'll pass in  "movementDirection * movementSpeed". This adds a force, but right now, by default, Unity is adding a ForceMode of "Force", which isn't really what we want. If we take a look at a nice  image made by "nothke", that was posted on Reddit, we see that the "Force" Force Mode not only depends on time but  it's also dependent on our player mass. We don't really want that. Besides not caring about our Player mass, we  don't want it to be time dependent either. We need an instant force that  moves our player at the same speed at the start or middle of the Input. Which leads us to the Force  Mode known as "VelocityChange". Just like it says in the formula, this  is the same as "velocity += force", which is almost the same as using our  "velocity" variable to add a force, but of course using the "AddForce" method,  which gets called in the Physics Update. Note that this is a 3D Force Mode and as  far as I know, this one doesn't exist in 2D, so it's quite possible you'll need to  use the "velocity" variable instead. Do note that it's also apparently not recommended  to use "deltaTime" when setting a velocity. Now that we know that, pass  in "ForceMode.VelocityChange" as the second parameter of the "AddForce" method. It might seem that we're done, but  remember that the "AddForce" method adds a force to the already existing force. This means that if we move our Player  and don't stop pressing our Input Keys, our Player will become Speedy Gonzales. To fix that problem, we simply need to remove the existing velocity from the  force we're going to be adding. To do that, type in, just before adding the force: "Vector3 currentPlayerHorizontalVelocity  = GetPlayerHorizontalVelocity();". Inside of this new method, create a new  "Vector3" named "playerHorizontalVelocity" and set it to be  "stateMachine.Player.Rigidbody.velocity;". Then, set the "playerHorizontalVelocity.y" to be "0f" and return the variable at the end. I'll also make this method "protected" and  add it to the "Reusable Methods" region. Back to our "Move" method, subtract  this horizontal velocity from our Force. If we save and go back to Unity, entering Play Mode should have our Player moving at a constant speed. Remember that we don't need to normalize  our input through code as the Input Action already does that automatically for us. That's the base of our Player Movement  done, but just as a small tip, lets say we were to add another float  to our "AddForce" multiplication. While this might seem normal at first  glance, because our first variable in the multiplication is a "Vector", it means that it will multiply with "movementSpeed" and return another Vector,  and then multiply with this new variable, which also returns a Vector. The problem here is that we're  doing two Vector multiplications, which are less performant  than normal multiplications. If we were, for example, to swap the order  and make our Vector the last variable, we would now multiply the new  float with "movementSpeed", which would just be a normal float multiplication, and only then multiply by our Vector,  which would be a Vector multiplication. We now have one Vector multiplication only  instead of two, which means it's more optimized. This can be a problem when you're  calling the multiplication multiple times like we're doing for our AddForce. It would be even worse if it was in the  "Update" method, as it's called once per frame. Adding the Vector as the last  value of the multiplication basically ensures you only  have one Vector multiplication, unless of course you have  more Vectors in the formula. In our example, we would now have 10  Vector Multiplications instead of 20 in 10 calls just by swapping the order. This doesn't really matter for our system  as we're only multiplying by one value. But, it might be a nice thing for you to keep in mind in case you ever multiply  vectors by multiple float values. In our case though, we can  leave the current order. With that, we've got the base of our Movement done, but if we take a look at Genshin Impact, we quickly notice that this  isn't how Movement works there. Not only does the Player rotate  towards the Movement Direction, that Movement Direction is relative  to the Camera and not to the Input. If we press "W" to move forwards, we'll always  move forwards towards where we are looking at. That also means that if we  press "S", we'll move backwards, or to the opposite of where we are looking at. To be able to do the same, we  need to create a Camera System. Thankfully for us, we're going to be using  "Cinemachine", which easily allows us to add one. This seems to be what Genshin Impact uses as well. We've already downloaded it  when setting up the project, so we can now easily create our Camera  by right clicking in the Hierarchy Window and going to "Cinemachine > Virtual Camera". I'll name it "PlayerCamera". While I was able to somewhat replicate  Genshin Camera with a "FreeLook" camera, there was a missing option that we would  need to add ourselves and apparently "Virtual Cameras" are more performant as they  only use 1 rig while "FreeLook Cameras" use 3. To start off with our Camera, we need a  target for the "Follow" and "Look At" fields. "Follow" is the target that  our Camera will follow around. "Look At" is the target that  our Aim will be relative to. By "relative" here, I mean that the center  of our Aim will always be on the target. To add these, select our "Player"  Game Object and add a new child Game Object named "CameraLookPoint". I'll add an icon to this game object so  that we can easily see where it's placed. Then, I'll make its "Y" axis be at "1.4". When that's done, we'll set our  Camera targets to be this new object. So, back into our "PlayerCamera", drag this new  Game Object to the "Follow" and "Look At" fields. Note that Cinemachine should add a Cinemachine  Brain to your Main Camera automatically, but if it didn't, make sure you do it yourself. This simply makes it so Cinemachine  Cameras can control your Main Camera. Our Camera is now looking  towards our Camera Look Point, but it's still using the old Input System, so our current mouse Input won't work. Thankfully, it's quite simple to  swap it with the new Input System, as all that we need to do is to add a new  component named "Cinemachine Input Provider". This component accepts an "XY" axis, which will represent the camera rotation and a "Z" axis, which will represent the distance. Of course, to assign an Input  here we need to create it first. So, go ahead and open the Input Actions Window. In here, lets first create a new  Action for our mouse movement by pressing on the plus (+) icon and naming it "Look". For our types, we'll have it  be a "Value" of "Vector2", as we want to constantly get our X and Y axis. In its binding, select "Mouse > Delta". Delta is apparently the change in pixels in Vector2 since the  last rendered frame mouse position. For our "Zoom", we'll add another action  named "Zoom" and set it to "Value" as well. We simply want a float here to know  whether we are scrolling up or down. The Value Type for float is called "Axis". Now, we'll use the value this input returns  to know how much we should scroll up or down but it currently comes with high values such  as "120" so we'll clamp it to a lower value. To do that, add a "Processor" of type "Clamp"  and set it to be from "-0.1" to "0.1". This Input also comes inverted, which  means scrolling up would return positive and scrolling down would return negative. We want the opposite of that, so add yet another  "Processor" but this time of type "Invert". When that's done, set its  Binding to be "Mouse > Scroll Y". Then, save the asset. Back in our "PlayerCamera", in the Input Provider  Component, select the corresponding Inputs, so "Look" for the "XY" axis and  then "Zoom" for the "Z" axis. We're now using the new  Input System for our Camera. We're only a few settings away of having  our Camera behaving like Genshin's camera. The first thing we'll be  doing is set our FOV to "60". The next thing is setting both  the camera "Body" and "Aim". The "Body" sets the algorithm that the  Camera uses to "Follow" its target. The "Aim" sets the algorithm that the  Camera uses to "Look At" its target. If we take a look at Genshin Impact camera, we  can see that we can rotate around the target. In the "FreeLook" Camera this is  known as an "orbital transposer". However, in the "Virtual"  Camera, the "orbital transposer" does not contain a vertical  rotation, but only horizontal. Thankfully, we can imitate  the "FreeLook" Camera setting using the "Framing Transposer"  Body together with the "POV" Aim. The "Framing Transposer" tries  to keep the camera centered to the Follow Object at the provided distance. The "POV" aim moves the camera according  to your input, which should translate to "where you are looking at", relative to the "Look At" target. If we enter Play Mode, our Camera should have the same orbital  behaviour that Genshin Impact camera offers. However, there are a few problems: Besides the camera movement being too fast, the faster you move your mouse  around, the slower the movement gets. What we want here is for the camera sensitivity  to be as close to the mouse speed as possible. To fix that problem, open up the "Aim" area and swap both inputs from "Max  Speed" to "Input Value Gain". Make sure your Vertical Axis also  has the "Inverted" option enabled, as otherwise we'll be rotating towards  the opposite vertical direction. Next, we'll set the speed on the vertical axis to  be "0.1" and on the horizontal axis to be "0.16", as Genshin Camera has a  faster horizontal movement. If we now move our Camera around, it should be slower and also have a  closer 1 to 1 sensitivity with our mouse. If you wanted a fully 1 to 1 sensitivity,  then you would need to set the speed to "1". There still are some problems left to resolve. One example is our vertical axis, as we  aren't able to rotate 90 degrees up or down. That's solvable by updating the "Value  Range" field from the vertical axis. We'll set it to be from "-90" to "90". For our Horizontal Axis,  we'll go with "0" to "360". I'm not entirely sure if we actually need this one but I'll leave it as is to  make sure nothing breaks. I'll also enable "Wrap" to allow the camera to keep rotating once we get  to the "360" degree rotation, as otherwise it would stop there and  we would need to move the camera back. The next thing is that if you start  moving your camera around and stop it, you might notice something: The acceleration and deceleration  of the camera are instantaneous. In Genshin Impact however there is a  small acceleration and deceleration. We can easily set that up by updating  the Vertical and Horizontal Axis "Accel Time" and "Decel Time" fields. For the vertical axis we'll go with "0.8"  acceleration and "0.05" deceleration time. For the horizontal axis, we'll also go with  "0.8" acceleration but "0.25" deceleration time. Our camera should now start or stop a bit slower. I'm not really sure what the  "Recenter Target" does here but I have mine set to "Follow Target Forward"  and it works fine so I'll add it here as well. In this case, it seems to be the same as the  "Look" one because both have the same target, but I don't really know what this "Axis  angles are relative to Target" means. I'm assuming it has something to do with  the "Vertical" and "Horizontal Recentering" and that it recenters relative to that  target, but I'm not completely sure. If you do know, please leave a comment below explaining it so that we  know what it actually does. This "Horizontal Recentering" option is  what Free Look Cameras don't provide us with but we'll only need this quite a bit  later so leave it disabled for now. For our "Body", we'll be setting  both the "X Damping" to be "0.2" and the "Y Damping" to be around "0.4". Damping is how fast or slow the camera tries  to keep itself centered with the target, which in this case, should be the "Follow" target. While we won't be needing to set this here, I'll also go ahead and set  the "Camera Distance" to "6". We now have a Camera working quite nicely but  we still need 2 more things to take care off. The first one is that currently our Camera goes  through the ground instead of colliding with it. The second one is that we  can't yet zoom in or zoom out. Thankfully, both are quite easy to do. Lets start by exiting Play Mode. For the Camera Collisions, go to the  bottom of the Cinemachine Component and open up the "Add Extension" dropdown. There should be an option  named "CinemachineCollider". Feel free to press on it. This should add a new component to our Camera Object that allows us to set  Collisions with the Camera. What we want here is to set the "Collide  Against" layer to something to collide with. To do that, we'll create a layer for our  Environment so that the Camera collides with every existing Environment instead of just the Ground. So, add in a new layer named "Environment". Then, go to all of our Environment  Game Objects besides the "Water" and set their layer as "Environment". When that's done, go back to the Camera and  now set the "Collide Against" to "Environment" and remove the "Default" layer from there. For the "Ignore Tag" we'll set it to "Player". Of course, we need to add this tag to  the player so select its Game Object and add the "Player" tag. Next, back into our Camera, we'll be changing  the "Strategy" of the Camera Collider. This is simply how the camera behaves  when colliding with something. In our case, we want the camera to "Pull  forward" when we collide with the Environment, which simply pulls the camera  to the front of the collider. I'll leave the rest with its default values  but feel free to change them if you want to. If we enter Play Mode, our Camera  Collisions should be working. That means that all that's  left is our Camera Zoom. Unfortunately, I don't think there's  a built-in functionality for this so we'll have to do it ourselves. Thankfully, with the Input Provider "Z"  axis and the Body "Camera Distance", this is quite simple to achieve. That's because the input provider "Z" axis gives  us the value of when we scroll our mouse wheel, while the "Camera Distance" is  how far we are from the target. So, go back to the "Scripts" folder  and create a new folder named "Camera". Inside, we'll create a new C# Script,  to which I'll name "CameraZoom". When you're done creating it, add it  as a component of our Player Camera. Then, open it up and remove the default methods. We'll start by creating 3 variables: The default distance that the camera will start at and the minimum and maximum  distances that the Camera can go to. So, type in "[SerializeField]  private float defaultDistance;" and default it to "6f". Then, duplicate the variable line twice and swap "default" with "minimum" and then "default" with "maximum". I'll set their default values  to "1f" and "6f" respectively. We'll also make it so we can update  these variables with a slider, so to do that simply add in after the  "SerializeField" attribute: "[Range(0f, 10f)]", meaning we can choose a value  from 0 to 10 through a slider. Next, we'll add two more variables:  A value to smooth our distance lerp and a value to multiply our "Z" axis value with. So, duplicate one of the variables above twice and  then change the first one to be named "smoothing" and the second one to be "zoomSensitivity". I'll default the "smoothing" to "4f"  and the "zoomSensitivity" to "1f". That's all the data we need to be able to set  in the inspector so now we need two more things: A reference to the Cinemachine Framing  Transposer, which represents our "Body" and a reference to the Cinemachine Input  Provider, which contains our "Z" axis value. So, create a new "private" variable  of type "CinemachineFramingTransposer" named "framingTransposer". Make sure you import the "Cinemachine" namespace. Then, create another "private" variable of type  "CinemachineInputProvider" named "inputProvider". To get their references, we'll  use the "GetComponent" method so type in "Awake" and inside type in "framingTransposer = GetComponent()", which gets our Virtual Camera. We'll now get our framing transposer by using the  ".GetCinemachineComponent()" method instead. For the type, we'll of course pass  in "CinemachineFramingTransposer". We use the "GetCinemachineComponent" method here because the "Body" is part  of the Cinemachine Component. For our provider, type in "inputProvider =  GetComponent();". When that's done, call in the  MonoBehaviour "Update" method and call in a new method named "Zoom();". We can now start zooming our camera. The way we'll do that is by  retrieving the value of our scroll, which we can do from the input provider "Z" axis. To do that, type in "float zoomValue  = inputProvider.GetAxisValue();". Here, we need to pass in an axis index,  which is the index of "2" for the "Z" axis. Then, we multiply this value by our "zoomSensitivity". We now need to add this value  to our current distance target, which we currently have no variable of. So, above, create a new "private  float" named "currentTargetDistance". This will start with a default value of "0" but  we want it to start with the "defaultDistance" so in the "Awake" method type in  "currentTargetDistance = defaultDistance;". Then, back in our "Zoom" method, type in "currentTargetDistance =  currentTargetDistance + zoomValue;". We need to make sure we clamp this target  distance to not pass our maximumDistance, so add in "Mathf.Clamp()"  surrounding our assignment and then pass in "minimumDistance"  as the second parameter and "maximumDistance" as the third one. We can now lerp the current  distance towards the target distance so that we slowly get there  instead of it being an instant change. To do that, create a "float" named "currentDistance" and assign to it  "framingTransposer.m_CameraDistance;". Then, type in "if (currentDistance ==  currentTargetDistance)", we "return;". This makes it so that if we're already at  the target distance, we won't do anything. To lerp our value, type in "float lerpedZoomValue = Mathf.Lerp(currentDistance, currentTargetDistance,  smoothing * Time.deltaTime);". We pass in "smoothing * Time.deltaTime"  here because we don't really want it to be an amount of seconds for the whole lerp but just keep it as consistent as possible at every  change and get there whenever it gets there. Then, finish it up by setting the "framingTransposer.m_CameraDistance = lerpedZoomValue;". That's all we need to Zoom our camera,  so save it up and go back to Unity. If we now enter play mode, we  should be able to zoom our Camera. With our camera working  like Genshin Impact Camera, we can finally use it to rotate our character  and move it towards the right direction. Because we'll need to know our  Camera rotation to do that, we'll need a reference to our Camera. So, open up the "Player" script. In here, create a new public  property of type "Transform" to which I'll name "MainCameraTransform". Then, in the "Awake" method, type in  "MainCameraTransform = Camera.main.transform;". We're getting the main camera  and not the Cinemachine camera because Cinemachine controls the Main Camera, so we are safe to get our data from there. Now, "Camera.main" was something that wasn't very performant before and that's the  reason why we're caching it here. This is because it used to search for all  objects with tags and add it to a temporary list, which would then be searched again for an  object with the Camera Component enabled. However, seems like in Unity  2020.2, that was optimized and now it stores a dedicated list of  objects with the "MainCamera" tag instead and then searches on that list only. And as it says there, they've found the new  way of doing things to be way more performant. However, we'll still cache it ourselves not  only because we're also getting the transform, but also caching it means we can  assign any value to this variable whenever we want and it's changed everywhere, and likely makes the code a  bit cleaner and organized. With that done, lets head  back to our "Movement State". We'll be adding our Player Rotation here. Just under our "Move" method declaration,  create another one by typing in "private float Rotate()". We'll accept a parameter of type  "Vector3" named "direction", which is simply the direction that  our Player will rotate towards. Before we start coding it though lets first  take a look at how it works in Genshin Impact. Lets start by pressing "W". As one might expect, this  will move the player forwards. However, the moment we rotate the camera  around, the player will start rotating with it and move forwards towards  where the camera is looking at. If we instead start pressing  "S" to move backwards, it will keep on doing the same but  towards the opposite direction. The same happens when we move sideways. What's happening here is that our  player is moving towards the sum of our "Input" Direction plus the Rotation of the Camera. So if we press "W" and move the camera "45º", we want to go 0º (Up) from our forward  direction plus 45 degrees from our camera. If we instead press "S", we  want to go 180º (Down) degrees from our backwards direction  plus 45 degrees from our camera. We'll understand more in a bit on why  "W" and "S" are "0" and "180" degrees. Lets then start by getting that sum. The first thing we'll need will be our  movement input angle, or its direction angle. We can get that through a  "Math" method named "Atan2". "Atan2" receives two parameters: the "y"  and "x" of a coordinate, in that order. Lets say we were moving forward, which  translates to the coordinate of "(0, 1)". Remember that our "y" axis here is  the equivalent of the 3D "z" axis. If we pass in "1, 0" to the "Atan2" method, it will be the same as having  the "(0, 1)" coordinate. The angle this returns is "90"  degrees, which should be correct. However, Unity forward axis is the "z" axis. Contrary to the normal degree circle,  it increments its angles clockwise, starting at the top. This means that our "(0, 1)" coordinate should've  been "0" degrees instead of "90" degrees, as "90" degrees in Unity would be  towards the right and not forwards. We can actually make it so that "Atan2"  returns relative to the "z" axis by simply swapping the order  of the parameter values. If we now pass in "0, 1" to  our "Atan2" method instead, which is the same order as the forward coordinate, the coordinate will now become "(1, 0)". If we take a look at the degree circle now, that coordinate is at "0" degrees,  which is exactly what we need. Now, I don't completely understand  the reason why this happens, but I guess that's just how math works. It's like we're saying "give us the  angle relative to the 'z' axis instead", or "relative to the second parameter axis". Lets also take a look at a few others  coordinates: "(0, -1) - Backwards", "(-1, 0) - Left", "(-0.7, 0.7) - Left Forward". With that in mind, lets  get that angle by typing in "float directionAngle =  Mathf.Atan2(direction.x, direction.z);". This method however returns the value in Radians. To transform it into degrees we just  need to multiply it by a certain value, which is by "Mathf.Rad2Deg". This works because we know that "1 Radian"  is the equivalent of an amount of degrees. That amount is stored in the  "Mathf Rad2Deg" variable, so by multiplying it with the amount of  radians, we get the angle in degrees. We however have yet another problem:  Atan2 can return negative values, as it ranges from "-180" to "180". If I remember correctly, there were some  problems with rotations that I needed to make sure that negative angles  were converted into positive angles. I believe it was because a rotation  wouldn't always follow the shortest path to a certain angle, so it could rotate wrongly. Thankfully, it's quite easy to turn  negative angles into positive angles. To do that, simply type in  "if (directionAngle then we sum to the  "directionAngle += 360f" degrees. This means that "-90" will equal to  "270", "-180" will equal to "180", etc.... With that, we have our input direction angle  and simply need to add our camera angle to it. Now, if any of you is wondering why  do we need to add our camera angle if we already have the input direction in degrees, that's because the movement input is always the  same regardless of where the player is looking at. That's of course because that's how  it is defined in the Input Actions, "W" is always "Up", "S" is always "Down", etc... Not only that, but no matter  how much the player rotates, "90" degrees in Unity is always to the same  direction (right), as it is in world space. That means that if we tell  our player to go "90" degrees, it will always go to the right in World Space and not to the Right relative to  where the Player is facing. And that's when our camera angle comes in. Lets say we move "90" degrees,  or to the right by pressing "D". Then, we also move our Camera  90 degrees to the right. Currently, our "Player" would keep  on moving towards the same direction as it's going to the World  Space "90 degrees" direction. However, lets also add the  "90" degrees of our camera to make a total of "180" degrees. Our player will now move "90"  degrees to the right of the camera, which is considered "Down" or "Backwards" in  World Space, the equivalent of "180 degrees", which is exactly what we want. So, to add our camera angle, simply type in "directionAngle += stateMachine.Player.MainCameraTransform.eulerAngles.y;". Note that we need to use "eulerAngles" and not  "rotation", as "rotation" returns a Quaternion. The "y" axis of a Camera  is the horizontal rotation. Of course, when adding the camera  angle to our direction angle, it will be possible to go over 360 degrees. To fix that problem, we simply  check "if (directionAngle > 360f)". If that's the case, we subtract to  the "directionAngle -= 360f" degrees. So, "600" degrees would lead to "240" degrees  or "380" degrees would lead to "20" degrees. With that done, we have our final direction angle. We can now return it by typing  in "return directionAngle;". To keep things organized, lets do two things: Select the first if statement  and assignment and extract it to a new method named "GetDirectionAngle();". Then, select the second if statement  and assignment and extract it to a new method named "AddCameraRotationToAngle();". I'll rename the last method  parameter to be "angle". We now have the direction angle we  want to rotate our player towards. But if we take a look at Genshin  Impact, whenever the player rotates, either by pressing one of the Movement  Keys or by rotating the Camera, it takes some time to get to the target rotation. You might not notice it too much when pressing  the Movement Keys because that reach time is quite low, but you can see that  it isn't null as it isn't snappy. If you do rotate the camera, then you can see  that the player is a bit delayed on its rotation. We'll be smoothing our player rotation much like  they do using a method named "SmoothDampAngle". This method requires a few things from us: The "current angle", which we'll be  able to obtain from our Player Rotation, the "target angle", which is our "directionAngle", a "velocity", which the method  takes care of updating its value and the "amount of time" it should  take to reach the target angle. We'll create one variable for each of  these, including for our "directionAngle", as we'll need it somewhere  else further into this series. So, above, create a few new variables: "protected Vector3 currentTargetRotation;", "protected Vector3 timeToReachTargetRotation;", "protected Vector3  dampedTargetRotationCurrentVelocity;" and "protected Vector3  dampedTargetRotationPassedTime;". We are creating "Vector3"'s because in our "Gliding System" we'll need a  value for the "x" and "z" axis, so we might as well just make  it a "Vector3" right away. We now need to initialize our reach time  to a certain value so in our constructor call in a new method named "InitializeData();". Inside, we'll type in  "timeToReachTargetRotation.y = 0.14f;". We'll later on understand how I  got to this value but just know that it is the time it takes for  the rotation to happen in Genshin. When that's done, back to our "Rotate" method, we'll call in a new method named  "RotateTowardsTargetRotation();". I'll make this method protected and move  it to the "Reusable Methods" region. We'll start by getting the  current angle by typing in "float currentYAngle = stateMachine.Player.Rigidbody.rotation.eulerAngles.y;". We can now start smoothing our player rotation but  only if we aren't already at our target rotation, so, type in "if (currentYAngle == currentTargetRotation.y)" If that's the case, we "return;". Otherwise, we'll smooth our angle by typing in "float smoothedYAngle = Mathf.SmoothDampAngle();". When that's done, pass in "currentYAngle"  and "currentTargetRotation.y". Then, we'll need to pass in the velocity  variable but with the "ref" keyword, as Unity automatically updates and uses this  variable as it wishes inside of the method, so pass in "ref dampedTargetRotationCurrentVelocity.y". For the amount of time it should  take to reach the target rotation, we'll pass in "timeToReachTargetRotation.y  - dampedTargetRotationPassedTime.y". If we were to pass in  "timeToReachTargetRotation" alone here, it would always take "0.14"  seconds for each smooth method call instead of "0.14" seconds for the whole rotation. Of course, we need to increment something to the passed time variable  as otherwise it will keep on being "0", so type in "dampedTargetRotationPassedTime.y  += Time.deltaTime;". Note that because this method is being  called in the "FixedUpdate" method, Unity will automatically return "fixedDeltaTime"  when we use the "deltaTime" variable. We now have our smoothed value, so  we can rotate our player with it. To do that, we'll create a new "Quaternion",  to which I'll name "targetRotation". Then, we'll set it to be "Quaternion.Euler()", which accepts euler angles and  transforms them into a quaternion, and pass in "0f" for the "x", "smoothedYAngle"  for the "y" and "0f" for the "z". When that's done, we'll set the  player rotation by typing in "stateMachine.Player.Rigidbody.MoveRotation(targetRotation); ". That takes care of rotating our Player. However, we need to remember that the moment  our direction angle changes to something else, we need to reset the passed time and also  set the target rotation to our new angle, as we're not yet doing that. So, back in our "Rotate" method,  between the last 2 methods, type in "if (directionAngle != currentTargetRotation.y)", we'll call in a new method named  "UpdateTargetRotationData();" with "directionAngle" as an argument. I'll update the name of the parameter  to be "targetAngle" instead. In here, we'll set the "currentTargetRotation.y"  to be the "targetAngle". Then, we'll reset the passed time by typing in "dampedTargetRotationPassedTime.y = 0f;". We don't really need to reset anything else. We don't need to reset our velocity either, as the "SmoothDampAngle" method  automatically takes care of that for us. Back in our "Rotate" method, I'll select the code of the if statement  up until the "GetDirectionAngle" method and extract it to another method  named "UpdateTargetRotation();". Then, I'll make it "protected" and add  it to the "Reusable Methods" region. I'll also add a new parameter of type "bool" named "shouldConsiderCameraRotation" defaulted to "true". Then, before we add our camera angle, type in "if (shouldConsiderCameraRotation)" and  put the line inside of this if statement. We'll need this later for our "Dashing State". We now have everything  needed to rotate our Player. Of course, we aren't yet calling  the "Rotate" method anywhere. If we take a look at Genshin Impact, if we  move the camera around while Idling or Jumping, our player won't rotate with it. This simply means that our rotation  only happens when we are moving. The Idling and Jumping are not considered "Moving"  because we set their "Speed Modifiers" to "0". So, in our "Move" method, right after  we get the movement direction, type in "float targetRotationYAngle = Rotate(movementDirection);". We now need to transform this angle into a  direction that the player can move towards, as right now we're moving  towards the input direction, which will always be the same  regardless of the camera angle. To do that, type in "Vector3 targetRotationDirection = GetTargetRotationDirection();", passing in "targetRotationYAngle" as an argument. I'll rename the method parameter  name to be "targetAngle" instead. I'll also make it be "protected" and  add it to the "Reusable Methods" region. In here, we'll "return Quaternion.Euler(0f,  targetAngle, 0f) * Vector3.forward;". Now, if I'm being completely honest, I'm  not too sure what actually happens here. If I understood something, it's that we have  a point, which is our forward direction. And then, we want to know what point would  we get if we rotated that forward direction by an amount of degrees, lets say "45 degrees". With this multiplication of a Quaternion, which  represents our "45 degrees angle", by a Vector, some black magic happens and we get our normalized point, which is the direction we want to go to. We pass in "Vector3.forward" here because  we always want a rotation from the "z" axis, which is the player forward axis. Do note, that it's apparently important for the  multiplication to be a Quaternion by a Vector and not the opposite, so order matters here. With that done, lets go back to our "Move" method. In the "AddForce" method, we  now swap the "movementDirection" with our new "targetRotationDirection",  which already comes normalized. If we save and go back to Unity, entering Play Mode should allow us to move  and rotate our Player around correctly. We even get a thumbs up from our shadow. Great! Our player now rotates and moves properly. But, while we're doing that, we're not really using any of our actual States. We're doing all of this while on the Idling  State, which of course shouldn't be happening, as Idling means standing  still doing nothing at all. To fix this problem, lets go  to our "Idling State" script. We have a few things to do here. The first thing is to set the speed modifier to "0f" when we "Enter" this State so to not allow our player to Move. We can do that by "overriding" the "Enter" method and set the "speedModifier = 0f;". We don't need to set this variable  back to "1f" when "Exiting" as every State will update this  variable to a specific value. The second thing we need to do  is to reset our Player Velocity. This is because we can come from  a State that's still moving. If we did not reset the Velocity, our player  would continue to slide due to Physics. We'll be creating a reusable method for this so  head back to the "PlayerMovementState" Script and in the "Reusable Methods" region, create a  new "protected" method named "ResetVelocity()". Here, type in "stateMachine.Player.Rigidbody.velocity = Vector3.zero;". As I've previously explained, we'll be setting this through the "velocity" variable to make  sure it is an instantaneous change. Back to our "Idling State", we  now call in "ResetVelocity();". I'll also add this method to a  new region named "IState Methods". That's most of what we need in our "Idling State". Right now, we'll only add the  transitions to other States and later on in the series we'll  finish whatever is left to do here. Taking a look at our Movement System States, the "Idling State" can transition  to the following states: "Walking", "Running", "Dashing" and "Jumping". Because we'll only do "Dashing"  and "Jumping" later on, we'll only transition to the  "Walking" and "Running" States. The way we'll be doing this is by using something  from the new Input System, which are callbacks. Every action should have 3 phases:  "started", "performed" and "canceled". When these are called depend on the "Action Type"  that we chose when creating the Input Actions. In our case, we need the "Movement"  Input which is of type "Value". For the "Value" Action Type: "started" means when we first  pressed one of the keys. "performed" means whenever we press a key after  the first one without actually releasing it, like going from "W" to "WD". And "canceled" means releasing  the Input Keys completely. We'll be using these actions  for most of our Transitions, but in some cases, this leads us to a problem. Lets say we were "Idling" and decided to "Jump". Whenever we are in the "Jump" state,  moving in the air is not possible, but we're still be able to press the  "Movement" Input Keys if we don't disable them, which we won't. In that case, if we hold one of the "Movement"  Keys while in the "Jumping State" and then land, the expected outcome is for us to start moving. However, because the "Movement Input" has  already "started" when we were "Jumping", the callback we'll add on the "Idling  State" won't be called anymore, which means we would stay in the "Idling State"  even though our "Movement" Keys are pressed. If we were to disable the Input when we  "Jump" and then enable it when we "Land", then the "started" would be called again, but because we won't be doing that  here, we need to find another solution. Thankfully, this is quite simple to solve: In our "Update" method, we simply need to check  if our "movementInput" variable is not "zero", and if it isn't, it means  we should start "Moving". This works because our "Movement Input" is of  "Value" Type, which continuously tracks changes. And if you remember, we're constantly reading for those changes  in the "PlayerMovementState" "Update" method. So, lets do that by typing in  "override Update" and pressing "Enter". Here, type in "if (movementInput == Vector2.zero)". If that's the case, we can "return;". Otherwise, we'll call in a  new method named "OnMove();". Note that even though this  is in our "Update" method, it will only be called once as we'll  be transitioning States in this method, meaning our "currentState" will change  to another State on its first call. Now, in Genshin Impact, we can go from  "Idling" to "Walking" or "Running". The transition depends on our "WalkToggle". If the walk is toggled, then we'll  transition to the "Walking State" and if it isn't, we'll transition  to the "Running State". Of course, we don't really have a way  to know in what toggle state we are, so we'll start by creating a variable  in our "PlayerMovementState" Script. In here, create a new variable by  typing in "protected bool shouldWalk;". To update it, we'll use the "WalkToggle"  Input Action "started" action. For the Button type: "started" is called whenever  we press on the Input Key, "performed" is called whenever the Input  Action is performed, which can change if you add an Interaction, like "Hold". An "Hold" interaction of 1 second here  would only call the "performed" action when the Button is held for that 1 second. This will be useful for us later on. For an Input Action without interactions though, this will be called right  after the "started" action. "canceled" is called whenever we release the Input Key. Start by going into our "Enter" method and call in  a new method named "AddInputActionsCallbacks();". In our "Exit" method, we'll also call in another  new method named "RemoveInputActionsCallbacks();". Make both of these methods  "protected" and "virtual" and move them to the "Reusable Methods" region. The way we can add a callback to the  Input "started" action is by typing in "stateMachine.Player.Input.PlayerActions.WalkToggle.started  += OnWalkToggleStarted;". If we generate a method for this, it  should also come with a parameter. We do not need to pass this parameter when we  add this callback as C# does that for us already. I'll rename it to "context". I'll also make this method  be "protected" and "virtual" and add it to a new region named "Input Methods". What we'll do here is quite simple: every  time we press the "WalkToggle" Input Key, we'll set the "shouldWalk" variable to its opposite value, which we can do by typing in  "shouldWalk = !shouldWalk;". This is how you add a callback to an Input Action. Now, we aren't really using the context variable  here, so do we really need to pass it in? The answer is actually yes. Whenever we add a callback, we need  to make sure we also remove it. The reason why is because we're adding a  callback every time we "Enter" a State, which means that if we were  to "Enter" it 10 times, we would have 10 of the  same callback being called. However, Unity needs to know what's  the callback we are trying to remove and apparently it needs this  "context" variable to know it. I've tried in a project before  removing the "context" parameter and it didn't really remove the callback, so we need to make sure it is there. Now, to actually remove the callback,  we simply copy our line that adds it and paste it in the  "RemoveInputActionsCallbacks" method. Then, we swap the plus (+) operator with the minus  (-) operator to subtract, or remove this callback. And that's our "WalkToggle" callback done. So, go back to our "PlayerIdlingState" and in here we can now use the  "shouldWalk" variable for our transitions. To do that, type in "if (shouldWalk)", we should then transition to  the "Walking State" by typing in "stateMachine.ChangeState(stateMachine.WalkingState);". We can of course "return;" from  the method right after that. Otherwise, if we aren't supposed to be walking,  we should go to the "Running State", so type in "stateMachine.ChangeState(stateMachine.RunningState);". We are now able to transition to  another State from the "Idling State". Of course, we haven't yet done the other States  logic so if we were to change States now, our Player would stand still due to  the speed modifier still being "0". Lets start by adding our  logic to the "Walking State". To do that, open up the  "PlayerWalkingState" Script. We'll start by "overriding" our "Enter" method and then set our "speedModifier"  to be around "0.225f;". This will make it so our Player moves  slowly, much like he was walking. I'll add this method to a new  region named "IState Methods". Note that every value that  I'll write are values that I found to be somewhat close to Genshin Impact. And that's really it for our Walking State  logic, we simply needed to make it move slowly. Of course, we also need to add  transitions to other States, so lets take a look at our Movement System States. From the "Walking State", we can transition to: "Running", "Light Stopping",  "Dashing" and "Jumping". Again, we'll add "Dashing" and  "Jumping" later on in the series. For our "Light Stopping", we'll also add it later so we'll instead  transition to the "Idling State" for now. We'll use callbacks for these transitions. The first one is to the "Running State". This happens when we are walking and  "untoggle" the "shouldWalk" variable. Thankfully, we can do that very easily by  "overriding" the "OnWalkToggleStarted" method. I'll add this method to a new  region named "Input Methods". We need to call our base method logic here as it takes care of setting the  "shouldWalk" variable for us and all we need to do here is to transition  to the "Running State" by typing in "stateMachine.ChangeState(stateMachine.RunningState);". That's it for our Running Transition, so for  the other, we'll be adding a new callback. We'll be using the "Movement"  "canceled" action for this. So, start by overriding the "Add" and  "RemoveInputActionsCallbacks" methods. I'll add them to a new region  named "Reusable Methods". Then, call in "stateMachine.Player.Input.PlayerActions.Movement.canceled  += OnMovementCanceled;". I'll make this new method "protected", rename the parameter to "context" and  add it to the "Input Methods" region. Make sure you also remove the callback  we've just added in the "Remove" method. In the callback, we'll simply type in "stateMachine.ChangeState(stateMachine.IdlingState);". That's all we need for our "Walking State" so  lets next do the logic of our "Running State". So, open up the "PlayerRunningState" script. Start by "overriding" the "Enter" method and set the "speedModifier" to be "1f;". I'll add this method to a new  region named "IState Methods". Again, that's all the logic we need so all  that's left are the transitions to other States. Looking at our Movement System States,  our "Running State" can transition to: "Walking", "Medium Stopping",  "Dashing" and "Jumping". This is much like the situation  of our "Walking State", so lets head back to the "PlayerWalkingState"  and copy the whole "Input Methods" region. Then, back into the "PlayerRunningState",  paste the code we've just copied. Our "OnMovementCanceled" callback  is as it should be for now. For our "OnWalkToggleStarted", we'll swap  "RunningState" with "WalkingState" instead. And that's all we need to  do in our "Running State". Go ahead and save all the files  and then go back to Unity. Entering Play Mode, our Player  should now be working fine and our "Debug.Log" should show the correct State. If we start "Running" and "toggle" our  "Walk" by pressing the "Left Control" Key, we should start moving slowly. Of course, "toggling" it again should now make  the Player go back to its "Running" speed. However, we currently have two problems: The first one is that we're starting to  unnecessarily repeat some code in our States, like our "OnMovementCanceled" callback. We are also hardcoding our values,  such as for our "speedModifier". The second one is regarding  our "shouldWalk" variable and how we are currently  reusing data between States. Lets exit and enter Play Mode again to make  sure everything is reset to its default values. Right now, our "shouldWalk"  variable should be set to "false". That means that if we start moving,  we should enter the "Running State", which is what's happening. But, lets now "toggle" our "Walking State". With this, not only are we moving slowly,  our "shouldWalk" variable should now be true. Lets now stop moving to enter the "Idling State". What do you think will happen  if we start moving again? From what we've done so far, because  the "shouldWalk" variable is now "true", we should start "Walking". However, we can see that's not the  case and are instead "Running". The reason why that's happening is quite simple:  the "shouldWalk" variable is not "static", which means each State has  its own "shouldWalk" variable. So, while in our "Running State" the  "shouldWalk" variable became "true", it remained as "false" in our "Idling State", hence why we started "Running" instead. We can further see this if we stop running,  press "Left Control" and start moving again. We should now be "Walking". One way of possibly fixing this is by setting the  "shouldWalk" variable to be a "static" variable, meaning it would be shared across all  instances of the "PlayerMovementState" class. However, that isn't a great solution when we can  have more than one Player, like in Co-Op games, as it would make it so every Player would share  the "shouldWalk" variable with each other. What we'll end up doing instead is  to create a class that holds data that can be reused between all of the States. Before we do that though, we'll  start by fixing our first problem, which also involves removing the  hard coded values of our code. We don't really have a lot of code that can  be reused in our "Moving States" right now, but we can still Update a few things. In our case, we'll be moving our  "OnMove", "OnMovementCanceled" and the "InputActionsCallbacks" methods to a new State, which will be our "Grounded State". Of course, we still need to create that State, so go to the Player States  folder in the "Grounded" folder. In here, create a new C# Script, to  which I'll name "PlayerGroundedState". Open it up and remove the default methods. We'll also swap the "MonoBehaviour"  inheritance with "PlayerMovementState" instead and generate the constructor it needs. When we're done doing that, we'll go ahead  and swap our "Moving" and "Idling" States to inherit from this State instead, so go to the  "PlayerIdlingState" and swap the inheritance. When that's done, do the same for our  "Walking", "Running" and "Sprinting" States. Then, go to the "PlayerWalkingState" Script and  copy or cut the "InputActionsCallbacks" methods. Next, paste that code into the  "PlayerGroundedState" script. Then, we'll go get the "OnMovementCanceled" method  from the "PlayerWalkingState" Script as well and paste it here with its region. Make it a "virtual" method as well. When that's done, remove them from  the "PlayerRunningState" Script. Next, we'll go to the "PlayerIdlingState"  Script and copy or cut the "OnMove" method and paste it in in the "Reusable Methods"  region of the "Grounded State" script. We'll also make it a "protected virtual" method. That's all we need to reuse for now. All that's left is to create a way for us to set  our current hardcoded values through the Inspector and also create the Reusable Data class  for us to reuse data between States. So, go back to Unity and move back  to the "Player" Scripts folder. In here, we'll create another folder named "Data". Inside, create two other folders:  "ScriptableObjects" and "States". Inside of the "ScriptableObjects"  folder, create a new C# Script. I'll name it "PlayerSO". Inside of the "States" folder,  create two new C# Scripts: "PlayerStateReusableData"  and "PlayerRotationData". Then, create a new folder named "Grounded". Inside of the "Grounded" folder, create  a new C# Script: "PlayerGroundedData". Then, create yet another folder named "Moving" and inside of that folder, 2 new C# Scripts: "PlayerWalkData" and "PlayerRunData". The "PlayerSO", or "Player Scriptable Object", will hold most of the values we'll  be using throughout our System, such as the speed modifiers  or rotation reach times. The "PlayerStateReusableData" will hold the data  that needs to be reused through multiple States much like our "shouldWalk" variable  or our "movementInput" variable. Start by going back to the "ScriptableObjects"  folder and open up the "PlayerSO" script. Remove the default methods and swap  the "MonoBehaviour" inheritance with "ScriptableObject" instead. Then, create a new "public" property of type  "PlayerGroundedData" named "GroundedData". I'll give it a private set. Because we'll need to set this in the Inspector,  add the "[field: SerializeField]" attribute to it. The "[field: SerializeField]" attribute  should work in Unity 2020 or above but if it doesn't work for you,  use public variables instead. I believe you can also use it with  non-automatic properties as well. That's all the Data we need here, so now, we  need a way to create this Scriptable Object. We can do that by adding, just above  the class name, "[CreateAssetMenu()]". I'll pass in the "fileName" of "Player" and  the "menuName" of "Custom/Characters/Player". When that's done, open up the  "PlayerGroundedData" script. In here, remove the default  methods and inheritance. We'll hold the Base Speed of  the player here, so type in "public float BaseSpeed" and give it a  private set and a default value of "5f". Then, give it the "[field: SerializeField]" attribute, together with the "[field: Range(0f, 25f)]" attribute. Then, we'll add our rotation data by typing in "[field: SerializeField] public PlayerRotationData  BaseRotationData { get; private set; }". Next, we'll add our "Walk Data"  and "Run Data", so type in "[field: SerializeField] public  PlayerWalkData WalkData { get; private set; }" and duplicate this line, swapping  the "Walk" with "Run" instead. To make this class show up in the Inspector  we need to make it serializable by typing in just above the class name "[Serializable]", which requires the "System" namespace. We'll now set our "PlayerWalkData" so open it up  and remove the default methods and inheritance. Then, type in "[field: SerializeField] [field:  Range(0f, 1f)] public float SpeedModifier", with a private set and a default value of "0.225f;". Make this class "[Serializable]" as well. That's all for our "Walk Data" and  our "Run Data" will be the same so copy the "SpeedModifier" variable and open up the "Run Data" script. In here, remove the default  methods and inheritance and then paste the variable  line and swap the range to be from "1f" to "2f" and  default the value to be "1f". Don't forget to make this  class "[Serializable]" as well. All that's left for our SO data is the Rotation  Data so open up the "PlayerRotationData" Script. Start by removing the default  methods and inheritance. When that's done, add a new "public Vector3" named "TargetRotationReachTime", giving it the "[field: SerializeField]" attribute. Make this class "[Serializable]" as well. That's it for our Scriptable Object Data, so for our Reusable Data, open up  the "PlayerStateReusableData" Script. In here, remove the default methods  and the "MonoBehaviour" inheritance. I'll be showing on the screen the necessary data to be reused. Lets start with our movement input by typing in "public Vector2 MovementInput { get; set; }". We'll be giving a public "set"  to all of the properties here, as we need to be able to set their values. Then, add the speed modifier by typing in "public float MovementSpeedModifier { get; set; }" and default it to "1f;". For our "shouldWalk", type in "public  bool ShouldWalk { get; set; }". With that done, we now need a  property for all of our "Vector3"'s. However, we have a problem when doing that. Whenever we have a Vector3 property, we  cannot set its variables from another class. So creating a Vector3 here  wouldn't allow us to change its "x", "y" and "z" variables from our States. This only happens when it is a property,  because properties return a copy of the private variable they create when its Type is of "Value Type", which means changing its "x", "y", or "z"  wouldn't do anything in the original Vector3. We could fix this by making it a  public variable instead of a property, but we'll actually fix it by making it  so that the property returns a reference of our private variable. This means that we can change its  variables as it's not a copy anymore but a reference to the actual Vector3. To do that, first create the private  variable by typing in "private Vector3 currentTargetRotation;". Then, under it, type in "public  ref Vector3 CurrentTargetRotation". We'll have to set the "get" ourselves and "return"  a "reference" of our "currentTargetRotation". We don't nor can give it a "set;"  because it is by reference, which basically means "do what  you want with it" already. Now, the reason why we also need the "ref"  in our Property and not just in the "return" is because properties are methods, so our "ref Vector3" is basically  our property, or method return type, which requires the "ref" keyword if  we're returning a value by "reference". All that's left to do is to do the  same for the other 3 variables, so duplicate the existing one 3 more times and  change the variables and properties name to "timeToReachTargetRotation", "dampedTargetRotationCurrentVelocity" and "dampedTargetRotationPassedTime". Make sure you make all the properties public. We now have our Reusable Data. Of course, we do still need to create our  Player Scriptable Object in Unity and also swap the current data usage with our data classes. For our Player Scriptable Object, head back  to Unity and go to the "Assets" folder. In here, create 3 folders, each inside of the other: "ScriptableObjects", "Characters" and "Player". Then, we'll create the Scriptable Object by  going to "Create > Custom > Characters > Player". I'll name it "Player". You can also access this menu  through the "Assets" top menu. We already have most of our  default values set to the correct values but because our base rotation reach time is a Vector3 we weren't  able to set a default value. So, change the "Y" value to be "0.14". That's all we need, but I'll  also add a new Inspector Tab by right clicking in the Inspector  Tab and going "Add Tab > Inspector". Then, I'll lock this tab so that if we  ever need to access the Scriptable Object, we can come here without needing  to go through all of the folders. When that's done, go back  to the other Inspector Tab. Our Scriptable Object is now created so  we need a reference to it in our Player. To do that, open up the "Player" Script. In here, create a new "public" property of  type "PlayerSO", to which I'll name "Data". Add the "[field: SerializeField]"  attribute to it as well. I'll also add a "[field:  Header()]" for "References". When that's done, go to Unity,  select the "Player" Game Object and add the Player SO to the respective field. That's it for our Scriptable Object. We now need to add our Reusable Data and  we'll do that in our "State Machine". We could add it for every single  State but it's unnecessary. So, open up the  "PlayerMovementStateMachine" Script. In here, type in "public PlayerStateReusableData ReusableData { get; }". Then, in the constructor, type in "ReusableData = new PlayerStateReusableData();". We can now start swapping our  values with our Data variables. Start by opening up the  "PlayerMovementState" script. We'll be using the "Rename"  function of Visual Studio for this, which other IDE's should also have. However, it doesn't really let  us add a dot (.) ourselves, but we can cheat that by typing  in "stateMachine.ReusableData.", select it and then copy or cut it. Then, we'll go to our variables, and  we'll select the "movementInput" variable and right click on it and press  "Rename", or just use the "F2" shortcut. When that's done, put the cursor on  the beginning of the variable name and paste our "stateMachine.ReusableData." text. Of course, change the "m" to be  upper case as well and press "Enter". This renamed our "movementInput"  variable usages to what we needed, so feel free to delete the variable now. When that's done, do the exact same thing for the  other existing variables except the "baseSpeed". Rename the "speedModifier" to  "MovementSpeedModifier" as well. Once we're done doing that, we need to  swap our hardcoded values with our SO data. For our base speed, we'll  remove the current variable and create a new "protected" variable of type  "PlayerGroundedData" named "movementData". Then, in the constructor,  we'll set the "movementData" to be equal to the  "stateMachine.Player.Data.GroundedData;". When that's done, go to the "GetMovementSpeed"  method and swap the old "baseSpeed" with "movementData.BaseSpeed". Now, in our "InitializeData" method,  we'll swap the hardcoded "0.14f" with "movementData.BaseRotationData.TargetRotationReachTime". We'll also remove the ".y" from  the "TimeToReachTargetRotation". All that's left now is to do the same thing  for our hardcoded speed modifier values, so go to the "Walking State" and swap the value  with "movementData.WalkData.SpeedModifier" and then go to the "Running State" and swap the  value with "movementData.RunData.SpeedModifier". We don't need to set our "Idling State"  modifier because it is always "0". We don't need to make the "movementData" reusable either because it's always referencing  to the same Scriptable Object Data. We are now reusing our Data and not hardcoding  any values besides the speed modifiers of "0". If we go into Unity and enter Play Mode, you might've noticed when going against  a wall that we kinda get stuck in there. We'll be fixing that by giving our Player  a Physics Material with no friction so that we don't get stuck anymore. So, exit Play Mode and in our "Materials"  folder, create another folder named "Physics". Inside, create a new "Physics Material". I'll name it "PlayerPhysics". Then, set every field to be "0". When that's done, drag it into our  Player Capsule Collider Material field. If we enter Play Mode again, going against  a wall should no longer get us stuck. As a last thing, while we won't really  be reusing any data or logic with it, we'll create our "PlayerMovingState". This is because we'll need it when animating  so we might as well just create it now. So, go to the Player "Grounded" States  Scripts folder and in the "Moving" folder create a new C# Script named "PlayerMovingState". Open it up and remove the default methods and  swap the inheritance with "PlayerGroundedState". Then, go to the "Walking",  "Running" and "Sprinting" States and swap their inheritance to  inherit from "PlayerMovingState". That's all we'll need for now. Saving it all and going back to Unity, entering Play Mode should now have our  "shouldWalk" variable working fine. If we start running and press "Left Control", our Walking will be toggled  and we'll start moving slowly. If we now stop and then start moving again,  we should start walking instead of running, which means that our variables are indeed  being reused between the different States. That's great! Our data can now be reused throughout all of the States. Before we start adding the remaining States though, we'll take a look at how are we going to allow our Player to move up and down Slopes and also "climb" steps up until a certain height. For the reason why, lets enter "Play Mode" and move up and down in our small ramp. Even though for the most part it works, when going down or going up our ramps, we sometimes jump. If we head to the other ramp, we can't climb it either as there's a small step. The first one happens because Unity Gravity isn't fast enough to keep with our horizontal movement speed, so it will look like we've jumped. The second one happens because we're colliding our Player Capsule Collider with the Ramp Collider, which in this case is like colliding with a Wall. It would work if the step was a bit smaller as it would hit the rounded part of the Capsule Collider, but it would be best for us to decide the maximum height we want our Player to climb instead. From what I've found when doing this system, there are 2 common ways of solving the slope problem: Floating Capsules and Normals Floating Capsules, which is the solution we'll be using, consists in adding a force to the player in a way that the capsule constantly floats or, more specifically, the center of the capsule stays at a certain distance from the ground. We can mix this with a resizable Capsule Collider to allow our Player to climb steps up until a certain height. Normals however involve getting the normal of the ground that's underneath the Player and set our Player direction relative to that normal. "Normal" is simply the direction that the Object is facing. However, I don't think Normals solve the "step climb" problem, so we'll go with the "Floating Capsule" technique. With that in mind, we now have two things to do: Allow our Capsule Collider "Height" to be changed through a slider, removing the Height from the bottom only and apply a force to our Player to keep him from falling due to Gravity. Lets start with the collider height. To do that, go to the "Scripts" folder and create a new folder named "Utilities". Inside, create another folder named "Colliders". In here, we'll create a new C# Script, to which I'll name "CapsuleColliderUtility". Open it up and remove the default methods and inheritance. Because we'll need it to show in the Inspector, we'll make this class "[Serializable]". In this script we'll basically be recalculating our Collider Height every time a slider that goes from "0" to "1" is updated in the Inspector, "0" being "0%" and "1" being "100%" of the original Collider Height. To do that, we'll need a few things: A reference to our Capsule Collider, the default Collider Data, and the Slope Data, like our percentage Slider. We'll be creating a Data class for each of these, including our Capsule Collider reference as we'll need to cache some data from it as well. So, back into Unity, go to the "Scripts" folder and create a new folder named "Data". Inside, create yet another folder named "Colliders". In here, we'll create 3 new C# Scripts: "CapsuleColliderData", "DefaultColliderData" and "SlopeData". Start by opening up the "CapsuleColliderData" script. Remove the default methods and inheritance. We'll be storing not only our Capsule Collider reference but also its local space center. So, type in "public CapsuleCollider Collider { get; private set; }" and then "public Vector3 ColliderCenterInLocalSpace { get; private set; }". To initialize this data, we'll create a new method by typing in "public void Initialize()" and pass in "GameObject gameObject" as a parameter. This is because we won't be setting our reference through the Inspector but instead through a Game Object we pass in. If you do want to set it from the Inspector instead, then the next 3 lines won't be required, but you'll need to add the "[field: SerializeField]" attribute to the "Collider" variable as well as the "[Serializable]" attribute to the class. Making it a "GameObject" instead of a "Player" makes this script reusable. Inside, type in "if (Collider != null)", we "return;", as it means it's already Initialized. Otherwise, we'll get it by typing in "Collider = gameObject.GetComponent();". For our Capsule Center in local space we have two ways of doing it. One is by using the "transform.InverseTransformPoint()" method to transform a world space position to a local space position. However, the "Capsule Collider" already contains a "center" variable in local space. We'll be setting it to our own property thought as that makes it so that if we ever need to update this center to be something else, we only need to change it here and it applies everywhere, or, if we ever need to remove it, errors will be thrown wherever we were using the property, which makes it easier for us to know where we need to remove it from. So, type in "ColliderCenterInLocalSpace = Collider.center;". We'll extract this last line to a new method named "UpdateColliderData();" and make it "public". That's it for our Capsule Collider Data for now, so open up the "DefaultColliderData" Script. We'll need this data when recalculating the Capsule Collider new dimensions. Remove the default methods together with the "MonoBehaviour" inheritance. We'll be making this class "[Serializable]" to be able to set the default data through the Inspector. We'll be needing 3 things: Its "Height", its "Center" and its "Radius". So, type in "[field: SerializeField] public float Height { get; private set; }". Then, duplicate this line twice and swap the names to be "CenterY" and "Radius". I'll give the "Height" a default value of "1.8f", the "CenterY" a default value of "0.9f" and the "Radius" a value of "0.2f". The "Height" of our Character Model is known through its "Mesh Renderer" "bounds.size" variable. The "CenterY" is simply half of that "Height" and our "Radius" is something we've defined ourselves before. That's it for our default data, so next, open up the "SlopeData" script. Remove the default methods and the inheritance and make the class "[Serializable]". We'll only need one variable for now which is our Step Height, so type in "[field: SerializeField] [field: Range(0f, 1f)] public float StepHeightPercentage { get; private set; }". I'll default it to "0.25f". We'll add the rest of our variables whenever we need them. That's it for now, so go back to the "CapsuleColliderUtility" Script. We'll create a variable for each of our Data classes. To do that, start by typing in "public CapsuleColliderData CapsuleColliderData { get; private set; }". Again, if you are setting this one through the Inspector, add the "[field: SerializeField]" attribute to it. Then, duplicate this line and swap "CapsuleCollider" with "DefaultCollider". When that's done, add the "[field: SerializeField]" attribute to this property. Duplicate this new line and swap the "DefaultCollider" with "Slope" instead. We now have our data variables so we can start creating the methods needed to recalculate our Collider Dimensions. To do that, create a new "public" method named "CalculateCapsuleColliderDimensions()". We'll be calling this method every time our Inspector values are updated, so, we'll need to recalculate the "Height", "Center" and "Radius". We'll start with our "Radius" by typing in "SetCapsuleColliderRadius();" and pass in "DefaultColliderData.Radius". Then, I'll make the method "public" just in case we ever want to reuse it. Inside, type in "CapsuleColliderData.Collider.radius = radius;". This is needed because we'll need to update the radius to something else later on under a certain condition. Once that's done, we'll need to set our collider height depending on our step height percentage, so duplicate this method and swap the "Radius" with "Height" instead. Then, in our "Calculate" method, call in "SetCapsuleColliderHeight();" and pass in "DefaultColliderData.Height". Of course, we need to multiply this with our step height percentage, which goes from "0" to "1", where "1" is "100%". So, multiply the Height by "* (1f - SlopeData.StepHeightPercentage)". We add the "1f -" here because the "step height percentage" is the "percentage" we want to remove, so removing "0.25", or "25%", means that our new "Height" should be "75%" of its default "Height", which we can get by multiplying the "Default Height" with "0.75". With that done, we now need to recalculate our "Center". While our "Center" is half of our Height when the Height is at 100%, this isn't true when we start lowering the Height. The reason why is because we'll only remove "Height" from the bottom to represent the "Step Height", which means we want the Capsule Collider to center itself at the top and not in the middle of the Character. Thankfully, it's quite easy to do that: We simply need to get the difference between the default height and the current height and then add half of that difference to the "Center". "Half" here is the same as the "bottom" or the "top" part and this basically makes it so that our "top" difference will add itself to the "bottom" instead. So, call in a new method named "RecalculateCapsuleColliderCenter();". I'll make this method "public" as well. Inside, type in "float colliderHeightDifference = DefaultColliderData.Height - CapsuleColliderData.Collider.height;". We get the default height first because our current height will never be bigger than our default height. When that's done, create a new "Vector3" named "newColliderCenter" and set it to be "= new Vector3(0f, DefaultColliderData.CenterY + (colliderHeightDifference / 2f), 0f);". Again, we're making it go up half of our Height, which is the same as adding our Top Height difference to the bottom. Then, we set the center by typing in "CapsuleColliderData.Collider.center = newColliderCenter;". Note that this "Collider.center" is measured in local space, so we can assign our new center without transforming it into World Space, which if you ever need, you can do it by using the "transform.TransformPoint" method. That's mostly it when recalculating the dimensions of our Capsule Collider, but we currently have a problem. Whenever we update our Step Height, it will come to a certain point where our Capsule Collider becomes a Sphere from how small it is and will start moving up when we keep removing height from it. This seems to happen whenever our "Height" is less than double of our "Radius". It's really simply because a "Capsule Collider" top and bottom are half spherical, which makes it become a sphere when there's no more middle ground to remove, so it starts going up instead, likely because it starts becoming a negative-like middle ground. What we'll do to fix it, is simply set our "Radius" to half of our "Height" whenever the height gets to that point we've mentioned earlier. This makes it so that instead of going up, it will keep being where it was but will instead become smaller. It isn't a perfect solution, but in my opinion, it's better. So, to do that, type in "if (CapsuleColliderData.Collider.height / 2f If this is confusing, we basically need to know if our "Height" is double or less of our "Radius", so if we divide our "Height" by "2", we get half of it. If half of our "Height" is equal to the "Radius", then it means that the full Height is Double of the "Radius". If half of our "Height" is less than the "Radius", then it means that the full Height is less than Double of the "Radius". In the last case, we need to Update the "Radius" to that half "Height", which keeps our "Height" at least double of our "Radius" at any given time. To do that, type in "SetCapsuleColliderRadius(CapsuleColliderData.Collider.heigh t / 2f);". I'll also extract this half height into its own variable named "halfColliderHeight" and swap it in the method call as well. That fixes our problem but we also can't forget that when we recalculate our center, we also need to cache our center in local space again, so call in "CapsuleColliderData.UpdateColliderData();". With that done, we now only need an initialization method so above type in "public void Initialize()" and accept a "GameObject" named "gameObject". Then, call in "CapsuleColliderData.Initialize(gameObject);". Of course, in case this class isn't "Serializable", which in my case isn't, we'll also need to instantiate it, so above, type in "CapsuleColliderData = new CapsuleColliderData();". And above that, type in "if (CapsuleColliderData != null)", we "return;", as to not keep calling this once it's already initialized. That's it for our Collider Utility so lets now add it to our Player. To do that, open up the "Player" script. In here, create a new "[field: SerializeField] public" property of type "CapsuleColliderUtility" named "ColliderUtility", with a private "set". I'll give it a "[field: Header("Collisions")]". When that's done, in the "Awake" method, call in "ColliderUtility.Initialize(gameObject);". Then, to make sure our collider dimensions are updated, call in "ColliderUtility.CalculateCapsuleColliderDimensions();". This takes care of setting our collider data whenever we enter Play Mode, but doesn't take care of setting it in the Editor whenever we update the slider value. To do that, we'll call in the MonoBehaviour "OnValidate()" method. We'll copy the two lines we've just added in the "Awake" method and paste them here. Saving and going back to Unity, every value update we do in the Inspector should now recalculate our Capsule Data. Our Capsule Collider Height is indeed updating fine. Of course, if we go ahead and enter Play Mode, our Player will fall and have its legs be stuck in the Ground as our Capsule Collider is no longer the size of our Character Model. That's where the Floating Capsule technique comes to the rescue. Simply put, we'll add a force to counter the gravity pulling us down. The way we'll be doing that is somewhat simple. Lets take a look at our Player Model with its Capsule. In the Editor Mode, we are able to update the Capsule Collider Height by only taking away Height from the bottom. Of course, because we're in the Editor Mode, there is no gravity and that's why it stays up. What we want is to keep this place even when we enter Play Mode. However, as we've seen just now, if we do enter Play Mode, our Player will fall until it collides with the ground. This is simply because our "Character Model" is just plain Graphics and doesn't take care of any collisions, as we've left that for our "Capsule Collider". The "Capsule Collider" then sees nothing underneath so it will start falling down due to Gravity until it collides with something. So, how do we make it so that the Player doesn't fall down due to Gravity and stays at the distance it was in the beginning? We have our "Capsule Collider Center" in Local Space, which currently, for our values, should be around "1.125". We'll then cast a ray down from the Capsule Collider World Space Center, as "Raycast" requires a world space origin, up until a certain distance. If this ray hits something, which should be our "Ground", it will gives us the "distance" between the origin and the ground hit point. We then should simply remove that distance from our Capsule Collider Local Space Center. There are 3 possible outcomes here: A "positive" value, a "null" value and a "negative" value. A "positive" value happens when our Capsule Collider isn't yet where it should be and should still go up. For example, lets say that in this position, the distance from our ray to the ground is "1". "1.125 - 1" results in "0.125". This means that our player still needs to go up "0.125" units for it to be at our desired point, which can be done by adding a positive vertical force. A "negative" value is quite the opposite, it means that our Capsule Collider is above where it should be and should go down. For example, lets say that in this position, the distance from our ray to the ground is "1.5". "1.125 - 1.5" results in "-0.375". This means that our player went "0.375" units above its desired point and needs to go down "0.375" units to be where it should be. This can be done by adding a negative vertical force. A "null" value happens when our Capsule Collider is at its desired point. For example, lets say that in this position, the distance from our ray to the ground is "1.125". "1.125 - 1.125" results in "0". This means that our Player is at the desired point, as it's both up or down "0" units, which is perfect. Of course, we need to keep a few things in mind here. The first one is that we need to use our local space center for this subtraction. If we were to pass in the world space center, then the "Y" could be "0", "1", "1000", "-250", etc. depending on the Player position at that time. The second thing is that we need to add the negative vertical force ourselves because if we let Unity Gravity take care of it, it would be too slow and we would still have our Slope "Jump" problem. The third thing is that these values that we'll receive will be smaller than that "1.125" value, meaning that they will be extremely small. Adding a force with such a small number will not be strong enough to overpower Unity Gravity. Because of that, we'll need to multiply that value with a force multiplier to make sure it has enough force to go Up or Down as fast as it can, instantaneously to the Human Eye if possible. Also, we need to remember that "AddForce" keeps on adding a force, so we need to make sure we remove the current vertical velocity as otherwise our Player would start bouncing. It doesn't end up flying because we'll also be adding a negative force for when we pass through the desired point, so it will keep coming back and start bouncing instead. The fourth and last thing is that it is very unlikely that our value will be "null", or "0". The reason why is because we're using Physics. So, by the time we call another "AddForce", Unity's Gravity will have added a tiny bit of negative velocity. But, we'll still check if it is "0" so that we can return and do nothing if that's ever the case. We'll do all of this in our "Grounded State", so open up the "PlayerGroundedState" Script. The reason why we'll do it here is because we really only want our Capsule to "Float" whenever we're on the "Ground", as the rest of the States, "Airborne" and "Aquatic" won't really need it and would probably not work very well if we added it there. With that in mind, start by "overriding" the "PhysicsUpdate" method. I'll add this method to a new region named "IState Methods". Then, call in a new method named "Float();". You can name it "FloatCapsule();" or anything else if you prefer. I'll add this "Float" method to a new region named "Main Methods". We'll start this off by casting our Ray Downwards towards the "Ground". To do that, we have the "Physics.Raycast" method. This method requires us to send the origin of the Ray in World Space, so create a new "Vector3" named "capsuleColliderCenterInWorldSpace" and set it to be "stateMachine.Player.ColliderUtility.CapsuleColliderData.Col lider .bounds.center;", which returns the center in World Space. Then, we'll create a new variable of type "Ray" named "downwardsRayFromCapsuleCenter" and assign it to "new Ray(capsuleColliderCenterInWorldSpace, Vector3.down);". As far as I understood, the difference between "Vector3.down" and "-transform.up" is that "Vector3.down" will always be in World Space, while "-transform.up" will be relative to the transform rotation. When that's done, type in "if (Physics.Raycast())". The first argument is our Ray, so pass in "downwardsRayFromCapsuleCenter". For the second argument we need a "RaycastHit" variable, which we can pass in "out RaycastHit hit", which allows us to use the "hit" variable inside of the if statement. This variable is what we'll use to know our distance. Of course, we now need a distance for the ray, so lets go to our "SlopeData" script and when you're here, add in a new "[field: SerializeField] [field: Range(0f, 5f)] public float FloatRayDistance { get; private set; }". I'll also default it to "2f;". You don't really need it to be "2f" here but just a bit above half of the Capsule Height. Giving it an higher number ensures it works and will also be used for something we'll see later on. Back into the "Grounded State", we'll need the slope data so lets go to our variables and type in "private SlopeData slopeData;". This is so we don't need to type in a long line, as we'll use it more than once. Then, in our constructor, set the "slopeData" to be "= stateMachine.Player.ColliderUtility.SlopeData;". Back to our "Float" method, we can now pass in "slopeData.FloatRayDistance". For our fourth argument we need the Layer or Layers that this ray will collide with. We'll only be colliding with our "Environment" layer but we don't yet have a way to get it. We could pass in its layer number or string but that isn't a great way of doing it. We'll instead create a new script to hold this data so head back to Unity. In here, open up the Player Data Scripts folder and create a new folder named "Layers". Inside, create a new C# Script named "PlayerLayerData". Open it up and remove the default methods and inheritance. We'll make this class "[Serializable]". When that's done, create a new "[field: SerializeField] public LayerMask GroundLayer { get; private set; }". Then, go to the "Player" Script and add a new property under our collider utility by typing in "[field: SerializeField] public PlayerLayerData LayerData { get; private set; }". Save and head back to Unity. Selecting the "Player" Object, go to the "Layer Data" area and set the "Ground Layer" to be "Environment". Back to our "Grounded State", pass in "stateMachine.Player.LayerData.GroundLayer". For our fifth argument, we'll pass in "QueryTriggerInteraction.Ignore", which ignores objects that have the "Environment" Layer but are Trigger Colliders, as we don't want to consider triggers a "Ground" we walk on. That's it for our if statement, so we can now start floating our Capsule. To do that, create a new variable to hold our remaining distance by typing in "float distanceToFloatingPoint = stateMachine.Player.ColliderUtility.CapsuleColliderData .ColliderCenterInLocalSpace.y", as we want the vertical axis. Now, this isn't extremely important for us but if we ever scale our Player Game Object, our capsule won't stay afloat at the point that we want. To fix this, we can multiply this collider center "y" with the player "y" local scale. So, multiply it by "* stateMachine.Player.transform.localScale.y". Of course, if the scale becomes too big, the distance will be more than "2" so the raycast won't find anything and our Player will enter the "Ground", which is solvable by increasing the distance value. We now simply need to subtract the distance from our ray hit, so type in "- hit.distance". This is of course the distance from the center of the capsule collider to the ground. We can now float if this value isn't "0", so first type in "if (distanceToFloatingPoint == 0f)", we "return;". Then, we'll need our lift force, which means we need a Vector3. Start by typing in "float amountToLift = distanceToFloatingPoint;". We now need to multiply this value with that extra force we've talked about previously and also remove the current vertical velocity. For the force, head back to the "SlopeData" script. In here, duplicate the ray distance variable and set the range to be from "0f" to "50f" and name it "StepReachForce". I'll default its value to "25f;". When that's done, go back to the "Grounded State" script and multiply our distance with "* slopeData.StepReachForce". We'll need to subtract it with the player vertical velocity now so lets create a method to get us that. Go to the "PlayerMovementState" Script and under the "GetPlayerHorizontalVelocity" type in "protected Vector3 GetPlayerVerticalVelocity()" and inside type in "return new Vector3(0f, stateMachine.Player.Rigidbody.velocity.y 0f);", You could also just return the "y" value if you'd prefer, which would return a float instead of a Vector3. Back into the "Grounded State" Script, subtract the "amountToLift" with "- GetPlayerVerticalVelocity().y". We now need to convert this float force into a Vector3 force, so type in "Vector3 liftForce = new Vector3(0f, amountToLift, 0f);". Then, to add this Force, we call in "stateMachine.Player.Rigidbody.AddForce(liftForce, ForceMode.VelocityChange);". "AddForce" here is fine even though we have another "AddForce" when moving because this one is a vertical force while the other one is an horizontal force. We would likely need to use the "velocity" variable if both "AddForces" were for the same axis. If we now save everything and go back to Unity and enter Play Mode, our Player should be floating, meaning that our Capsule Collider won't be touching the ground. We can also go up and down Slopes without any Jumps. Furthermore, it's now possible for our Player to climb the other Ramp Step without a problem. Of course, we can see our feet entering the Ground when going up the Ramp, but Genshin Impact likely fixes this using "IK", or "Inverse Kinematics", which we won't be covering. Regarding what I've previously said about our "FloatRayDistance", which is "2f", this ray distance is also used when the Player needs to go down. This means that if we Jump or Fall down something, lets say we fall off the ramp, if the distance from the Center of the Capsule Collider to the Ground is enough to be caught in the Raycast, we'll instantaneously teleport to our desired floating point, as we've added a negative Force to move down. This, however, won't be too noticeable once we add "Falling" in our "Gliding System". With that, we are now able to correctly move on Slopes, so we'll add something else as well. If we take a look at Genshin Impact, the speed of the player is reduced when we enter a Slope. This happens both when going up or down the Slope. So, back into our "Grounded State", in the Raycast if statement, we need to get the angle of the ground. That's easily obtainable by using the "Vector3.Angle" method, which returns the angle in degrees between two Vectors. So type in "float groundAngle = Vector3.Angle();". For our first Vector, we'll pass in the "Ground" facing direction, which we can get through the "hit.normal" variable. For our second vector, we'll pass in our "downwardsRayFromCapsuleCenter.direction". However, this direction is going down, which is the opposite of the ground direction, so, we'll instead do its opposite direction by adding a minus (-) at the beginning. With this value, we can now set our speed modifier to something else depending on the given angle, so call in a new method named "SetSlopeSpeedModifierOnAngle();" and pass in "groundAngle". I'll rename the parameter to be named "angle" instead. We'll now need to create a new property for our Speed Modifier when on Slopes, as we'll want to make it be relative to the current State Speed. To do that, go to the "PlayerStateReusableData" script and when you're here, duplicate the "MovementSpeedModifier" property and name it "MovementOnSlopesSpeedModifier". This will act as a percentage, from "0" to "1". Back to our "Grounded State", we need to check if this angle is at a certain angle and only update our slope speed modifier when that's true. However, while adding an "if statement" is fine, we'll learn something a bit cooler: "Animation Curves". This easily allows us to add different speeds for different angle ranges through the Inspector. To get our Animation Curve, go ahead and open up the "PlayerGroundedData" script. In here, under our Base Speed, create a new "[field: SerializeField] public AnimationCurve SlopeSpeedAngles { get; private set; }". Save it up and go to Unity. Then, open up the second "Inspector" Tab to see our "Player" Scriptable Object. If we open the "Animation Curve", we can see an empty graph-like window with a value from "0" to "1" on the left and "0" to "1" on the bottom. The "bottom" value is considered the "time", while the "left" value is considered the "value". We can read it as "it having a certain value at a given time". What we'll do is convert this "time" into our "angle" and the "value" into our "slope speed modifier", so, it will end up "having a certain slope speed modifier at a given angle". We could leave the "time", or "angle" to be from "0" to "1" for it to be normalized, but that would make it harder to understand angle ranges, so instead, we'll change our "time" to be from "0" to "90". To do that, select the first curve preset at the bottom. Then, right click on the last key and choose "Edit Key". In here, press "Tab" to go to the "Time" field and set it to be "90" instead. Press "Enter" when you're done. Then, press "F" to recenter the Graph. This made it so that our "time", or "angle", will now go from "0" up to "90" degrees. Right now though, our "slope speed modifier" is always "1" at any given "angle". Lets make it so that our "slope speed modifier" is "0.75" when the ground "angle" is within "15" and "55" degrees. To do that, right click in the curve and press "Add Key". Then, right click on the key and edit to be "0.75" on the "Value" and "15" on the "Time". When that's done, press "Enter" and "F" to recenter the Graph. Make sure the graph itself is selected or you'll recenter on the selected key instead. Now, add yet another key and edit it to be at "0.75" for the "Value" and "55" for the "Time". Press "F" again to recenter. Right now our curve is looking like an actual curve, which makes it so that the "slope speed modifier" is actually descending from "1" to "0.75" as the angle increases instead of being either "1" or "0.75". This can be what you desire and is what makes Animation Curves quite powerful, but we want it to only be one of the two values. To do that is thankfully quite simple: We right click on a key and open up the "Both Tangents" dropdown. We then set it to be "Constant". Do the same for the other key. Our values are now either "1" or "0.75" and not something in-between. We do however have another problem: From our angle "55" upwards, it keeps being "0.75" instead of "1". To fix this, we'll edit the second key and set the "Value" to be "1". The problem with this outcome though, is that now our angle "55" is also "1". So, edit it again, and make the "Time" "55.1" instead. This now translates to the following: If the angle is on the range of "0" to "14.9", the speed modifier is "1". If the angle is on the range of "15" to "55", the speed modifier is "0.75". If the angle is on the range of "55.1" to "90", the speed modifier is "1". In Genshin however, we can actually very slowly climb high angled grounds up until a certain angle. After that angle, we'll fall or slide instead. So, swap the "55.1" key value to be "0.4" and then add another key for "65.1" and make its value be "0". Make the "90" degrees key have a value of "0" as well. That's done for our slope modifier so feel free to close the Animation Curve Window and head back to the "Grounded State" Script. In here, we need a way to get the slope speed modifier at the given ground angle. That translates to "get the value of the curve at a given time". Thankfully, we can easily do that by using the Animation Curve "Evaluate" method. So, type in "float slopeSpeedModifier = movementData.SlopeSpeedAngles.Evaluate();" and pass in "angle". We now have our curve value, or "slope speed modifier", so we need to set it up by typing in "stateMachine.ReusableData.MovementOnSlopesSpeedModifier = slopeSpeedModifier;". We are now setting our slope speed modifier using an Animation Curve. We'll also return this value so type in "return slopeSpeedModifier;" and update the return type to be "float". In our "Float" method, get its returned value by assigning it to a new "float" variable named "slopeSpeedModifier". Then, under that, we'll check "if (slopeSpeedModifier == 0f)", we "return;". This makes it so we don't float on Grounds that have an angle that's too high, which makes it so that we can't walk on them and will fall or slide instead. Of course, we still need to multiply our Movement Speed with this new variable, so open up the "PlayerMovementState" Script and go to the "GetMovementSpeed" method. In here, simply add to the multiplication "* stateMachine.ReusableData.MovementOnSlopesSpeedModifier". When that's done, save everything and go back to Unity. Entering Play Mode and going to a slope should now update the speed that our player is moving at depending on the "Ground" Angle. We now have our slopes working so it's  time to start adding the remaining States. We'll be starting with our "Dashing State". To do that, we'll need to first  add an Input Action for our Dash, so open up the docked Input Actions Window. In here, add a new Action and name it "Dash". I'll leave the "Action Type" as "Button". Make sure you remove the "Clamp" and "Invert"  Processors Unity automatically added from our previous Input. I assume it's a bug. For our Binding, I'll bind it to the "Left Shift" Key. We'll also add another binding and  set it to be the mouse "Right Click". This is simply because you can Dash  with any of these 2 keys in Genshin. When you're done setting the  Bindings, feel free to save the asset. Still in Unity, open up the "Grounded"  States Folder in the "Player" Scripts Folder. In here, we'll create a new C# Script,  to which I'll name "PlayerDashingState". Open it up and remove the default methods. Swap the inheritance with "PlayerGroundedState"  instead and generate the constructor. We'll need to cache this State so open up  our "PlayerMovementStateMachine" Script. In here, duplicate one of the properties  and swap it with the "DashingState". Don't forget to initialize it as well. When that's done, go back to  the "Dashing State" Script. When entering this State, we'll  have two possible outcomes: When we Dash while moving, which  is the equivalent of transitioning from a "Moving State" to the "Dashing State", we'll dash towards our movement direction. When we Dash while standing still, which  is the equivalent of transitioning from the "Idling" or the "Stopping States" to the "Dashing State", we'll dash towards the direction that our player is facing. To do that, start by "overriding" the "Enter" method. If our dash comes from a "Moving State", we'll simply update the speed modifier to be a bit higher. If it comes from a "Stopping State", we'll add a force instead, as our player isn't able to move  without pressing a Movement Input Key. Of course, that means that we  need a speed modifier value, which also means that we'll need  to create a Dash Data script. So, back in Unity, go to the States Data  folder and in the "Grounded" folder, create a new C# Script, to which I'll name "PlayerDashData". Open it up, remove the default methods and the  inheritance and make the class "[Serializable]". Then, add the speed modifier by typing in "[field: SerializeField] [field: Range(1f, 3f)] public float SpeedModifier { get; private set; }". I'll also default it to "2f;". Then, go to our "PlayerGroundedData" Script. In here, create a new "[field: SerializeField] public PlayerDashData DashData  { get; private set; }". We can now return to our "PlayerDashingState" script and set the "stateMachine.ReusableData.MovementSpeedModifier = movementData.DashData.SpeedModifier;". We'll be needing more data from this class later  on, so above create a new variable by typing in "private PlayerDashData dashData;". In the constructor, set the "dashData"  to be equal to "movementData.DashData;". Then, swap it in our "Enter" method. This does it for when we're  transitioning from a "Moving State", so for the from a "Stopping State" case, we'll call in a new method named  "AddForceOnTransitionFromStationaryState();". I'll add this method to a new  region named "Main Methods". I'll also add the "Enter" method to  a new region named "IState Methods". In our new method, we only want to add a force  if there was no Input by the time we got into the "Dashing State", so type in "if (stateMachine.ReusableData.MovementInput  != Vector2.zero)". If that's the case, then we can "return;". For the force, we'll add it towards the player  facing direction multiplied by the movement speed. To do that, start by typing in "Vector3 characterRotationDirection =  stateMachine.Player.transform.forward;". We only need our horizontal rotation direction, so now type in "characterRotationDirection.y = 0f;". Here, we'll actually set the force through  the ".velocity" variable of the "Rigidbody". The reason why we'll be doing that is  because we can "Move" while "Dashing". That means that it's possible that our "Move  AddForce" will be called before "Dashing". Even if we were to reset the  velocity before we add this force, it's possible that it will "Reset >  Add Movement Force > Add Dash Force", which again, because we're "Adding",  it would become double the velocity. For some reason, doing the same as in the "Move Add Force"  of getting the "Player Horizontal Velocity" and subtracting it from the "AddForce", only  removed half of the velocity it needed to remove. I couldn't really understand why, which is why I decided to set the  ".velocity" variable right away, which seems to fix our problems, as it  completely overrides the current velocity. So, type in "stateMachine.Player.Rigidbody.velocity =  characterRotationDirection * GetMovementSpeed();". This should be enough to get  the base of our "Dash" working. However, in Genshin, if we dash  twice in a small amount of time, we'll get into a cooldown where  we can't dash for a second or two. We'll be calling this limit  our "consecutive dashes limit". The way they will work is as follows: When we "Enter" our "Dashing State", we'll  save the current time into a variable. Before we save that time, we'll check if the "current time is less than our saved time + a time we've defined", which is the time to be  considered a consecutive dash. If the current time is less than that sum, then it is a consecutive dash so we add  "1" to the consecutive dashes count. Whenever that count is equal to the limit  of consecutive dashes we've defined, we'll disable the Dashing Input for a few seconds. We'll be setting this data in our  "PlayerDashData" script, so open it up again. We'll need 3 new properties so  duplicate the existing one 3 times. We'll name the first one  "TimeToBeConsideredConsecutive" and default it to "1f". We'll also set its range to be from "0f" to "2f". We'll name the second one "ConsecutiveDashesLimitAmount" and make it an integer. We'll default it to "2" and make the range be from "1" to "10", without the "f", as it's no longer a "float". For the third one, we'll name  it "DashLimitReachedCooldown" and default it to "1.75f". We'll also change the range  to be from "0f" to "5f". When that's done, head back to  the "PlayerDashingState" script. We'll start by creating our  saved time variable so type in "private float startTime;". Then, we'll create one to count how many  consecutive dashes we've done so type in "private int consecutiveDashesUsed;". In our "Enter" method, call in a new method  we'll create named "UpdateConsecutiveDashes();". I'll add this method to the "Main Methods" region. Inside, we'll start by checking if the Dash  we're in is a consecutive dash, so type in "if (IsConsecutive())" and generate the method. Inside, we'll "return Time.time", which is the current game time, " of when we've entered the previous dash, "+ dashData.TimeToBeConsideredConsecutive". So, if we entered the current "Dashing State"  not too long after our previous "Dashing State", we'll consider the current  "Dash" a "Consecutive Dash". That's done, so in our  "UpdateConsecutiveDashes" method, we'll add the not (!) operator to the if statement and reset our dash count if  it's not a consecutive dash. We do that by typing in  "consecutiveDashesUsed = 0;". After that, outside of the if statement, we'll add "1" to the used dashes count  by typing in "++consecutiveDashesUsed;". We now need to check if our used dashes count  is equal to our dash limit amount, so type in "if (consecutiveDashesUsed ==  dashData.ConsecutiveDashesLimitAmount)". If that's true, we'll need to  reset our used dashes count. To do that, type in "consecutiveDashesUsed = 0;". This is likely not necessary if our cooldown is  higher than the "TimeToBeConsideredConsecutive", but this makes it a safe reset. We'll also need to disable our  Dash Input for a few seconds. Unfortunately, it doesn't  seem like the new Input System has a built in way of disabling an Input for an amount of seconds so we'll need to do it ourselves. We'll do that in the "PlayerInput" Script. In here, we'll create a new "public  void" method named "DisableActionFor()" and pass in an "InputAction" named  "action" and a "float" named "seconds". Don't forget to import the necessary  namespace for the "InputAction" type, which is the type of our "Movement"  Input, "Dash" Input, etc.... Simply put, in this method, we'll disable  an action for an amount of seconds. We could add a list of Inputs that  we wanted to disable and then iterate through that list in the "Update" method to see if we should enable any again, but we'll instead use a Coroutine. This is simply because in this  use case it seems better for me to use a Coroutine than to loop a list of disabled Actions every Update call. So, under our method, create  a new Coroutine by typing in "private IEnumerator DisableAction()"  and pass in the same parameters as above. Import any necessary namespace  for the "IEnumerator" type. Inside, we'll disable the action, wait  a few seconds and then enable it again. To do that, simply type in "action.Disable();". Then, type in "yield return  new WaitForSeconds(seconds);". And then, we enable the action again  by typing in "action.Enable();". That's all we need to do, so in our public  method, we'll call this Coroutine by typing in "StartCoroutine(DisableAction(action, seconds));". In case you didn't know, Coroutines can  only be called in a MonoBehaviour class, which is why we're doing it here. With that done, go back to the  "PlayerDashingState" Script. We can now call in  "stateMachine.Player.Input.DisableActionFor();" and pass in the action of  "stateMachine.Player.Input.PlayerActions.Dash" and the seconds of  "dashData.DashLimitReachedCooldown". All that's left to finish this up is  to set our "startTime" variable value. We'll do this after we "Update the consecutive  dashes" because we need to know the "startTime" of the last "Dash" and not the current one. If we did set it before we check  if it's a "Consecutive Dash", then we would be comparing the  current passed time with itself, which would always lead to the same result. So, in our "Enter" method, right after we  call the "UpdateConsecutiveDashes();" method, type in "startTime = Time.time;". We're now finished adding consecutive dashes but we of course still need to add the  transitions to and from this State. We'll start by adding the  transitions to the "Dashing State". If we take a look at our "Movement System States", the States that transition to the "Dashing State" are: "Idling", all "Moving States", all "Stopping  States" and the "Light Landing State". In other words, every "Grounded  State" besides the "Dashing State". The reason why the "Dashing State"  doesn't transition to itself is that in Genshin if we try to dash, we can actually only transition when we enter the  "Sprinting State" or the "Hard Stopping State". It's a bit hard to understand  it as the "Dashing State" only happens for quite a small period of time, but if you keep pressing "Shift" on that  period of time, it will not "Dash" again. So, knowing that, lets go to  our "PlayerGroundedState". Then, in the "AddInputActionsCallbacks" method, call in "stateMachine.Player.Input.PlayerActions.Dash.started  += OnDashStarted;". I'll also remove the callback in the  "RemoveInputActionsCallbacks" method. When that's done, I'll make the  method be "protected virtual" and add it to the "Input Methods" region. I'll also rename the parameter to "context". Inside, we'll simply update the  state to be our Dashing State, so type in "stateMachine.ChangeState(stateMachine.DashingState);". Of course, right now this also adds  the callback to the "Dashing State", which is why we've made this a "virtual" method, so open up the "Dashing State" again and  add a new region named "Input Methods". In here, "override" the "OnDashStarted"  method and leave it empty. Regarding the "Dashing State" transitions to other States, if we take a look at our "Movement System States": At the end of the Dashing Animation, we can transition to the "Sprinting State"  if we keep holding the Movement Input Key or to the "Hard Stopping State" if  there was no Movement Input Key pressed. We can also transition to the "Jumping State". Of course, we still don't have our "Stopping States" so we'll instead transition to the "Idling State" and won't yet transition to the "Jumping  State" either as we haven't done it yet. However, as I've just said, these transitions  will happen at the "end" of the "Animation". The way we'll be doing that is by using  something called "Animation Events". Simply put, we can add an event at a certain  frame of the Animation in the "Animator" tab and call a specific method for when  our Animation enters that frame. That means that we won't actually be able to test our transitions to those  States until we add Animations, but we can at least start creating the methods  that we'll need to call in our Animation Events. To do that, we'll add 3 new methods to our  States, so open up the "IState" Interface. The methods we'll be adding are: "public void OnAnimationEnterEvent();", which we can use for things like "make the player immune to damage when the animation enters its first frame". and "public void OnAnimationExitEvent();", which we can use for things like "make the player vulnerable to damage again when the animation enters its last frame". In Genshin, an example of this would  be the Characters Ultimate ability, which makes the character immune to  damage when the animation is playing. For our last one, we have the "public void OnAnimationTransitionEvent();", which we can use for transitioning to other  States when the animation enters a certain frame. An example of this would be our Dashing State, which is the reason why we're  adding these events right now. When that's done, open up the  "PlayerMovementState" script. In here, implement the 3 new methods we've added. I'll add them at the end of the "IState  Methods" region and also make them "virtual". While we'll only be using  them when we add Animations, I'll also open up the "StateMachine" script. In here, I'll create a new method by typing  in "public void OnAnimationEnterEvent()" and call in  "currentState?.OnAnimationEnterEvent();". Then, I'll duplicate this method twice and  swap "Enter" with "Exit" for the first one and then "Enter" with  "Transition" for the second one. When that's done, we can add the  transition to our "Dashing State". To do that, open up the  "PlayerDashingState" Script. In the "IState Methods" region, start by "overriding" the  "OnAnimationTransitionEvent" method. Inside, we'll check "if (stateMachine.ReusableData.MovementInput  == Vector2.zero)", we'll then transition to  our "Hard Stopping State". Of course, we don't yet have it so lets transition  to the "Idling State" instead by typing in "stateMachine.ChangeState(stateMachine.IdlingState);". We can then "return;". If that isn't the case and we have  something in our "MovementInput" variable, which means we're pressing a Movement Key, we  need to change to the "SprintingState" instead, so copy the line above and  paste it under the if statement. Then, swap the "IdlingState"  with "SprintingState". There is one last thing we need to do and that's to "override" the "OnMovementCanceled" callback  method and leave it empty. This makes it so we don't go to the "Idling State"  if we press and release a Movement Input Key. Our "Dashing State" should now be finalized. Do note though, that it will only completely  work whenever we add Animations to our System. But, if we enter Play Mode, we  should at least be able to dash once. Of course, it will never  leave the "Dashing State", which has an higher speed modifier as well. With our "Dashing State" done, we can now do our next State, which will be the "Sprinting State". This State has a few things to consider, but before we go ahead and talk about them, lets first see how can we enter this State. We enter the "Dashing State" by pressing "Shift" or "Right Click" and then at a certain frame of its animation we transition to the "Sprinting State". The "Sprinting State" will make the player go faster for a while but only keeps sprinting if we didn't stop "holding" the "Shift" or "Right Click" keys for a while. Lets look at an example in Genshin where we are running. While we're doing that, we press on the "Shift" key to "Dash" and then unpress it right away. The "Dashing State" will still enter the "Sprinting State" and the player will run faster for a second or two before the "Sprinting State" transitions back to the "Running State". This is because we haven't held the "Shift" key long enough for our Player to keep "Sprinting". Lets take a look at the same example, but we now don't let go of the "Shift" key for a while. Our Player will now keep on "Sprinting". This stays true even if we let go of the "Shift" key, as we've held the key long enough already. Thankfully, the new "Input System" has an "Hold" Interaction that only calls the "performed" action once we've held the Key for a desired amount of time, which makes this system quite simple to achieve. Of course, we should start by creating our Sprint Input. To do that, open up the docked "Input Actions" Window. We'll add a new Action named "Sprint". I'll leave it with the "Button" Action Type as we don't need to continuously read its value. In the "Properties" area, we'll add the "Hold" Interaction. We should now get a "Press Point" and an "Hold Time". I don't really know what the "Press Point" is, but I assume it's how much force we need to apply to a key or button for it to be considered pressed. We'll leave it with the default value. The "Hold Time" is what we need, which is how long do we need to press the key for it to be considered pressed, which translates to how long does it need to be pressed to call the "performed" action. We'll untick the "Default" from this one and set the amount of time to be "1" second. For our "Binding", we'll listen for the "Left Shift" key and then add another binding for the mouse "Right Click". When that's done, feel free to "Save the Asset". Then, open up the "PlayerSprintingState" Script in the "Moving" States folder. We'll start by setting the speed modifier for this State, so "override" the "Enter" method. I'll add it to a new region named "IState Methods". We need a value for our Speed Modifier, so back in Unity, go to the "Grounded Moving" State "Data" folder and create a new C# Script named "PlayerSprintData". Open it up and remove the default methods and the "MonoBehaviour" inheritance. We'll also make it "[Serializable]". Then, create a new "[field: SerializeField] [field: Range(1f, 3f)] public float SpeedModifier { get; private set; }". I'll also default it to "1.7f;". Next, open up the "PlayerGroundedData" script. Duplicate the "RunData" property and swap the "Run" with "Sprint" instead. When that's done, go back to the "Sprinting State" script. Lets start by creating a variable to hold our data by typing in "private PlayerSprintData sprintData;". Then, in the constructor, type in "sprintData = movementData.SprintData;". Next, in our "Enter" method, we set the speed modifier by typing in "stateMachine.ReusableData.MovementSpeedModifier = sprintData.SpeedModifier;". Now, this is basically it for our "Sprinting" logic and the rest is mainly transition logic. Like we've seen before, if our "Sprint" Key isn't held enough time, our "Sprinting State" will go to the "Running State". To know if our Key was held enough time, lets go to our variables and create a new "private" variable of type "bool" named "keepSprinting;". This variable will start as false, but if our "Sprint performed" action is ever called, it will become "true", as it means that the Key was held long enough, so we need to keep sprinting. So, add a new region named "Reusable Methods". In here, start by overriding the "Add" and "RemoveInputActionsCallbacks" methods. Then, add a new callback by typing in "stateMachine.Player.Input.PlayerActions.Sprint.performed += OnSprintPerformed;". Don't forget to remove the callback as well. I'll add this method to a new region named "Input Methods". I'll also update the parameter name to be "context". Inside, we'll simply set the "keepSprinting" variable to "true". The way we'll transition to the "Running State", in case this variable keeps on being false, is by checking if this variable is false and if a certain amount of time has passed on the "Update" method. This certain amount of time passed is that 1 or 2 seconds we've seen in our example before it transitions into the "Running State". So, in our "IState Methods" region, "override" the "Update" method. In here, type in "if (keepSprinting)", we "return;", as that means we don't want to transition to another State. For our time, we need two variables: One for when have we entered the "Sprinting State" and the other one for how long can we stay here before transitioning. So, above in our variables, create a new "private float" named "startTime". Then, in the "Enter" method, type in "startTime = Time.time;". For the other one we'll do it in our Data Class, so open up the "PlayerSprintData" Script. In here, duplicate the speed modifier property and name it "SprintToRunTime" and default it to "1f". We'll also make the range be from "0f" to "5f". When that's done, go back to our "PlayerSprintingState" Script. In the "Update" method, under our current if statement, we'll check "if (Time.time If this is the case, it means not enough time has passed for us to transition to the "Running State", so we need to "return;". Otherwise, we'll call in a new method named "StopSprinting();". I'll add this method to a new region named "Main Methods". In here, we'll either transition to the "Running State" or the "Hard Stopping State", just in case we've entered this method at the same time that we've stopped any movement input, which will be extremely rare but can still happen. Of course, we don't have our "Stopping States" yet, so we'll transition to the "Idling State" instead. To do that, type in "if (stateMachine.ReusableData.MovementInput == Vector2.zero)", then, we transition to the "Idling State" by typing in "stateMachine.ChangeState(stateMachine.IdlingState);". We of course "return;" right after. Otherwise, we'll need to transition to the "Running State", so copy the line above and swap "IdlingState" with "RunningState". That's it for our transition from "Sprinting" to "Running". However, there is yet another use case. We've seen us going from "Running" to "Sprinting" to "Running", but we haven't yet seen what happens when we go from "Walking" to "Sprinting" to "Walking". What happens is a bit weird but likely the way they've decided to "smooth" the transition. If we take a look at a Genshin Impact example, we can see that our "Sprinting" still changes to the "Running State" but then, our "Running State" changes to the "Walking State" after a small amount of time. We'll add this logic of swapping to our "Walking State" in the "Running State", so open up the "PlayerRunningState" Script. The logic will be quite simple. If we entered the "Running State" while the "ShouldWalk" property was true, then it means we entered the State through the "Sprinting State". This is because if that was not the case, we should've entered the "Walking State" instead, as our "ShouldWalk" property is set to true. So, start by "overriding" the "Update" method. In here, type in "if (!stateMachine.ReusableData.ShouldWalk)", we can "return;", as it means we should be running, as the "ShouldWalk" property is set to false. Otherwise, after an amount of time passes, we'll need to change the State to the "Walking State". The logic is much like our "Sprinting" to "Running" logic. So above, we'll create a variable for the start time by typing in "private float startTime;". Then, in the "Enter" method, set the "startTime" to be "Time.time;". We now need the time it takes to transition. We'll do that in the "PlayerSprintData" as that's the reason why we need this logic here, so open it up. In here, duplicate the "SprintToRunTime" property and change its name to "RunToWalkTime". I'll default it to "0.5f" and update the range to be from "0f" to "2f". Feel free to do this in the "PlayerRunData" if you prefer it that way. We can now go back to the "Running State". In here, we'll create a variable for our Sprint Data so type in "private PlayerSprintData sprintData;" and then in the Constructor, type in "sprintData = movementData.SprintData;". Then, in the "Update" method, we'll type in "if (Time.time we "return;". Otherwise, we can call in a new method named "StopRunning();". I'll add this method to a new region named "Main Methods". We'll do the exact same as we did in our "Sprinting State" here but transition to the "Medium Stopping State" instead, which will need to be our "Idling State" for now, so type in "if (stateMachine.ReusableData.MovementInput == Vector2.zero)" If that's the case, we change the state to be the "IdlingState". We should also "return;" right after. Otherwise, we copy this line and swap the "IdlingState" with "WalkingState" instead. When that's done, go back to the "PlayerSprintingState". We'll still need to do one thing here and that's setting our "keepSprinting" variable to "false" whenever we exit the "Sprinting State", as otherwise it'll always keep on sprinting when we come back to this State, because we're caching in our States and not initializing new ones, meaning our variable data will remain intact. So, in the "IState Methods" region, start by "overriding" the "Exit" method. In here, simply type in "keepSprinting = false;". And that's all we need for our "Sprinting State". We can also transition to the "Jumping State" from the "Sprinting State", but we haven't done it yet so we can't do it for now. We can't really test it either as we don't have Animation Events, but it should be working. If you wanted to try it out, you could always change from the "Dashing State" to the "Sprinting State" after a certain amount of time has passed instead of it being on the Animation Event. With our "Dashing State" and "Sprinting State" done, we can now step ahead into our "Stopping States". These are the States that will be used whenever  we stop pressing our "Movement" Input Keys and will represent the different stopping motions. Thankfully, these won't need any Input Action themselves as all we need to know is if the  "Movement" Input Action was "canceled". Of course, we still need to create their State Scripts. So, head over to the Player  Scripts "Grounded" States folder. In here, create a new folder named "Stopping". Inside of this new folder, create 4 new C# Scripts: "PlayerStoppingState", "PlayerLightStoppingState", "PlayerMediumStoppingState" and "PlayerHardStoppingState". Start by opening up the  "PlayerStoppingState" Script. Remove its default methods and swap the inheritance with "PlayerGroundedState" instead, generating the necessary constructor. When that's done, go to the other 3 "Stopping States" and do the same but inherit from  the "PlayerStoppingState" instead. Then, open up the  "PlayerMovementStateMachine" Script and cache the 3 new "Stopping States": "LightStoppingState", "MediumStoppingState" and "HardStoppingState". Initialize them as well. Head back to the "PlayerStoppingState" Script when you're done. The way "Stopping" works in Genshin  is by slowly coming to an halt and not by instantaneously "Stopping". In other words, the Player starts decelerating  the moment we stop pressing a "Movement" Key, or, at the end of the "Dashing State" animation if we didn't press the "Movement" Key in the first place. Now, all that changes between the "Stopping States" logic is at what force the Player will decelerate. Because of that, we can add the deceleration  logic in our "PlayerStoppingState" Script and individually set the force in each State. We'll start by actually setting the speed  modifier by "overriding" the "Enter" method and typing in "stateMachine.ReusableData.MovementSpeedModifier  = 0f;". Although likely not necessary due to  a few transitions we'll be adding, this makes it so that we can't move  while in our "Stopping States". I'll add this method to a new  region named "IState Methods". For our Deceleration, we'll be creating a  method in our "PlayerMovementState" Script as we'll later on need to decelerate  the Player in our "Swimming System", so feel free to open it up. We'll add it in our "Reusable Methods"  region, so when you're there, type in "protected void DecelerateHorizontally()". Remember that we only need to Decelerate  our Player in the Horizontal Axis. We'll be doing that by  using the "AddForce" method. Also remember that because  our speed modifier is 0, the Move method "AddForce" won't be called, which means we can call this AddForce here without any problems even if it is a Force in the same axis. However, we want our Player to slowly decelerate and not for it to have an  instant change on its velocity, so we can't or shouldn't use the  "VelocityChange" Force Mode here. If we take a look at the same image  we've seen previously made by @nothke, we can get our desired  ForceMode by knowing two things: We want our Player to slow down overtime, which means we want our Force to depend on how much time has passed, or in other words, to be "time dependent". Not only that, we don't want our Player  mass to have any matter on the deceleration, so we know that our Force is "mass independent". This leads us to the "Force  Mode" known as "Acceleration". Of course, we don't want to "Accelerate"  our Player but to "Decelerate". The way we do that is simple: We'll simply slowly accelerate our Player towards its opposite direction, which we can get from its current velocity. Of course, we'll only add this Force  while the Player velocity is over 0 or over a close enough value to 0. This is because if we didn't  check for our velocity, we would keep Decelerating the Player  even if we had already stopped. Of course, this means that we  would start moving backwards. Not only that, but because we're using Physics, we can't expect our velocity to reach  exactly "0" when we're adding Forces, so we'll instead check for a  bit of an higher value than "0". Of course, even if we do stop  adding more deceleration forces, the forces we've added before will still be there. Physics of course takes care of slowing it down, but it does so slowly if there are no collisions, so our Player would slide for a while. We could reset the player velocity once we've  come to the point where we're almost at "0", but because these States will transition to the  "Idling State" at the end of their animations, we'll actually not need to  reset the Player Velocity here, as we already reset the Player Velocity  when we enter the "Idling State". With that in mind, in our "Decelerate"  method, start by typing in "Vector3 playerHorizontalVelocity  = GetPlayerHorizontalVelocity();". Then, we'll add a force by typing in "stateMachine.Player.Rigidbody.AddForce();" and pass in "-playerHorizontalVelocity", which gets the opposite direction of our velocity and pass in "ForceMode.Acceleration"  for our ForceMode. Note that this "ForceMode"  is multiplied by "deltaTime", as it's a "time dependent" Force Mode. Of course, we want to set how fast  we want to decelerate our Player, so we'll need to multiply our horizontal velocity with some other value that we'll  set in each State individually. To do that, start by opening up the  "PlayerStateReusableData" Script. In here, duplicate the last  speed modifier property and rename it to "MovementDecelerationForce". Then, go back to the "PlayerMovementState"  Script and multiply the horizontal velocity with "* stateMachine.ReusableData.MovementDecelerationForce;". With that done, we now need to create the necessary data property to be able to  set this force through the Inspector. Before we do that though, we'll  first create another method. This method will simply tell us if  the Player is Moving Horizontally, because again, we'll only Decelerate  our Player if it isn't Moving. So, under our "DecelerateHorizontally"  method, type in "protected bool IsMovingHorizontally()" and pass  in "float minimumMagnitude = 0.1f" as a parameter. The parameter is required because we'll know if the Player is Moving by checking the magnitude  of its horizontal velocity. The "magnitude" of a Vector is simply the distance between the Vector origin (0,  0, 0) and the Vector Point. In this case, it will be the distance  between the origin and our velocity. If the distance is "0" or close to "0", then it means we aren't moving or  are almost completely stopping. This is mostly so that we don't check  if the "x" or the "z" is over "0" and can instead check one variable only. So, type in "Vector3 playerHorizontalVelocity  = GetPlayerHorizontalVelocity();". However, this velocity comes with an "y" value. Even when this "y" is "0", it  changes our magnitude results. This is because we're checking for a  Point, so "0" is part of its coordinate. To fix that, create a new "Vector2"  named "playerHorizontalMovement" and set it to be "= new Vector2(playerHorizontalVelocity.x, playerHorizontalVelocity.z);". Then, "return playerHorizontalMovement.magnitude  > minimumMagnitude;". These are all the methods we'll need, so now  go back to the "PlayerStoppingState" Script. In here, we'll do both the "Is Moving" check and  the Deceleration in the "PhysicsUpdate" method, so override it in the "IState Methods" region. Then, start by checking "if  (!IsMovingHorizontally())". In case this is true, meaning that  we aren't moving, we can "return;". Otherwise, we can call in our  "DecelerateHorizontally();" method. This takes care of Decelerating our Player. However, we still aren't setting the  Force we want to Decelerate with. To do that, lets head back into Unity and go  to the Player "Grounded" States Data folder. In here, create a new folder named "Stopping". Inside, create a new C#  Script named "PlayerStopData". Open it up and remove the  default methods and inheritance. We'll make it a "[Serializable]" class. We'll be creating 3 Forces, one for each  "Stopping State", so start by typing in "[field: SerializeField] [field: Range(0f, 15f)] public float LightDecelerationForce { get; private set; }". Then, duplicate this line twice and swap  "Light" with "Medium" and then with "Hard". I'll default the "Light" Force to "5f", the "Medium" Force to "6.5f" and the "Hard" Force to "5f" as well. When that's done, open up the  "PlayerGroundedData" Script. In here, add a new property by typing in "[field: SerializeField] public PlayerStopData StopData { get; private set; }". That's all the data we'll need so lets now set the Deceleration Force  Property to its corresponding values. Start by opening up the "LightStoppingState". Here, "override" the "Enter" method and set the "stateMachine.ReusableData.MovementDecelerationForce" to be  "movementData.StopData.LightDecelerationForce;". I'll add this method into a new  region named "IState Methods". When that's done, copy this whole region and  open up the "PlayerMediumStoppingState" Script. In here, paste the region and swap the "Light"  force with the "MediumDecelerationForce". Then, do the same in the  "PlayerHardStoppingState" Script. That's all we need for our Deceleration logic, so we'll now take care of the  transitions to and from these States. We'll start with our transitions to these States  by taking a look at our Movement System States. As we've seen before, we had multiple States  that transitioned to a "Stopping State" but that we've decided to transition  to the "Idling State" instead while we didn't have them. Those states are: "Walking",  "Running", "Sprinting" and "Dashing". The "Walking State" will transition  to the "Light Stopping State". The "Running State" will transition  to the "Medium Stopping State". The "Sprinting" and "Dashing States" will  transition to the "Hard Stopping State". Start by opening up the  "PlayerWalkingState" Script. In here, we'll "override" our "OnMovementCanceled"  method in the "Input Methods" region. Remove the base method call and transition  to the "Light Stopping State" by typing in "stateMachine.ChangeState(stateMachine.LightStoppingState);" . Then, copy this method and go to  the "PlayerRunningState" Script. Paste the copied method into  the "Input Methods" region and swap the "LightStoppingState"  with "MediumStoppingState" instead. When that's done, go to the  "PlayerSprintingState" Script. Paste the copied method into  the "Input Methods" region again and swap it to the "HardStoppingState" instead. The "Dashing State" will also  transition to the "HardStoppingState" so open it up and in our  "OnAnimationTransitionEvent" method, swap the "IdlingState" with  "HardStoppingState" instead. That's it for the States that  transition to the "Stopping States", so we now need to set the State  transitions from the "Stopping States". Taking a look at our "Movement  System States" again, our "Stopping States" can transition to: "Idling", "Walking", "Running",  "Dashing" and "Jumping". Whenever any "Stopping State"  animation reaches its last frame, we'll transition to the "Idling State". Whenever we press a "Movement" Key again, we'll transition to the "Walking" or "Running State", depending on the "Walk Toggle" value. We'll also be able to transition to the  "Dashing" and "Jumping States" as usual, although we won't be able to add the  transition to the "Jumping State" yet, as we don't really have it. With that in mind, start by opening  up the "PlayerStoppingState" Script. In here, in the "IState Methods" region, we'll "override" the  "OnAnimationTransitionEvent" method. Inside, we'll transition to  the "Idling State" by typing in "stateMachine.ChangeState(stateMachine.IdlingState);". This method override makes our  "OnMovementCanceled" method useless and although it should never be called because  not only will we only enter a "Stopping State" when we have already Stopped Input but also add transitions to the "Moving States", lets "override" it and leave it empty. I'll also add it to a new  region named "Input Methods". Now, previously we've talked about going  to "Moving States" through the callback and the "Update" method for situations  where we could press the "Movement" Input in a State like "Jumping", where the callback won't be called as  the Input had already been pressed. In our "Stopping States" case, we'll  never enter them from the "Jumping State". We'll also never enter our "Stopping States"  until we release our "Movement" Input Keys. This simply means that we'll never  be able to enter a "Stopping State" with our "Movement" Input Keys already pressed. For that reason, we can add a callback  to our "Movement" "started" action instead of using the "Update" method. To do that, add a new region  named "Reusable Methods". In here, "override" the "Add" and  "RemoveInputActionsCallbacks" methods. Inside, type in "stateMachine.Player.Input.PlayerActions.Movement.started  += OnMovementStarted;". Don't forget to remove the callback as well. When that's done, I'll add the new  method to the "Input Methods" region. I'll also rename the parameter to "context". Inside, we can simply call the  "Grounded State" "OnMove();" method, which takes care of transitioning  to the "Moving States". For our "Dash" transition, it's already set up in the "Grounded  State" so we don't need to add it here. That means that we're now  transitioning to the "Idling State", the "Moving States" and the "Dashing State". Of course, the "Jumping State" will be added  whenever we add "Jumping" to our System. However, there's one more thing to keep in mind. In Genshin, in the "Light"  and "Medium Stopping States", we are able to transition to the  "Walking State" at any given moment. However, that doesn't seem to be the  case for the "Hard Stopping State". Whenever we try to do it, the Player  doesn't start Walking or Moving at all, meaning that we can only  transition to the "Running State". To copy this situation, open up the  "PlayerHardStoppingState" script. In here, we'll simply  "override" the "OnMove" method, which we've made "virtual"  in our "Grounded State". I'll add it to a new region  named "Reusable Methods". In here, remove the base method call and then check "if (stateMachine.ReusableData.ShouldWalk)" and if that's true, we "return;", as we  can't transition to the "Walking State". Otherwise, we should transition to  the "Running State" by typing in "stateMachine.ChangeState(stateMachine.RunningState);". That's all we need for our "Stopping States", so save it all and go back to Unity. Entering Play Mode, we should now be able to  stop after "Walking", "Running" or "Sprinting" by slowly decelerating instead  of instantaneously stopping. Of course, because we  haven't yet added Animations, our Animation Event is never going to be called, which means we'll never enter the "Idling State". Because of that, our Player will  very slowly slide for a while. We also won't be able to test  our "Dashing" transition yet. With our "Stopping States" added into our System, the only remaining States are the  "Jumping" and "Light Landing States". Before we take a look at those States though, we need to add something else  first: Automatic Rotations. If we take a look at Genshin Impact, if we press a "Movement" Key like "S" when looking forward and unpress it right after, the Player will still finish rotating itself even though we're not pressing on the Key anymore. Genshin offers this automatic  rotation in multiple places: When Dashing, When Stopping, And when Jumping. In the "Dashing State", if  we ever tap a "Movement" Key to change its direction in the middle  of the dashing or even before dashing, the Player will still finish rotating. In the "Stopping States", the player simply finishes rotating towards the direction that  the player was going to move. This means that it actually automatically  rotates when stopping and not when moving, which makes sense as we  already rotate while moving. In the "Jumping State", we  have 2 possible outcomes: If by the time that we "Enter" the "Jumping  State" we are pressing on a "Movement" Key, then the Player will rotate and "Jump"  towards the "Movement Direction" even if we were looking somewhere else. This happens either we leave  the Key pressed or unpress it, as long as it's after we  "Enter" the "Jumping State". If by the time that we "Enter" the "Jumping  State" we are not pressing any "Movement" Key, then the Player will "Jump" towards  its "Facing" or "Forward Direction", which doesn't really need any extra rotation. We'll be adding the first two cases here and then add the "Jumping" case whenever we add "Jumping" into our System, which should be right after this. Thankfully, when we've added  Movement to our Player, we already created reusable methods  that allow us to rotate our Player, so it will be quite simple to add this feature. We'll start with our "Stopping States", so open up the "PlayerStoppingState" Script in the "Stopping" States folder. What we'll do here is quite simple: In the "PhysicsUpdate" method, we'll simply make sure our Player keeps rotating until it reaches the target rotation. We'll use the "PhysicsUpdate" method as  our reusable methods use the Rigidbody "MoveRotation" method, which involves Physics. So, in the "PhysicsUpdate" method,  above the rest of the code, call in "RotateTowardsTargetRotation();". We don't need to check if it  reached the target rotation already because we already do that  inside of the Rotation method. And that's really all we need to do. Whenever we Enter a "Stopping State" now, it will finish rotating even though  we're not pressing on a "Movement" Key. When that's done, open up the  "PlayerDashingState" Script. When "Dashing", we want to  keep rotating in two occasions: When we "Enter" the "Dashing State"  with a "Movement" Key already pressed and when we press a "Movement" Key  while already in the "Dashing State". Now, in the first case, because  we'll be calling our "Move" method, we'll already be rotating our Player. However, it's possible that we  release the key right after, which means that our Player will stop rotating. Because of that, we need to make  sure we somehow keep rotating. For the second case, lets say we "Entered" the "Dashing State" without any "Movement" Key pressed at all and then pressed and unpressed  a "Movement" Key mid-dash. For example, we could go from "Idle" to  "Dash" and press and unpress "S" quite fast. Because we aren't reading for  that change in Input anywhere, our Player won't keep rotating towards  where it should when we release the Key. With that in mind, lets start by  creating a new variable by typing in "private bool shouldKeepRotating;". Then, in our "Enter" method,  after we add the Force, we'll type in "shouldKeepRotating = stateMachine.ReusableData.MovementInput != Vector2.zero;". This will become true if we  are pressing any "Movement" Key or false if we're pressing none. This is just in case we  release the key right after to make sure it keeps rotating regardless. For our second case, we'll need to make this  variable true whenever we press a "Movement" Key, which we can know through the "performed" action. So, add a new region named "Reusable Methods". In here, "override" the "Add" and  "RemoveInputActionsCallbacks" methods. Then, add a new callback by typing in "stateMachine.Player.Input.PlayerActions.Movement.performed  += OnMovementPerformed;". Don't forget to remove it as well. Remember that "performed"  for the "Value" Action Type will be called right after "started"  and every time a new Key is pressed, even if we didn't release the  old one, which is what we want. I'll add this method to the "Input Methods" region and also rename the parameter to "context". Inside, all we need to do is to set the  "shouldKeepRotating" variable to "true". When that's done, we need  to make our Player rotate, so head over to the "IState Methods" region and "override" the "PhysicsUpdate" method. In here, first check "if (!shouldKeepRotating)" and if we shouldn't, we can then "return;". Otherwise, we call in  "RotateTowardsTargetRotation();". That's mostly it, but we currently have a problem. Lets say we are facing forwards and tap "S". This updates our target rotation to be backwards. In the middle of this rotation however, we  press "Shift" to "Enter" the "Dashing State". Because by the time we've "Entered" the "Dashing  State" there was no "Movement" Key pressed, our Player will "Dash"  towards its facing direction. However, at the end of our "Dash", we'll  transition to the "Hard Stopping State", simply because we were not  pressing any "Movement" Key. What happens next is that our "Stopping State"  will rotate towards its target rotation, which is still backwards from  when we've pressed our "S" Key. This happens because we are only  updating our target rotation when we Move and we haven't pressed any "Movement"  Key since the first "S" Key press. Thankfully, this problem is quite simple to solve: Whenever we "Dash", we simply need to update our target rotation to be our Player "Forward Direction". So, in our  "AddForceOnTransitionFromStationaryState" method, type in right after we get our  character rotation direction, "UpdateTargetRotation();". For the direction, pass in  "characterRotationDirection". For the second argument, we'll pass in "false" as we don't want to consider the camera rotation but only the Player "Forward Direction"  or our "Movement Input Direction". There's one more thing  regarding our Dash Rotation, which is at what speed our Player rotates. If we take a look at Genshin, our "Movement" and "Stopping" rotations  should be around "0.14" seconds. This is how I obtained the "0.14"  from our existing "Target Reach Time". However, in both the "Dashing"  and "Jumping" rotations, the rotation takes only about "0.02" seconds. Lets update this rotation reach time by  opening up our "PlayerDashData" Script. In here, after our "Speed Modifier", add a new "[field: SerializeField] public PlayerRotationData  RotationData { get; private set; }". When that's done, go back to  the "PlayerDashingState" Script. In the "Enter" method, we now simply type in "stateMachine.ReusableData.RotationData  = dashData.RotationData;". Of course, we don't still have this property, so generate it by pressing "Alt +  Enter" and choose "Generate Property". Then, go to that Property  and make its set "public". We of course need to set its default value so open up the "PlayerMovementState" Script and in the "InitializeData" method, just  before setting our reach time, type in "stateMachine.ReusableData.RotationData  = movementData.BaseRotationData;". Then, swap the "movementData.BaseRotationData" from our reach time line with "stateMachine.ReusableData.RotationData". When that's done, select these 2 lines and extract them to a new method  named "SetBaseRotationData();". I'll make this method protected and add  it to the "Reusable Methods" region. The reason why we've made this a new  method is because we need to make sure we set our rotation data back when  we "Exit" the "Dashing State", as otherwise our rotation would  keep on being "0.02" seconds. To do that, open up the  "PlayerDashingState" Script. Then, in the "IState Methods"  region, "override" the "Exit" method. In here, call in "SetBaseRotationData();". When that's done, save it  up and head back to Unity. In the second "Inspector" tab, update the "Dash  Data" Rotation "Y" value to be "0.02" seconds. If we enter Play Mode now, our  Player should rotate correctly even when we unpress our "Movement" Keys. The next State we'll be taking care of is the "Jumping State", as we now have everything we need to add it to our System. Before we start doing that though, we of course need to know how "Jumping" works in Genshin. There are several outcomes when it comes to "Jumping", depending on the current Player Input or State. The most basic "Jump" is when "Idling". This will add an upwards force and make the Player "Jump" in the same place, without moving horizontally. The outcome that follows it is "Jumping" while "Moving". This will still add a normal upwards force but also "Jump" towards our "Movement" direction. One thing to note is that Genshin seems to have a different Force for each "Moving State". This "Force" doesn't seem to have anything to do with the "Speed Modifier" or with the velocity that our Player is moving at. Another outcome is when rotating. We've already seen this previously but if we press "Space" when we have a "Movement" Input Key pressed, then the Player will "Jump" towards the "Movement Input Direction". If we "Jump" to the opposite side, then we'll rotate our Player and "Jump" towards that side. We also know from our Automatic Rotation part that it takes "0.02" seconds for this rotation to happen. If we pressed a "Movement" Key and unpressed it right after, if we end up pressing "Space" while the Player is still rotating, then the Player will stop rotating and "Jump" towards its "Facing Direction". This is because the "Stopping States" will have their own Forces. Of course, if we were back to the "Idling State" already, we would "Jump" in the same place. And last but not least, we can "Jump" while on a slope. In Genshin, when on a slope, we "Jump" with a lower force. This is because if we were to "Jump" with our normal force when we're facing a slope, it would be too much of a force and we wouldn't really "Jump" but instead "slide" on the slope. This of course happens when Moving, as we're adding an horizontal velocity towards the slope. Now that we know the possible outcomes of the "Jumping State", we'll start by creating its Input. To do that, head over to the docked Input Actions Window. In here, we'll add a new "Action" named "Jump". If Unity added the "Hold" Interaction to it, feel free to remove it. We can leave the "Action Type" as "Button". For our "Binding", we'll Listen for the "Spacebar" Key. When that's done, feel free to "Save the Asset". Next, we'll create our "Jumping State" Script. To do that, head back to the Player "States" Scripts folder. In here, create a new folder named "Airborne". Inside of the "Airborne" folder, we'll create 2 new C# Scripts: "PlayerAirborneState" and "PlayerJumpingState". Start by opening up the "PlayerAirborneState" Script. In here, remove the default methods and swap the inheritance from "MonoBehaviour" to "PlayerMovementState". Generate the necessary constructor as well. When that's done, open up the "PlayerJumpingState" Script. Remove the default methods and swap the "MonoBehaviour" inheritance with "PlayerAirborneState" instead. Make sure you generate the necessary constructor. We now need to cache the "Jumping State", so open up the "PlayerMovementStateMachine" Script. In here, create a new property for our "Jumping State". Make sure you initialize it as well. With that done, go back to the "PlayerJumpingState" Script. When "Jumping" in Genshin, we aren't able to move, simply meaning there's no air movement like there's when Gliding. We can easily not allow movement in our "Jumping State" by setting our speed modifier to 0, so "override" the "Enter" method. Then, inside, type in "stateMachine.ReusableData.MovementSpeedModifier= 0f;". I'll add this method to a new region named "IState Methods". We can now start thinking on how to add the Jump Force. The first thing we need is the Force that we'll want to add when "Jumping", which changes depending on the current Player State. To create it, head over to the "PlayerStateReusableData" Script. In here, under our other Vector3's Properties, type in "public Vector3 CurrentJumpForce { get; set; }". Now, because we'll be assigning this property value to a temporary variable, we don't really need to do the same thing we did in our other Vector3 properties. This is of course because we'll not actually update the "x", "y" or "z" of this Vector3 but only the one of our temporary variable. With that done, go back to the "PlayerJumpingState" Script. In our "Enter" method, we'll now call in a new method named "Jump();". I'll add this method to a new region named "Main Methods". We'll start by assigning our current force into a temporary variable, so type in "Vector3 jumpForce = stateMachine.ReusableData.CurrentJumpForce;". The reason why we're adding a temporary variable here is because the jump force will need to be changed depending on the slope angle as well as the Jump Direction. For example, lets say we want to add a force of "3" in the Horizontal Axis. "3" will always be a positive force, which will always jump towards the same side, even if we intended to jump in the opposite direction. We'll need to update it to be relative to our desired direction, which we might need to make it become "-3" instead. We'll also need to reduce it when on slopes to be a certain percentage of the original force, which requires us to hold that original force somewhere. By creating a temporary variable, we can update it as we desire without updating the original property value. With that in mind, we'll now add our force by typing in "stateMachine.Player.Rigidbody.AddForce(jumpForce);". We'll also be setting the "Force Mode" to be "ForceMode.VelocityChange", as it should be both time and mass independent. Of course, we need to update our jump force to be able to "Jump" in the direction that our Player is moving and not towards the positive force direction only. Not only that, but currently, if we set a force of "3" while we're only moving in one of the "Horizontal Axis", we'll still jump towards both "Horizontal Axis" as we're adding a force of "3" to both Axis. To solve these 2 problems, we can simply multiply our Jump Force with the Player Forward Vector3. The "Player Forward" is basically our Player normalized direction, which is the equivalent of where our Player is facing. This means that if we're only moving in one "Horizontal Axis", the force of the other Axis will be multiplied by "0", which means we won't "Jump" towards that direction. To be able to do that, just before adding our Force, type in "Vector3 playerForward = stateMachine.Player.transform.forward;". Then, we multiply our "jumpForce.x *= playerForward.x;" and our "jumpForce.z *= playerForward.z;". We don't want to multiply our "y" axis as our player forward direction has no effect whatsoever in our upwards velocity. That's the very base of our Jump done and we can now "Jump" with a given Force. However, before we Jump, we should reset any velocity we have, as we don't want our current Player velocity to impact our "Jump" Force. To do that, above our "AddForce" method, call in "ResetVelocity();". This situation is one of the reasons why our "ResetVelocity" method sets the "velocity" variable right away for an instantaneous change. If we didn't do that, "Jumping" multiple times in a row could make the Player "Jump" more than it should. Again, I'm not entirely sure why that happens, but I assume the order of the "AddForces" is being swapped or the force we were trying to add was now more than the player force at the time that the "AddForce" method was actually called. Of course, we still need to create the data classes to be able to set the corresponding "Jump Forces". To do that, save everything and head back to Unity. When you're there, go to the Player States "Data" folder. In here, create a new folder named "Airborne". Inside, we'll create 2 new C# Scripts: "PlayerAirborneData" and "PlayerJumpData". Start by opening up the "PlayerJumpData" Script. In here, remove the default methods and inheritance and make this class "[Serializable]". We'll be adding one property for each force strength. To do that, start by typing in "[field: SerializeField] public Vector3 StationaryForce { get; private set; }". Then, duplicate this property 3 times. Swap their names to: "WeakForce", "MediumForce" and "StrongForce". When that's done, open up the "PlayerAirborneData" Script. Remove the default methods and inheritance and make this class "[Serializable]". We'll add a jump data property here by typing in "[field: SerializeField] public PlayerJumpData JumpData { get; private set; }". When that's done, open up the "PlayerSO" Script. Duplicate the existing "GroundedData" property and swap the "Grounded" with "Airborne" instead. Once you're done doing that, save it all up and head back to Unity. We'll be setting our forces values so open up the second "Inspector" tab. In the "Airborne Jump Data" area, we'll set the following forces: For the "Stationary Force", we'll set the "y" to a value of "5". For the "Weak Force", we'll set the Vector to "1, 5, 1". For our "Medium Force", we'll set the Vector to "3.5, 5, 3.5". For the "Strong Force", we'll set the Vector to "5, 5, 5". With that done, we now need to assign these values in the corresponding States. If we take a look at Genshin, we can see our Forces being applied to the following States: "Idling", which gets set to the "Stationary Force". "Walking" and "Light Stopping", which get set to the "Weak Force". "Running" and "Medium Stopping", which get set to the "Medium Force". "Sprinting", "Dashing" and "Hard Stopping", which get set to the "Hard Force". Regarding the Forces, they also seem to depend on the size of the selected Character. For example, smaller characters seem to have the same "Medium" and "Strong" "Jumping Force", while taller characters seem to have a stronger "Strong Force". Thankfully, if you ever need to differentiate their Forces like that, you can simply create another Scriptable Object for smaller or taller characters. We will however not do that and just use our existing Player Scriptable Object. Now, sometimes the "Stopping States" used the "Stationary Force" at any given frame and sometimes they use the "Weak", "Medium" and "Hard Forces" at any given frame as well. I couldn't really understand what was the factor that decided which "Force" to use, as it didn't seem to be at a certain frame. To make it easier for us, we'll leave the same "Force" for the whole "Animation" duration. If you did want to set the "Stationary Force" in a certain frame, you could do it by "overriding" one of the "Animation Event" methods and set the "Current Jump Force" to the "Stationary Force" there. Upon further Inspection after recording everything, I noticed it would depend on the last Jump we've made. So if we've "Jumped" while "Idling", the "Stopping State" would "Jump" with that Force. It wasn't the case for every single "Stopping State" as the "Lighter Stopping States" for example, wouldn't transition to the "Stronger Force". It seems to cause a bug sometimes where something like "Dashing" and "Jumping" would cause the "Jump" to use a "Stationary Force" as well, so because I don't find this way of doing it to be correct and mainly because I've already recorded everything, I'll go with my current implementation. If you do want to try to be as close as possible to the actual implementation, you would likely not set the Forces in the "Stopping States". With that in mind, we'll now assign the Forces in the corresponding States. We'll start with the "Idling State", but because we'll need to reference the Airborne Data a few times, start by opening up the "PlayerMovementState" Script first. In here, we'll create a new variable by typing in "protected PlayerAirborneData airborneData;". Then, in the constructor, we type in "airborneData = stateMachine.Player.Data.AirborneData;". When that's done, open up the "PlayerIdlingState" Script. In the "Enter" method, we'll set the "Jump" Force by typing in "stateMachine.ReusableData.CurrentJumpForce = airborneData.JumpData.StationaryForce;". When you're done doing that, copy this line and open up the "PlayerWalkingState" Script. Paste the line we've just copied in the "Enter" method and swap the "StationaryForce" with "WeakForce" instead. Copy the line again and open up the "PlayerLightStoppingState" Script. Paste the line in its "Enter" method. Next, open the "PlayerRunningState" Script. Paste the line in the "Enter" method and swap it with "MediumForce" instead. Copy the line again and open up the "PlayerMediumStoppingState" Script. In here, paste the line in the "Enter" method as well. When you're done doing that, do the same for the "Sprinting", "Dashing" and "Hard Stopping States" but swap the "MediumForce" with "StrongForce" instead. That's it for our Forces so before we add the remaining features we'll actually make it so that we can transition to and from the "Jumping State". This will allow us to test what we have done so far. Taking a look at our "Movement System States", we can transition to the "Jumping State" through the following States: Every "Grounded State". This basically means that we can add the transition to the "Jumping State" in our "PlayerGroundedState" Script. So, knowing that, open it up. In here, in the "AddInputActionsCallbacks" method, found in the "Reusable Methods" region, we'll add a new callback by typing in "stateMachine.Player.Input.PlayerActions.Jump.started += OnJumpStarted;". Don't forget to remove the callback as well. I'll make this method "protected virtual" and add it to the "Input Methods" region. I'll also rename the parameter to "context". To transition to the "Jumping State", we simply type in "stateMachine.ChangeState(stateMachine.JumpingState);". That's it for the transitions to the "Jumping State" so we'll now add the transition from the "Jumping State". For now, because we don't have the "Falling" and the "Landing Systems" in our System, we'll simply transition to the "Idling State" whenever we "Jump" and touch the "Ground" again. The way this transition works is by having a "Trigger Collider" at the feet of the Player waiting for the "OnTriggerEnter" call whenever the collider enters the "Ground" after "Jumping". Knowing that, we need 2 things: A "Trigger Collider" and a way to call the "OnTriggerEnter" method. Lets start with our "Trigger Collider" by going back into Unity. Selecting our "Player" Game Object, create a new Child Game Object and name it "TriggerChecks" or any other name you'd like for colliders that will check for something. Inside of this "TriggerChecks" Game Object, create yet another Child Game Object named "GroundCheck". Adding a collider in a different game object allows us to add a specific layer to it, which means we can control what that specific collider collides with. In the "GroundCheck" Game Object, add a new Component of type "Box Collider". A Sphere would likely be fine as well, but I'll go with a Box. When that's done, make the Collider a "Trigger" by ticking the "Is Trigger" checkbox. Right now though, its center and size aren't the most correct, so edit the "Center" to be "0, 0.1, 0" and the "Size" to be "0.25, 0.2, 0.25". It should now be positioned around our feet and just slightly touching the "Ground". If your "Capsule Collider" Height is still at 100%, make sure this "Collider" is able to touch the Ground by updating its "Center" or "Y Size" if necessary. When that's done, we'll add a Layer to this "Collider" Game Object, so add in a new layer named "GroundCheck". When you're done adding it, go to "Edit > Project Settings > Physics" and in the layers at the bottom make sure you disable every collision for the "GroundCheck" layer besides collisions with the "Environment" layer. This ensures that our "Trigger Collider" can only collide with the "Environment", meaning that other Game Objects such as other Players or Enemies won't call the "OnTriggerEnter" method when being collided with. With our "Trigger Collider" added, we can now add the "OnTriggerEnter" method. To do that, open up the "IState" Script. In here, add in a new "public void" method named "OnTriggerEnter". Unity MonoBehaviour "OnTriggerEnter" method requires a "Collider" parameter so pass in "Collider collider" as the method parameter. Make sure you import any necessary namespace. When that's done, open up the "PlayerMovementState" Script and implement the "OnTriggerEnter" method, making sure you add it to the "IState Methods" region and remove whatever code is inside. Make it a "virtual" method as well. With that done, open up the "StateMachine" Script and create a new method to call the "OnTriggerEnter" method by typing in "public void OnTriggerEnter(Collider collider)". Make sure you import the necessary namespace. Inside, call in "currentState.OnTriggerEnter(collider);". Then, open up the "Player" Script. In here, under the "Start" method, call in the MonoBehaviour "OnTriggerEnter" method. I'll rename the parameter to "collider" instead. Inside, we can simply call in "movementStateMachine.OnTriggerEnter(collider);". All that's left now is to check if we collided with the "Ground" and transition to the "Idling State" if that's the case. To check for that collision, we can either use "Tags" or "Layers". To be honest, I couldn't find which one is the best for these comparisons, so I'll go with "Layers" by using a "LayerMask" to compare them, simply because "Layers" seem to be the most related to Collisions. Unfortunately, we can't or shouldn't really compare a "LayerMask" with the collider "layer" right away and it's a bit bothersome to compare them when you don't understand how it works, but I'll try to explain it as best as I can. Before I explain it though, open up the "PlayerLayerData" Script. This is where our "GroundLayer" reference is in and we'll now add a new method that allows us to compare layers, or more specifically, check if a "LayerMask" contains a certain "Layer". To do that, create a new method by typing in "public bool ContainsLayer(LayerMask layerMask, int layer)". Inside, we simply "return (1 Now, if you've never seen this before or never compared layers before, you're likely completely lost on the meaning of this "return" statement. To be honest, I was too and had no idea what this meant. To be able to fully understand it, lets go bit by bit. We have "LayerMasks" and we have "Layers". If you remember the "Layer" Inspector, you know that we can have up to "32 Layers", some already used by Unity. This is basically a "32 bit bitmask". In "Human Readable Numbers", these "Layers" will be represented from "0" to "31", which is what people often use in their code. However, lets take a look at our "Environment Layer" in "Computer Numbers". Using the Inspector "32 bit bitmask", it would be represented by a bitmask with a "1" at its "7th" position. This is because our "Environment Layer" is the "Layer 6" in the Inspector and because Layers in the Inspector start from "0", the actual position is that number "+ 1". Of course, our "GroundCheck Layer" would have a "1" in the "8th" position instead and so on. So, that's how our "Layers" are represented in the "32 bit bitmask". A "LayerMask" is also a "32 bit bitmask". If you remember from our Inspector, a "LayerMask" can be used to select multiple "Layers". So while it starts with "0"'s for the whole "bitmask", the moment we select one or more "Layers" in the "Inspector", the "32 bit bitmask" would start turning "0"'s to "1"'s in the respective "Layer" position. For example, selecting the "Environment Layer" would have a "1" at the "7th" position. Now, if our "LayerMask bitmask" is looking the same as our Layers "bitmask" with our "Environment Layer" selected, why can't we compare our "Layer" with our "LayerMask"? The reason why is that while "LayerMasks" are represented with "Computer Numbers", Layers are represented with "Human Numbers", simply to make it easier for us to understand them. So, when we compare the "Environment Layer" with a "LayerMask", we're comparing a "Human Number" with a "Computer Number", which won't work out very well. For "Raycast" cases, because we're passing in the "Human Number" to the "layerMask" argument, that "Human Number" would be converted to a "Computer Number" represented by the "Layer" number. For example, if we passed in "6", as that's our "Environment Layer" number, it would be transformed into binary and would be represented by a "32 bit bitmask" with a "1" at its "2nd" and "3d" positions. This is because "6" in binary is represented by "110". That "110", would then be transformed into a "32 bit bitmask" as the "Raycast" requires a "Layer Mask". Of course, this would simply add the remaining bits, or "0"'s, to its left side. If we now compare it with the "LayerMask bitmask", we can see that they do not look the same at all, even though we've passed in the correct "Layer" Number. The fix to all of this is quite simple: We simply need to convert our "Human Number" to a "Computer Number", which is what our weird formula does. Starting from the beginning, our "1" represents the first position of the bitmask. The " Simply put, we tell our bitmask to "shift" an amount of times to the left. So, our "1" that was in our first position, will now shift itself "layer" amounts to the left. If our "Layer" is the "Environment Layer", then it will shift itself "6" times to the left, which means it will now be in the "7th" position. With this, our "Human Number" is now a "Computer Number" and represents the same bitmask that our "LayerMask" represents. This means that comparing them would give us the desired result. However, remember that a "LayerMask" can contain multiple "Layers". If we were to also add in the "GroundCheck Layer", it would now have a "1" in the "7th" and "8th" position, which would now be different than our "Environment Layer bitmask". That's where the "& layerMask" comes in play. The "&" sign is a "bitwise operator" and represents the "AND" operator. This operator will compare all bits in both of our "bitmasks" and return "0" when one of the same positioned bits is "0", or "1" when both bits are "1". Simply put, its keeps the common "1"'s between both "bitmasks". If the returned bitmask has any "1" in it, then it means that if we convert this "bitmask Computer Number" into a "Human Number", it will return a value over "0". For example, in this case, it would be "64", as it's a "1" in the "7th" position. This means that our "LayerMask" contains the passed in "Layer". If the final "bitmask" were to return a bitmask with only "0"'s, then it would mean that the "Layer" wasn't part of the "LayerMask". That's what our "!= 0" is for. Again: If the final "Human Number" is "0", then it means the "LayerMask" didn't contain the passed in "Layer", as there was no "1" in the same bit in both "bitmasks". If the final "Human Number" is "!= 0", then it means the "LayerMask" contains the passed in "Layer", as there was a "1" in the same bit in both "bitmasks". I'll leave a few links in the description if you want to dive deeper into it, but this should be enough for you to understand our use case. With that in mind, we'll also create another method to check if it's a "Ground Layer", so type in "public bool IsGroundLayer(int layer)". Inside, "return ContainsLayer(GroundLayer, layer);". That's all we need here so now open up the "PlayerMovementState" Script. In our "OnTriggerEnter" method, we'll check if the object we've collided with is part of the "Environment" by typing in "if (stateMachine.Player.LayerData.IsGroundLayer (collider.gameObject.layer))". Inside of this if statement, call in a new method named "OnContactWithGround();" and pass in "collider" as an argument. "return;" right after, to make sure nothing else gets called. I'll make the method "protected virtual" and add it to the "Reusable Methods" region. We'll override this method wherever we need something to happen when we enter in contact with the ground, which we don't need in our "Movement State". We can use this method to transition to the "Idling State" in our "Jumping State", but because I know we'll need to transition more times to the "Idling State", or more specifically to the "Light Landing State" whenever we add it, we'll actually add it to the "PlayerAirborneState" Script, so open it up. In here, create a new region named "Reusable Methods". Inside, "override" the "OnContactWithGround" method. Then, transition to the "Idling State" by typing in "stateMachine.ChangeState(stateMachine.IdlingState);". Feel free to remove the "base" method call as well. This should be enough to test our current Jump, so save it all up and go back to Unity. If we enter "Play Mode", our "Jump" should now work and its force should depend on the current Player State. We also cannot move mid-air. However, we still need to finish up a few features from the "Jumping State": Automatic Rotation, Force Modifier when on a Slope, Fixing the Floaty Jump Feeling and Keep Sprinting after Jumping. We'll start with the "Automatic Rotation" so go back to the "PlayerJumpingState" Script. We need to automatically rotate our Player if we've entered the "Jumping State" with a "Movement" Input Key pressed. Not only that, but currently we are always jumping to the Player Facing Direction. This is not what should happen when we press a "Movement" Key and should instead "Jump" towards our "Movement Input Direction" if that's the case. We'll start by creating a variable to know if we should rotate, so above, type in "private bool shouldKeepRotating;". Then, in the "Enter" method, set the "shouldKeepRotating = stateMachine.ReusableData.MovementInput != Vector2.zero;". This makes it so we keep rotating if we have Input. With that, we can now "override" the "PhysicsUpdate" method and inside type in "if (shouldKeepRotating)". If that's the case, we can call in "RotateTowardsTargetRotation();". We're adding it inside of the if statement as we'll have another if statement in this method later on. We should now make it so that the Player "Jumps" towards the "Movement Input Direction" instead of the Player Forward Direction if we entered the "Jumping State" with a "Movement" Key pressed. To do that, go to the "Jump" method. The way we'll do this is by updating our "playerForward" variable value to be the Movement Input Direction instead when we have a "Movement" Key pressed. We can use the "shouldKeepRotating" variable to know if our Movement Input Key was pressed as that's the condition we gave it in our "Enter" method. So, after our "playerForward" variable, type in "if (shouldKeepRotating)". If that's the case, we'll update our "playerForward" to be the Movement Input Direction. However, we actually want the direction to be relative to the Camera, as the "Movement Input" would be in "World Space". We can do this by getting the direction of our "target rotation", which was updated when we "Moved" before "Jumping". We also have already created a method before that gets us a direction given a rotation. So, simply type in "playerForward = GetTargetRotatioDirection();" and pass in "stateMachine.ReusableData.CurrentTargetRotation.y". This is the method that transforms a quaternion with a certain angle into a Vector Direction. Remember that even if we come here from a "Stopping State", we "Enter" them with an updated target rotation already, so we don't need to Update it here. Our "playerForward" variable name no longer makes much sense, so we'll select it and rename it by pressing "F2" or right clicking on it and choosing "Rename" and set its name to "jumpDirection". Of course, like we've seen before, both "Dashing" and "Jumping" have a different reach time, so we need to set that up. To set it up, open up the "PlayerJumpData" Script. In here, create a new property by typing in "[field: SerializeField] public PlayerRotationData RotationData { get; private set; }". When that's done, save it up and go back to Unity. In the second "Inspector" tab, set the "Jump Data" "Y Rotation" value to be "0.02" seconds. Then, go back to the "PlayerJumpingState" Script and create a new variable above by typing in "private PlayerJumpData jumpData;". Then, in the constructor, set it to be "jumpData = airborneData.JumpData;". In our "Enter" method, we can now type in "stateMachine.ReusableData.RotationData = jumpData.RotationData;". We should also not forget to reset this rotation data when "Exiting" so "override" the "Exit" method and call in "SetBaseRotationData();". If we save it all and head over to Unity, entering "Play Mode" should now have our "Jump" working fine. That's great, but we should now make sure we have a lower Jump Force when on Slopes to make sure we don't get into the Slopes when "Jumping", which would make the Player look like it's sliding. This mostly happens on steeper slopes. To do that, head back to the "PlayerJumpingState" Script. We'll do this by casting a "Ray" in our "Jump" method to know if we're on a slope and then update the Forces if that's the case. To do that, before resetting the velocity, type in "Vector3 capsuleColliderCenterInWorldSpace = stateMachine.Player.ColliderUtility.CapsuleColliderData. Collider.bounds.center;". Then, create the "Ray" by typing in "Ray downwardsRayFromCapsuleCenter = new Ray(capsuleColliderCenterInWorldSpace, Vector3.down);". We can now cast our "Ray" by typing in "if (Physics.Raycast(downwardsRayFromCapsuleCenter, out RaycastHit hit))". We need the distance of our "Ray" now, so open up the "PlayerJumpData" Script. In here, type in "[field: SerializeField] [field: Range(0f, 5f)] public float JumpToGroundRayDistance { get; private set; }". I'll default it to "2f". When that's done, go back to the "PlayerJumpingState" Script. Pass in to the "Raycast" "jumpData.JumpToGroundRayDistance". For our "LayerMask", we'll pass in "stateMachine.Player.LayerData.GroundLayer". Then, pass in "QueryTriggerInteraction.Ignore" to ignore "Trigger Colliders". If we hit something with this ray we then need to know if we're on a slope. We'll of course be using the "Vector3.Angle" method again. To do that, type in "float groundAngle = Vector3.Angle(hit.normal, -downwardsRayFromCapsuleCenter.direction);". We now need a way to check for the angles and set a force accordingly, which we'll be doing by using "Animation Curves". However, we'll need to create an "Animation Curve" for both going up and down slopes. This is because that's how it works in Genshin. When going up we update the "Horizontal Forces" to make sure we don't slide in the slope. When going down we don't really need that as there's no ramp in front of us but it seems that they update the "Vertical Force" to be a bit lower. To know whether we're going up or down, we simply need to check the Player vertical velocity. Going up means it's positive, while going down means it's negative. We'll create 2 methods for that so open up the "PlayerMovementState" Script. In the "Reusable Methods" region, head over to the "IsMoving" method. Then, create a new method by typing in "protected bool IsMovingUp(float minimumVelocity = 0.1f)". Inside, we can simply "return GetPlayerVerticalVelocity().y > minimumVelocity;". That's all we need so now duplicate this method and rename it to "IsMovingDown" instead. Then, we'll swap the ">" with " a minus (-) sign before the "minimumVelocity". This makes it so that we can pass in a positive velocity as an argument while the method takes care of it being checked as negative, which is what should happen. When that's done, go back to the "PlayerJumpingState" Script. In the raycast if statement, now check "if (IsMovingUp())" and also "if (IsMovingDown())". We can now start creating and setting the Animation Curves for each situation. To do that, open up the "PlayerJumpData" Script. In here, create a new "[field: SerializeField] public AnimationCurve JumpForceModifierOnSlopeUpwards { get; private set; }". You can give it any other name if you find something better. Then, duplicate the property and swap "Upwards" with "Downwards". When you're done, save everything and head back to Unity. In here, open up the second "Inspector" tab. In the "Jump Data" area, start by opening up the "Upwards" Force Animation Curve. Select the first curve preset. Then, right click on the last key, press "Edit Key" and set its "Time" to be "90". We'll have 2 different ranges: From "20" to "35", the force modifier will be "0.75", or "75%" of the original "Jump Force". From "35.1" to "75", the force modifier will be "0.5", or "50%" of the original "Jump Force". This is because the steeper the slope, the easier it is to look like we're sliding, so we need to make sure we have a lower "Horizontal Force". Knowing that, add in 3 keys: "20", with a value of "0.75". "35.1", with a value of "0.5". And "75.1", with a value of "1". Of course, we don't want this to look like an actual Curve so select all Keys and set "Both Tangents" to "Constant" for all Keys. If all has been done correctly, "20" to "35" should be at "0.75", "35.1" to "75" should be "0.5" and the rest should be "1". With that done, close the current "Animation Curve" and open up the "Downwards Animation Curve". Choose the first curve preset and change the last key "Time" to be "90". We'll only have 1 range here: From "20" to "70", the force modifier will be "0.85", or "85%" of the original "Jump Force". Make sure the "70" key is actually "70.1" and set to "1", for the same reason as all the other "Animation Curves". Set both keys "Tangents" to be "Constant". We should be left with one single area from "20" to "70" set to "0.85" and the rest set to "1". That's all we need for our "Animation Curves" so feel free to close it and head back to the "PlayerJumpingState" Script. In our "IsMovingUp()" if statement, we'll need to apply the forces only on the "Horizontal Axis". To do that, start by typing in "float forceModifier = jumpData. JumpForceModifierOnSlopeUpwards.Evaluate(groundAngle);". Then, apply the modifier by typing in "jumpForce.x *= forceModifier;" and "jumpForce.z *= forceModifier;". That's all we need when moving up, so in our "IsMovingDown" if statement we'll need to apply the forces only for the "Vertical Axis". To do that, copy the first 2 lines of the "IsMovingUp" if statement and paste them in in the "IsMovingDown" if statement. Then, swap "Upwards" with "Downwards" and "x" with "y". This should be all that's necessary to correctly "Jump" on a Slope so save it all and go back to Unity. Entering "Play Mode", we should now be able to "Jump" correctly in both of our Ramps. We should also be "Jumping" differently when going up or down the Ramps. Do note that animations also play a part on how good this looks so you might need to update the values once you add animations into the System. If it isn't working in your own Game Objects, make sure that the Ramp angles are within the ranges we've added, as if they aren't, they will "Jump" normally. I think you could ramp up the "20" to "30" if you'd like, but I made it "20" so that we could test it out in our current Ramp, as "20" shouldn't be steep enough to need the "Jump" force modifier. With that done, you might have also noticed that our "Jump" looks kinda "Floaty". If we take a look at Genshin, if we "Jump", we see that it gets to the top quite fast, not really having much of a floating feeling. And yes, I'm still talking about the "Jump". The reason why the "Jump" feels "Floaty" is because it's taking its time to get to the top and then Gravity also takes its time to create a strong enough negative velocity. The way we'll make it less floaty is by adding a small negative force while the Player is going up. This makes it so that we get to the top faster, which makes it look less floaty. You could also add a small negative force when going down, which would make the Player fall faster, but I'll just add one when going up. To do this, go back to the "PlayerJumpingState" Script. In here, in our "PhysicsUpdate" method, type in "if (IsMovingUp())". If that's the case, we need to add the force to our Player "Vertical Axis". We'll do that using the "Acceleration" Force Mode, as we want to slowly decelerate our Player over time. Of course, we currently only have an "Horizontal Deceleration" method, so we'll need to create one for a "Vertical Deceleration". To do that, open up the "PlayerMovementState" Script. In the "Reusable Methods" region, go to our "DecelerateHorizontally" method and duplicate it. Then, rename it to "DecelerateVertically". Inside, swap the "GetPlayerHorizontalVelocity" with "GetPlayerVerticalVelocity" instead and rename the variable to "playerVerticalVelocity". If you want this "Decelerate" to always "Go Down", then make sure you swap the minus (-) sign to be on the Force instead as otherwise a negative velocity will become positive, which means it would make the player go up. Because I named it "Decelerate", this is what I'll have it do. This of course means that if our Player was going down, "Decelerating" it "Vertically" would make it fall slower and slower over time. With that done, go back to the "PlayerJumpingState" Script and inside of the if statement, call in "DecelerateVertically();". Of course, we need a value for the "Deceleration Force", so open up the "PlayerJumpData" Script. In here, type in "[field: SerializeField] [field: Range(0f, 10f)] public float DecelerationForce { get; private set; }". I'll default it to "1.5f". Back into the "PlayerJumpingState" Script, in the "Enter" method, set the "stateMachine.ReusableData.MovementDecelerationForce = jumpData.DecelerationForce;". And that's all we need to make it less floaty, so save it all up and go into Unity. If we enter "Play Mode", we should now be getting to the top a bit faster. If you wanted the Player to get there faster, you would simply make the force stronger, although, if you make it too strong, it will reduce the Jump Height. We're almost done with our "Jumping State", but there's one more situation to solve. If we take a look at Genshin, if we "Jump" when we were "Sprinting", we will keep "Sprinting" after "Landing". However, this only happens if we have held the "Sprint" key long enough. If we didn't, then we'll go back to the "Running" or "Walking State", depending on the "Walk Toggle" value. We also do not keep sprinting with other States like Gliding or Hard Landing or Rolling. Of course, we still don't have these in our System, but we'll make it so that it only works for the "Jumping State". We'll be doing this by creating a "Reusable Data Property" that defines whether we should keep "Sprinting" or not. Then, in our "OnMove" method, if this property is true, we'll simply transition to the "Sprinting State". The property will only become true when we are "Sprinting" and decide to "Jump". So, to do that, start by opening up the "PlayerStateReusableData" Script. In here, duplicate the "ShouldWalk" property and name it "ShouldSprint". When that's done, open up the "PlayerGroundedState" Script. In the "Reusable Methods" region, go to the "OnMove" method. In here, above everything else, type in "if (stateMachine.ReusableData.ShouldSprint)". If that's the case, then we can transition to the "Sprinting State" by typing in "stateMachine.ChangeState(stateMachine.SprintingState);". At the end, we "return;", as to not call the remaining statements. When you're done doing that, open up the "PlayerSprintingState" Script. In the "Input Methods" region, in our "OnSprintStarted" method, type in "stateMachine.ReusableData.ShouldSprint = true;". The reason why we're adding this here instead of in the "OnJumpStarted" method is because we only want to keep "Sprinting" if the "Sprint" Key was held enough time, which we won't know if that's the case in the "OnJumpStarted" method. You could do it in the "OnJumpStarted" method by checking the "keepSprinting" variable value if you prefer it to be that way. Above in our variables, we'll create a new variable by typing in "private bool shouldResetSprintState;". Then, when we "Enter" the "Sprinting State", we'll set the "shouldResetSprintState" variable to "true". When we "Exit" the "Sprinting State", we'll check "if (shouldResetSprintState)". If we should reset it, then we set our "keepSprinting" to "false" and our "stateMachine.ReusableData.ShouldSprint" to "false" as well. In case we shouldn't reset it, we want to keep this variables as they were, which makes it so we start "Sprinting" once we "Land". Of course, we need to set this variable to "false" somewhere and we'll be doing that when we "Jump". So, in our "Input Methods" region, "override" the "OnJumpStarted" method. Then, before our "base" method call, we type in "shouldResetSprintState = false;". The reason why we are doing this before our "base" method call is because on the base "OnJumpStarted" method we're transitioning to the "Jumping State". This means that our "Sprinting State" "Exit" method would be called before we set the "shouldResetSprintState" variable value to what we want. Now, while everything seems okay so far, lets say we "Enter" the "Jumping State" with the "ShouldSprint" property set to "true". Whenever we come back to a "Grounded State" we'll "Enter" the "Sprinting State" due to the "OnMove" method. However, lets say that while in the "Jumping State" we were to start "Gliding" whenever we add "Gliding" to our System. In Genshin, this makes it so that we no longer go to the "Sprinting State" whenever we "Land". But because we are not resetting it anywhere in our "Jumping" or "Airborne States", right now we'll always come back to "Sprinting". To fix this, open up the "PlayerAirborneState" Script. In the "Reusable Methods" region, create a new method by typing in "protected virtual void ResetSprintState()". Inside, we'll call in "stateMachine.ReusableData.ShouldSprint = false;". Then, call this method in the "Enter" method, which we need to "override". I'll also add the "Enter" method to a new region named "IState Methods". This makes it so that every time we "Enter" an "Airborne State", the "ShouldSprint" property gets reset to "false". Of course, we don't want that to be the case on the "Jumping State" so open up the "PlayerJumpingState" Script and create a new region named "Reusable Methods". In here, "override" the "ResetSprintState" method and leave it empty. Now every "Airborne State" besides the "Jumping State" will reset the "ShouldSprint" property. However, we still have another problem. While we'll indeed keep "Sprinting" after "Jumping" and "Landing", this currently stays true even if we stop pressing the "Movement" Keys. In Genshin, we'll only keep "Sprinting" if we never let go of the "Movement" Keys, or, more specifically, if a "Movement" Key was pressed when we "Landed". If we do "Land" with no "Movement" Key pressed, then when we move again, we'll go back to the normal "Running" or "Walking States". To fix this, open up the "PlayerGroundedState" Script. In here, "override" the "Enter" method. Inside, we'll call in a new method named "UpdateShouldSprintState();". Inside of this method, we'll check "if (!stateMachine.ReusableData.ShouldSprint)" and "return;" if that's the case. We'll also check "if (stateMachine.ReusableData.MovementInput != Vector2.zero)", and "return;" if that's the case as well. If none of these are "true", then we'll reset the "ShouldSprint" property by typing in "stateMachine.ReusableData.ShouldSprint = false;". This makes it so that every time we "Enter" a "Grounded State", if our "ShouldSprint" property is set to "true" and there's no "Movement" Key pressed, we set the "ShouldSprint" property to "false", which means we won't transition to the "Sprinting State". I'll also add this method to the "Main Methods" region. That's all we need to do for our "Jump". Of course, we can't really test this out until we add "Animations", but it should be working fine. Our Player is now able to "Jump" well but if we enter Play Mode and  fall down from one of the Ramps, pressing "Space" while in the  air will make our Player "Jump". This is because right now,  when we leave the "Ground", our Player keeps being in a "Grounded State". We'll solve this problem by  adding a new State: "Falling". While I didn't intend to add "Falling"  until I introduced the "Gliding System", due to a few reasons I've decided to  add it to the first part of the series. Because it wasn't intended to be added now, we haven't yet downloaded any "Falling Animation". To do that, open up "mixamo.com". In here, after logging in, make sure  you select your Game Character Model and then in the Animations  Tab search for "Falling Idle". We'll download it as is with "FBX for Unity"  selected and rename it to "ybot@Fall". In Unity, open up the "Airborne" Animations Folder and drag the downloaded  Animation into this folder. Then, open up the asset and select the  Animation Clip and press "Ctrl + D". We now have our Animation Clip so feel  free to remove the asset we've dragged in. Select the "Animation Clip" and check the  "Loop Time" option to loop the Animation. With that done, lets now take  a look at how Falling works. We have our "Ground Check" Collider that  we already use in our "Jumping State". This simply allows us to go back to the "Idling  State" when we touch the "Ground" again. To do that, we use the "OnTriggerEnter" method. In the "Falling" case, it  will be quite the opposite. If when we're in a "Grounded State", our  Trigger Collider "Exits" the "Ground", then we should start "Falling". When "Falling", the moment we enter  in contact with the "Ground" again, we'll go back to the "Idling State", only  until we don't have our "Landing States". We'll have to care about a few  things regarding our "Falling State" but we'll understand them later whenever  we finish the base of our "Falling". We'll start by creating the "Falling State" Script so head over to the "Airborne" States folder. In here, create a new C# Script  named "PlayerFallingState". Open it up and remove the default methods. Then, swap the inheritance to inherit  from "PlayerAirborneState" instead. Generate the necessary constructor. We'll need to cache this State so open up  the "PlayerMovementStateMachine" Script. Duplicate the "Jumping" Property  and rename it to "Falling" instead. Don't forget to initialize it as well. Because we need the "OnTriggerExit"  method, we'll need to add it to our States, so open up the "IState" Script. In here, duplicate the "OnTriggerEnter" method and rename it to be "OnTriggerExit" instead. When that's done, open up the  "PlayerMovementState" Script. Implement the necessary interface method and place it under our "OnTriggerEnter" method in the "IState Methods" region. Once you're done doing that, open  up the "StateMachine" Script. In here, duplicate the "OnTriggerEnter" method and rename the "Enter"'s to "Exit". Then, open up the "Player" Script. Under the MonoBehaviour "OnTriggerEnter", call in the MonoBehaviour "OnTriggerExit" method. I'll rename the parameter to "collider". Inside, call in  "movementStateMachine.OnTriggerExit(collider);". We now need to check if our  Collider exited the Ground so go back to the "PlayerMovementState" Script. In the "OnTriggerExit" method, we'll start  by checking if we exited the "Ground" by typing in if (stateMachine.Player.LayerData .IsGroundLayer(collider.gameObject.layer))". If it is a "Ground" Layer, we then call in a  new method named "OnContactWithGroundExited();", with "collider" as an argument. Make sure you "return;" right after as well. I'll make this method "protected virtual" and  add it under our "OnContactWithGround" method in the "Reusable Methods" region. You can name it "OnGroundExit" or  something else if you'd prefer, I've just named it like this to be something  close to the "OnContactWithGround" method. We'll now transition to the  "Falling State" with this method but we'll do it in our "PlayerGroundedState", as we can only exit the "Ground"  when we're "Grounded", so open it up. In here, in the "Reusable Methods" region, start by "overriding" the  "OnContactWithGroundExited" method. Inside, call in "stateMachine.ChangeState(stateMachine.FallingState);". We'll extract this line into its own  method, to which I'll name "OnFall". I'll make it a "protected virtual" method as well. This is just because we'll need  to do something with it later on. Now, while this seems fine at first glance, we're currently starting to "Fall" the moment we leave the "Ground". This means that we'll fall even if we have "Ground" underneath  not too far from the Player feet. We don't really want that and don't mind it  being considered a "Grounded State" in this case as the distance is small, so  it won't really look weird. The "Floating Capsule" also helps it looking  better as we'll somewhat teleport down. We'll check for ground underneath using  a "Raycast", so start by typing in "Vector3 capsuleColliderCenterInWorldSpace = stateMachine.Player.ColliderUtility .CapsuleColliderData.Collider.bounds.center;". Then, we'll create our "Ray" by typing in "Ray downwardsRayFromCapsuleBottom", as we'll cast a ray from the bottom  of our capsule and not the center, and assign it to "= new  Ray(capsuleColliderCenterInWorldSpace, Vector3.down);". For us to get our Capsule Collider bottom, we'll get its center and then  add to it half of its height. Of course, we need to somehow get it. We'll cache this half height into  a property so go to the Script where the "CapsuleColliderData"  property is at by "Ctrl" clicking it. Then, "Ctrl + Click" the  "CapsuleColliderData" Class. If you can't do this, just go to  the "CapsuleColliderData" Script. In here, right after our  local center property, type in "public Vector3 ColliderVerticalExtents  { get; private set; }". We've named it "Extents" because the  Collider has a "bounds.extents" variable which returns half of the Collider size and  because we only need half of the height. So, in our "UpdateColliderData"  method, now type in "ColliderVerticalExtents = new Vector3(0f,  Collider.bounds.extents.y, 0f);". We're now caching this every time our Capsule  Collider is changed through the Inspector. With that done, back to our  "PlayerGroundedState" Script, in our "Ray", we now subtract  (-) to the collider center "stateMachine.Player.ColliderUtility .CapsuleColliderData.ColliderVerticalExtents". We need to subtract because we want the  "Capsule Collider" "Bottom" and not the "Top". For our "Raycast", we type in "if (!Physics.Raycast())", which means we found no "Ground", we pass in "downwardsRayFromCapsuleBottom" for the first parameter and "out _" for the second one. This is known as "discards". The "Raycast" method requires a "RaycastHit"  here, but we won't really need it, so instead we are telling C# to use a placeholder  and say there's no value for this variable, which might even make it  so storage isn't allocated. We now need a distance from  our bottom to the ground, so open up the "PlayerGroundedData" Script. In here, duplicate our "Base Speed" and rename it to "GroundToFallRayDistance". We'll also update the range  to be from "0f" to "5f" and default it to "1f". Back to our "PlayerGroundedState" Script, we now pass in "movementData.GroundToFallRayDistance"  to the "Raycast". For our LayerMask, we pass in  "stateMachine.Player.LayerData.GroundLayer". And then "QueryTriggerInteraction.Ignore" as we  don't want to consider "Ground Trigger Colliders". Make sure you move the "OnFall" method  call inside of the if statement. This looks like it's over but we'll  actually need to do yet another check. Lets take a look at those 2  red obstacles we've created. We can see that there's a  small gap in between them. Now, when we exit this Obstacle we'll enter the "Falling State" as the  "Ground" is still a bit far. The problem here is that small gap. There are a few possible outcomes  from this but the main cause is "Entering" the "Falling State" because our  Raycast saw no Ground in the ray it casted, as it's a really thin ray. Problems like getting stuck in the  Obstacle or between the Obstacles are possible because even though  our Capsule Collider is big enough to pass this gap without "Falling", our Raycast didn't realize that there was Ground a bit further, so we started "Falling" and got stuck instead. What we'll do to fix this is cast  not only our Ray but also a Box. This makes it so if there's  "Ground" really close to each other or the Player is going to  "Fall" a really small distance, we'll keep being on the "Grounded State". The Box can have any size you want but  the best would be around the same size as the Capsule Collider or a bit smaller. We'll go with the size of our "Ground  Check Trigger Collider" for this. Of course, we don't yet have a  reference to our Trigger Collider so we'll need to create a Script to hold it. So, in Unity, head over to the  Player Scripts "Data" folder. In here, create a new folder named "Colliders". Inside, create a new C# Script  named "PlayerTriggerColliderData". Open it up and remove the  default methods and inheritance. Make the class "[Serializable]" as well. We'll create a property to hold the "Ground  Check Trigger Collider" reference so type in "[field: SerializeField] public BoxCollider  GroundCheckCollider { get; private set; }". We'll need to add this to our  "CapsuleColliderUtility" Script, but it currently is a reusable class, while  this new class is for the Player only. Because of that, we'll create another class  to hold it together with the Collider Utility. So, back into Unity, go to  the Player "Utilities" folder and create a new folder named "Colliders". Inside, create a new C# Script named  "PlayerCapsuleColliderUtility". Open it up and remove the default methods. Swap the inheritance from "MonoBehaviour"  to "CapsuleColliderUtility". Make the class "[Serializable]" as well. Then, add in a new property by typing in "[field: SerializeField] public PlayerTriggerColliderData  TriggerColliderData { get; private set; }". When that's done, go to the "Player" Script and swap our "CapsuleColliderUtility" type  with "PlayerCapsuleColliderUtility" instead. This basically allows us to add  more things into the Utility that are specific to the Player, without  needing to change anything anywhere. You could and likely should go for Composition instead, which is simply adding the  "CapsuleColliderUtility" as a property instead of it being inheritance, but for simplicity I'll go with inheritance, as again, that makes it so we don't  really need to change anything else. If you prefer the Composition way, add it as a property and make sure you update all  usages to get there correctly. With that done, save everything and go to Unity. In the "Player" Game Object, open up the "Trigger Collider Data" Area and assign to it the "GroundCheck" Game Object. When that's done, open up the  "PlayerGroundedState" Script. In the same method, above our  if statement, we'll type in "if (IsThereGroundUnderneath())"  and generate the method. Inside of the if statement, we'll "return;" as that means we shouldn't "Fall". I'll add this method to the "Main Methods" region. In here, we'll check for a box  using the "OverlapBox" method. There are other methods to check for a  Box but at least the "CheckBox" method seems to count the transform we cast it  from as well, even with a Layer Mask, so we'll use the "OverlapBox" instead. This method requires a center in World Space from  us, which is where the box will be positioned. We'll use our "Ground Check" Box center for this. So, start by typing in "Vector3 groundColliderCenterInWorldSpace = stateMachine.Player.ColliderUtility .TriggerColliderData.GroundCheckCollider.bounds.center;". Then, we'll create a "Collider[]" named "overlappedGroundColliders" and assign it to "= Physics.OverlapBox();". For our center, we'll pass in  "groundColliderCenterInWorldSpace". Next we have the size, but it actually  requires half of it, so pass in "stateMachine.Player.ColliderUtility .TriggerColliderData.GroundCheckCollider.bounds.extents". We're repeating this quite  a bit so above our "Vector", create another variable of type  "BoxCollider" named "groundCheckCollider" and assign to it the "GroundCheckCollider". Then, swap it with where we use  our "Ground Check Collider". For the third argument, we need  the box rotation, so pass in "groundCheckCollider.transform.rotation". For our "LayerMask", we want  the "Ground Layer" so type in "stateMachine.Player.LayerData.GroundLayer". Pass in "QueryTriggerInteraction.Ignore"  for the last argument. Now, we can "return overlappedGroundColliders.Length > 0;", as that means we've found  ground underneath our Player. That should get our "Falling" working  whenever we exit the "Ground", but we still have a few things to do. The first one is to not allow  air movement while "Falling", which we can do by setting  the speed modifier to 0, so open up the "PlayerFallingState" Script. In here, "override" the "Enter" method. Inside, type in "stateMachine.ReusableData.MovementSpeedModifier = 0f;". I'll add this method to a new  region named "IState Methods". The second thing is to make sure  we limit our "Falling" velocity. This is because when falling, Gravity  will keep on increasing the velocity. When using ground colliders like "Mesh Colliders" or Colliders that don't really have a lot of depth, because our velocity can be quite high, our collider will go through the ground at a speed that the collision won't be detected, even with our Rigidbody collisions  set to "Continuous" and all that. This means that we would simply  keep on the "Falling State" even though we have already hit the "Ground". To fix that problem, we simply  limit our vertical velocity, which Genshin seems to do as well. We can do that by adding a vertical Force to it in a way that it doesn't go over a certain limit, so "override" the "PhysicsUpdate" method and call in a new method named  "LimitVerticalVelocity();". I'll add this method to a new  region named "Main Methods". We'll need to know what's our velocity limit, which means we need a Data Class for "Falling". So, back in Unity, in the "Airborne" Data folder, create a new C# Script named "PlayerFallData". Open it up and remove the  default methods and inheritance. We'll make it "[Serializable]". In here, create a new "[field: SerializeField] [field: Range(1f, 15f)] public float FallSpeedLimit { get; private set; }". I'll default it to "15f". Higher values than this might not  work so beware when setting it higher. Then, open up the "PlayerAirborneData" Script and duplicate the "JumpData" property and swap "Jump" with "Fall" instead. When that's done, go back to  the "PlayerFallingState" Script. In our variables, create a  new variable by typing in "private PlayerFallData fallData;" Then, in the constructor, type in "fallData = airborneData.FallData;" Then, back to our "LimitVerticalVelocity" method, We'll check if we've reached  the speed limit by typing in "if (stateMachine.Player.Rigidbody.velocity.y  >= -fallData.FallSpeedLimit)". If that's the case, we "return". Note that our "speed limit" is turned to negative  here, so "-14" will be greater than "-15". "-16" for example, would be an  higher speed fall than "-15", but because it's a negative number,  it means that it's lower than "-15". Otherwise, if we've passed the limit, we'll set  the current Y velocity to be our max fall speed. To do that, type in "Vector3 limitedVelocity = new Vector3(0f, -fallData.FallSpeedLimit -  stateMachine.Player.Rigidbody.velocity.y, 0f);". We're simply getting the  difference between both here. If we're falling at "-16" and the max is "-15", then we'll add "1" to make sure we go to "-15". Because we only enter this  statement when the Player velocity is stronger than the fall speed  limit, doing the subtraction this way ensures we always receive a positive  value, not needing to use "Abs"olutes. We're also getting our velocity  twice now so above everything type in "Vector3 playerVerticalVelocity  = GetPlayerVerticalVelocity();". Then, swap the "velocity.y" lines with  "playerVerticalVelocity.y" instead. With that done, we simply need to add  the Force, so at the bottom, type in "stateMachine.Player.Rigidbody.AddForce(limitedVelocity,  ForceMode.VelocityChange);". We pass in "VelocityChange" as we don't want it  to be over time nor to depend on the Player mass. That solves one bug so we only  have 2 things left to do or solve. The first one is to transition  from "Jumping" to "Falling". This is because when we "Jump" right now, we'll never start "Falling" as there's  nothing telling us to change States. Now, we could have this happen in 2 possible ways. The first one is by setting an  Animation Transition Event on the frame that the "Jump Animation" gets to the top. However, while this may seem fine  at first glance, there's a problem. Lets say we "Jump" and there's an  obstacle right above our Player head. When we hit this Obstacle we'll  start moving down due to Physics but because our "Jump Animation"  still hasn't reached the top frame, we'll be falling while in the "Jumping State". The second way of doing it is the  way we'll be fixing this problem: Simply transition to the "Falling State"  whenever our velocity becomes negative, as that means we're "Falling". So, start by opening up the  "PlayerJumpingState" Script. In here, in the "IState Methods"  region, "override" the "Update" method. Inside, we'll check "if  (GetPlayerVerticalVelocity().y > 0)" and "return;" if that's the case. Otherwise, we can transition to  the "Falling State" by typing in "stateMachine.ChangeState(stateMachine.FallingState);". This is basically what it's  normally needed to transition from the "Jumping State" to the "Falling State", but, we need to keep in mind that we're  using the "Floating Capsule" technique. This technique tries to keep the "Capsule  Collider" afloat by adding a Force to it. This is needed because the "Capsule Collider"  would otherwise fall due to Gravity. This means that our velocity  will always become negative until the next "AddForce" method is called. Because of that, if we leave  things as they are right now, it will "Enter" the "Falling  State" right after we "Jump". Thankfully, it's quite simple to solve that. In our variables, create a new variable by typing in "private bool canStartFalling;". Then, in our "Update" method, we'll check "if (!canStartFalling && IsMovingUp())". We'll also pass in "0f" to the "IsMovingUp" method to make sure we check for the  actual moment it starts going up. If that's the case, we'll set  the "canStartFalling = true;". Then, in our state change if statement,  we'll add in "!canStartFalling ||" The transition will now only  happen if we "canStartFalling" and the Player velocity is  under "0", or, negative. Of course, because we're caching in our States, we need to make sure we reset this  variable to false when "Exiting". So, in our "Exit" method, simply  type in "canStartFalling = false;". With that, we should have our transition  to the "Falling State" working correctly. However, I had a problem when falling off ramps that we would sometimes just randomly Jump. I don't really remember the cause,  but I remember that to fix it, I had to reset our vertical velocity  when we "Entered" the "Falling State". We don't reset our horizontal velocity because  we still want to keep our Horizontal Momentum. So, open up the "PlayerFallingState" Script. Then, in our "Enter" method, type  in "ResetVerticalVelocity();". Of course, we don't still have this method, so open up the "PlayerMovementState" Script. Go to our "Reusable Methods" region  to our "ResetVelocity" method. Under it, create a new method by typing in "protected void ResetVerticalVelocity()". Inside, we'll make it so that our  vertical "velocity" becomes "0", so start by typing in "Vector3 playerHorizontalVelocity =  GetPlayerHorizontalVelocity();". Then, set the "stateMachine.Player.Rigidbody.velocity = playerHorizontalVelocity;". The "GetPlayerHorizontalVelocity" method returns a Vector with the "y" value as "0", which makes it so we're resetting the vertical  velocity while keeping the rest being the same. If we now go to the "PlayerFallingState" Script, the method should no longer be throwing an error. This should be enough so now  there's only one thing left to do, which is to "override" the  "ResetSprintState" method and leave it empty. I'll add this method to a new  region named "Reusable Methods". This override is required because now our "Jumping  State" only happens until we're at the top, so we need to make sure we keep  sprinting after we "Fall" as well. Of course, because we can "Jump"  and go to the "Idling State" in certain situations where we  "Jump" to an higher leveled "Ground", we can keep the "JumpingState" "override" as well. However, we need to remember that we can also go  from a "Grounded State" to the "Falling State". This means that in our "Sprinting State", we also need to set the "shouldResetSprintState" variable to "false" when we start "Falling", much like what we did for when we "Jumped". Thankfully, we've previously created  a "virtual" method named "OnFall" that will make it easy for us to do that. So, open up the "PlayerSprintingState" Script. In here, in the "Reusable Methods" region, "override" the "OnFall" method. Before we call the "base" method, we simply type in  "shouldResetSprintState = false;". This means that if our  "ShouldSprint" becomes "true", it will now not be reset when we fall. Note that the "ShouldSprint"  should only become "true" if the "Sprint" Key was held long enough, which is how it works in Genshin and is how it's working for us as well. That should be all that's needed for "Falling", so save it up and go to Unity. Entering Play Mode, we should  now be able to "Fall" correctly. With our "Falling State" done, we can now add in our last States, which are the "Landing States". Originally, the "Light Landing State" was the only "Landing State" that we would add in the first part of the Series, but with the addition of the "Falling State", I think it's fair if we add the other 2 "Landing States" as well. Of course, for the same reason as our "Falling State", we haven't yet downloaded the 2 extra "Landing States Animations", so we'll need to do it now. Start by opening up "mixamo.com". Make sure you're logged in and your Character Model is selected. Then, in the "Animations" tab, search for: "Hard Landing". Download it with the "FBX For Unity" option selected. I'll rename it to not contain the white space it comes with. And also search for "Sprinting Forward Roll". Download it with the "In Place" option enabled. I'll rename it to "ybot@Roll". Of course, "ybot" here would be your selected Character Model name. When that's done, in Unity, go to the "Landing" Grounded Animations folder. Then, drag the two downloaded Animations to this folder. Open both by clicking on the arrow key and "Ctrl + Click" both "Animation Clips". Then, press "Ctrl + D" to duplicate them. Once that's done, remove the imported Assets as we no longer need them. Regarding our "Landing States", we now know that we have 3 possible States: "Light Landing", which happens when we fall from a small Height, like after "Jumping" or when touching the "Ground" from "Gliding". "Hard Landing", which happens when we fall from a higher Height. And "Rolling", which also happens when we fall from a higher Height but only if we have a "Movement" Key being pressed. With that in mind, lets start by creating their Scripts. To do that, go to the Player "Grounded" States Scripts folder. In here, create a new folder named "Landing". Inside, we'll create 4 new C# Scripts: "PlayerLandingState", "PlayerLightLandingState", "PlayerRollingState", and "PlayerHardLandingState". Start by opening up the "PlayerLandingState" Script. Remove the default methods and swap the inheritance from "MonoBehaviour" to "PlayerGroundedState". Generate the necessary constructor. When that's done, go to all of the other "Landing States" and remove their default methods and swap their inheritance with "PlayerLandingState" instead, generating the necessary constructor for each one of them. When that's all done, we'll cache our States so open up the "PlayerMovementStateMachine" Script. In here, we'll create 3 new properties, one for each of the "Landing States". To do that, type in "public PlayerLightLandingState LightLandingState { get; }". Then, duplicate this property twice and swap the "LightLandingState" with "PlayerRollingState" and "PlayerHardLandingState". Don't forget to initialize them as well. When you're done, go back to the "PlayerLandingState" Script. We will transition to a "Landing State" from an "Airborne State", which means we can "Enter" the "Landing State" with a "Movement" Key pressed. That means that if we let the Key go, our "OnMovementCanceled" method callback will be called and we'll transition to the "Idling State". Of course, we only want to transition to the "Idling State" at the end of their animations. So, to remove the current behaviour, "override" the "OnMovementCanceled" method and leave it empty. I'll also add it to a new region named "Input Methods". The next thing we'll do is to update the current "Idling State" transitions that were supposed to be to the "Light Landing State". We only need to do it in the "Jumping" and "Falling States". Thankfully, we've added that transition in our "Airborne State", so open it up. In here, in the "OnContactWithGround" method, swap the "IdlingState" with "LightLandingState" instead. Of course, we don't really do anything in our "Light Landing State" yet. The first thing we'll do is make sure we can't move while in this State, so open it up. In here, "override" the "Enter" method. Then, set the "stateMachine.ReusableData.MovementSpeedModifier = 0f;". I'll add this method to a new region named "IState Methods". The reason why we're not doing this in the "Landing State" is because "Rolling" will have an actual speed, as we'll be able to move while "Rolling". I'll also reset the velocity when we "Enter" this State as we want to make sure we stand still and don't keep any previous velocity. So, call in "ResetVelocity();". And that's all we really need regarding the "Light Landing State" logic, so all that's left to do here is to set the transitions from this State. If we take a look at our "Movement System States", we can transition from the "Light Landing State" to: "Idling", all "Moving States", "Dashing" and "Jumping". When the "Light Landing State" reaches its last frame, it will transition to the "Idling State". Whenever a "Movement" Key is pressed while in the "Light Landing State", it will transition to the respective "Moving State", including the "Sprinting State". At any given moment of the "Light Landing State", we can "Dash" or "Jump". With that in mind, lets transition to the "Idling State" by "overriding" the "OnAnimationTransitionEvent" method. Inside, type in "stateMachine.ChangeState(stateMachine.IdlingState);". Make sure you remove the base method call. For our "Moving" States, we'll have to follow the "Update" method approach, as we'll be getting here through an "Airborne State" like "Jumping". So, above, "override" the "Update" method. Inside, check "if (stateMachine.ReusableData.MovementInput == Vector2.zero)" and "return;" if that's the case. Otherwise, we call in "OnMove();". For our "Dashing" and "Jumping" transitions, because a "Landing State" is a "Grounded State", they are already set up. That's all for our "Light Landing State", so we can now do our "Hard Landing State". In Genshin, if we "Fall" from a high enough height and "Land", if there's no "Movement" Input Key being pressed, we'll enter the "Hard Landing State". In this State, much like our "Light Landing State", we don't nor can "Move" while in it. Lets start by doing that by opening up the "PlayerHardLandingState" Script. In here, "override" the "Enter" method. Inside, we'll set the "stateMachine.ReusableData.MovementSpeedModifier = 0f;". We'll also call in "ResetVelocity();" to reset any velocity we had before "Landing", making sure we don't accidentally move horizontally due to Physics. I'll add this method to a new region named "IState Methods". That's most of the necessary logic for our "Hard Landing State" and the only remaining logic is regarding the transitions to other States. Because only the "Falling State" will transition to the "Hard Landing State", we'll do that transition after we also add the "Rolling State". Regarding Transitions to other States, if we take a look at our "Movement System States", we'll be able to transition to: "Idling", "Running" and "Dashing". Whenever the "Hard Landing State" reaches its final frame, the Player will transition to the "Idling State". Regarding the "Running State", the way the "Hard Landing State" handles moving to a "Moving State" is somewhat different. Whenever we first "Land", we'll actually not be able to "Move", or, press a "Movement" Input Key. However, at a certain frame of its animation, we'll be able to start "Moving" by pressing a "Movement" Input Key. This simply means that we'll have to disable the Input for a few frames. One thing to note as well is that we cannot transition to the "Walking State" and only the "Running State". And last but not least, we can "Dash" at any given moment of the "Hard Landing State". Regarding "Jumping", we cannot "Jump" again while we're on the "Hard Landing State". With that in mind, we'll start with the "Idling State" transition by "overriding" the "OnAnimationTransitionEvent" method. Inside, we'll remove the "base" method call and type in "stateMachine.ChangeState(stateMachine.IdlingState);". For our "Moving States" transition, we'll start by "overriding" the "OnMove" method. I'll add this method to a new region named "Reusable Methods". Then, we'll remove the base method call and then check "if (stateMachine.ReusableData.ShouldWalk)" and if that's the case, we "return;". Otherwise, we'll call in "stateMachine.ChangeState(stateMachine.RunningState);". To call the "OnMove" method we'll use the "Movement" "started" callback. Now, we can do this here even though we can come from the "Falling State" because we'll be disabling and enabling the "Movement" Input, which means the "started" action will be called when enabling again if a Key is being pressed. So, above our "OnMove" method, "override" the "Add" and "RemoveInputActionsCallbacks" methods. Then, add a new callback by typing in "stateMachine.Player.Input.PlayerActions.Movement.started += OnMovementStarted;". Don't forget to remove it as well. I'll add this method to a new region named "Input Methods". I'll also rename the parameter to "context". Inside, we simply type in "OnMove();". Regarding our "Dashing State" transition, it's already coded in the "Grounded State". Now, right now, we are able to "Jump" because of our "OnJumpStarted" method in the "Grounded State". So, in our "Input Methods" region, "override" the "OnJumpStarted" method. Then, simply leave it empty, meaning we won't transition to the "Jumping State". Regarding our "Movement" Input, we'll Disable it when we "Enter" this State and then Enable it both when "Exiting" the State and at a certain frame of its Animation. We need to make sure we also enable it when "Exiting" because it's possible that we never reach the defined Animation frame, in case we decide to do something like "Dash", which we can at any given frame. So, to do that, in the "Enter" method, type in "stateMachine.Player.Input.PlayerActions.Movement.Disable(); ". Then, "override" the "Exit" method and type in "stateMachine.Player.Input.PlayerActions.Movement.Enable();" . Copy this line and now "override" the "OnAnimationExitEvent" method. We'll use this one to enable the Input at a certain frame of the Animation, so remove the "base" method call and paste the line we've copied inside. And that's all that's needed for our "Hard Landing State", so next we'll do our "Rolling State". Of course, lets first understand what this "Rolling State" is. In Genshin, if we "Fall" from a high enough height and "Land", if there's any "Movement" Input Key being pressed, we'll "Roll" instead of "Hard Landing". In this "Rolling State", we can also "Move" around while "Rolling". With that in mind, we know we'll need 2 things: A Movement Speed & a Rotation Data. Thankfully, it uses the same rotation as our normal "Movement", so we don't really need to do anything extra regarding it. We'll however, still need our movement speed modifier, so we'll need to create its Data class. To do that, go back into Unity. In here, in the Player "Grounded" Data folder, create yet another folder named "Landing". Inside, we'll create a new C# Script to which I'll name "PlayerRollData". Open it up and remove the default methods and inheritance. We'll make the class "[Serializable]" as well. Then, type in "[field: SerializeField] [field: Range(0f, 3f)] public float SpeedModifier { get; private set; }". I'll default it to "1f". We'll then open up the "PlayerGroundedData" Script. Duplicate the "Stop" property and swap "Stop" with "Roll" instead. With that done, we can now open up the "PlayerRollingState" Script. In here, create a new variable by typing in "private PlayerRollData rollData;". Then, in the constructor, type in "rollData = movementData.RollData;". We can now set our speed modifier, so start by "overriding" the "Enter" method. Inside, type in "stateMachine.ReusableData.MovementSpeedModifier = rollData.SpeedModifier;". I'll add this method to a new region named "IState Methods". Now, our "Rolling State" will keep "Rolling" until its last frame even if we stop pressing any "Movement" Key. This of course means that if we fast press a Key, our Player will stop Rotating as the "Move" method isn't being called anymore. Because of that, we'll need to add an Automatic Rotation to it. Thankfully, that's quite simple to do here, so start by "overriding" the "PhysicsUpdate" method. Inside, we simply call in "RotateTowardsTargetRotation();". That should take care of it, but let's also add an "if (stateMachine.ReusableData.MovementInput != Vector2.zero)" and "return;" inside. This ensures we only rotate when we are not calling the "Move" method, as our previous implementation would call the "RotateTowardsTargetRotation" method twice while "Moving". Right now, because we are able to "Move" in the "Rolling State" and also because we can only "Enter" here if we were pressing a "Movement" Input Key, our "Grounded State" "ShouldSprint" if statement will never "Enter" due to its condition. However, in Genshin, we do not keep sprinting after "Rolling". To fix this, we'll just go to our "Enter" method and type in "stateMachine.ReusableData.ShouldSprint = false;". This should take care of the problem and we should no longer keep sprinting once we "Enter" the "Rolling State". We'll now take a look at the transitions from the "Rolling State". Regarding transitions to the "Rolling State", much like the "Hard Landing State", we can only come here from the "Falling State", so we'll add that transition when we're finished doing the "Rolling State". Regarding transitions from this State, if we take a look at our "Movement System States", we can transition to: "Walking", "Running", "Dashing" and "Medium Stopping". Whenever the "Rolling State" reaches its last frame, if there's no "Movement" Input Key being pressed, it will transition to the "Medium Stopping State". If it reached its last frame but we had a "Movement" Input Key being pressed, then we would transition either to the "Walking" or the "Running State" depending on the "WalkToggle" value. In Genshin, it seems that you transition to the "Walking State" a bit earlier, but I didn't like the way it looks so I decided to transition at the last frame. If you do want to completely replicate it, then you could "override" another "Animation Event" like the "Enter" or "Exit" method and do that transition there. I say "Enter" or "Exit" because we'll already be using the "Transition" Event to transition at the last frame, so unless you kept a variable that would check if we reached the last frame already, it would possibly transition to the other States earlier as well. Feel free to do it as you prefer. We can also "Dash" at any given moment of the "Rolling Animation". Much like our "Hard Landing State", we cannot "Jump" in the "Rolling State". With that in mind, lets start by setting our Animation Transition Event by "overriding" the "OnAnimationTransitionEvent" method. Inside, remove the "base" method call and start by checking "if (stateMachine.ReusableData.MovementInput == Vector2.zero)". If that's true, we can then transition to the "Medium Stopping State" by typing in "stateMachine.ChangeState(stateMachine .MediumStoppingState);". Of course, "return;" right after to not call the rest of the code. Otherwise, if we are "Moving", then we'll just call in "OnMove();". Because we're setting the "ShouldSprint" property to "false" above, the "OnMove" here will never transition to the "Sprinting State", so we don't need to "override" it. Because we're only adding this transition at the last frame, you could also very likely simply transition to the "Medium Stopping State" and let it take care of the transitions to the "Moving States". However, I'll leave them here as they are right now. The "Dashing State" is of course already done in our "Grounded State". For our "Jump", lets "override" the "OnJumpStarted" method and then leave it empty, as we don't want to be able to "Jump". I'll also add this method to a new region named "Input Methods". With that done, all that's left is to add the transition from the "Falling State" to the "Landing States". To do that, open up the "PlayerFallingState" Script. The way the transitions from the "Falling State" to the "Landing States" work is as follows: If we fall from a small height, we'll transition to the "Light Landing State". If we fall from a bigger height, we'll transition to the "Hard Landing State" or the "Rolling State" depending on whether we are pressing a "Movement" Input Key or not. This means that we need to do 2 things: Get the distance that the Player fell and override the "OnContactWithGround" method, as right now it's only transitioning to the "Light Landing State". Knowing that, in the "Reusable Methods" region, start by "overriding" the "OnContactWithGround" method and remove the "base" method call. We now need to know our distance. The way we'll be knowing it is by saving the position of the player when it "Entered" the "Falling State" and then the position of the Player when it collided with the "Ground". Knowing both, we can then subtract the "Y" value of one with the "Y" value of the other, which gets us the vertical distance between both positions. Of course, we should also always make this value positive. This is because the "Jumping State" transitions to the "Falling State" when it reaches its top point, which means we can "Fall" into a "Ground" that was above our previous "Ground", which gets us a negative distance. So, knowing that, in our variables area, create a new variable by typing in "private Vector3 playerPositionOnEnter;". Then, in our "Enter" method, we type in "playerPositionOnEnter = stateMachine.Player.transform.position;". When that's done, go back to our "OnContactWithGround" method. In here, we'll get the distance by typing in "float fallDistance = Mathf.Abs();", for "Absolute", which makes it become positive when the subtraction returns negative and pass in "playerPositionOnEnter.y - stateMachine.Player.transform.position.y;". This gets us the difference between the player position on "Enter" and its current position. When that's done, we now need to know what's the distance to be considered a "small" or "big" fall. So, to know that, open up the "PlayerFallData" Script. In here, duplicate the existing property and name it "MinimumDistanceToBeConsideredHardFall". I'll also make the range be from "0f" to "100f" and then default its value to "3f". When that's done, go back to the "PlayerFallingState" Script. In our "OnContactWithGround" method, now check "if (fallDistance If that's the case, we'll call in "stateMachine.ChangeState(stateMachine.LightLandingState);". We could instead call the "base" method here as it does the same as this transition line, but, we won't because the "base" method can be changed at any moment to contain something else. We of course need to "return;" right after to not call the remaining code. Under this, if we hit the minimum distance, we'll either transition to the "Hard Landing State" or the "Rolling State". We'll start with the "Hard Landing State" by typing in "if (stateMachine.ReusableData.MovementInput == Vector2.zero)". Inside, we call in "stateMachine.ChangeState(stateMachine.HardLandingState);". We "return;" right after as well. Otherwise, if we have movement, we'll transition to the "Rolling State" by typing in "stateMachine.ChangeState(stateMachine.RollingState);". And that's all we need for our transitions. With this, our "Landing States" should now be working. As a tip, if you wanted to add "Fall Damage" to your Player, this would likely be the Place for you to do it. Now, regarding our transition to the Landing States, in Genshin, if we have the "WalkToggle" on, we'll not be able to transition to the "Rolling State". However, that's only true if we're not "Sprinting". If we are, then we'll be able to transition to the "Rolling State". So, in our "Hard Landing State" Transition if statement, add in "stateMachine.ReusableData.ShouldWalk && !stateMachine.ReusableData.ShouldSprint ||". This makes it so that if we have the "WalkToggle" on and are not "Sprinting", we'll transition to the "Hard Landing State". One last thing to do is to set the Jump Force to the Stationary Force when we Light Land, so open up the "PlayerLightLandingState" Script. In here, in the "Enter" method, we type in "stateMachine.ReusableData.CurrentJumpForce = airborneData.JumpData.StationaryForce;". Saving and going back to Unity, entering Play Mode should allow us to "Fall" and "Land" well. Of course, until we add Animations, we won't be able to fully test our "Landing" System. There's now one last feature we need to take care of, which we've left to do later when we added our Player Camera. If you don't remember it by now, I've previously said that we would be enabling the Virtual Camera "Horizontal Recentering" option, which was the option that the "FreeLook Camera" didn't offer us. Now, what exactly is this "Horizontal Recentering" option and why do we really need it? Lets take a look at Genshin Impact. Currently, we're facing forwards while in the "Idling State". Lets say that we start moving to one of the sides, right or left. The moment we do so, we can notice that our Player slowly rotates and moves in a circular motion. We can further notice this if we look down on the Player at around a 90 degrees angle and start moving. What happens is that our Player will rotate extremely fast. This happens even if we move "backwards" while we're looking down. This is achievable through the "Horizontal Recentering" option. Simply put: it automatically recenters our "Camera" at a certain speed, also allowing us to set a time it should wait before it starts recentering. The speed of the Recenter is called "Recentering Time", which is how long it takes to recenter the Camera. The time it should wait before it starts Recentering our Camera is called "Wait Time". Of course, our Player only rotates and moves in a circular motion because its movement is dependent on the Camera "Horizontal Axis". So by Recentering the Camera "Horizontally", or, in the "Horizontal Axis", our Player will start rotating itself and move with that Rotation in mind. The higher the "Recentering Time", the longer it takes to Recenter the Camera, which means the slower the Player Rotates. Of course, as we've seen before, different angles can have different settings or even enable or disable the Recentering option. For example, moving "backwards" while looking forwards disables the option while moving "backwards" while looking up or down enables it. To do this, we'll need to know the direction we're moving, which we can know through the "Movement Input" variable and also the settings we want for each angle in each direction. To know those settings we'll actually not use "Animation Curves" and use our own class this time, in which we can simply have a list of angle ranges and their Recentering values. This is because I haven't found a way to have 2 different values in one Animation Curve, which would require us to have 2 "Animation Curves", one for each value, which I found somewhat unnecessary as the values are updated for the same ranges. You are free to do it using 2 "Animation Curves" if you prefer to. With that in mind, we'll start by creating the class that will hold this Data. To do that, go to the Player "Data" folder. In here, create a new folder named "Cameras". Inside, create a new C# Script to which I'll name "PlayerCameraRecenteringData". When that's done, open it up. Remove the default methods and the "MonoBehaviour" inheritance and make this class "[Serializable]". We'll create the angle ranges by typing in "[field: SerializeField] [field: Range(0f, 360f)] public float MinimumAngle { get; private set; }" Then, duplicate this property and name it "MaximumAngle" instead. Next we'll need one property for each of our Recentering values, so duplicate the last property. I'll make the range be from "-1f" to "20f" and rename it to "WaitTime". Then, duplicate this new property and rename it to "RecenteringTime" instead. We made the minimum range value "-1" as a way to specify that we want to use the default setting value, which we'll define later. Next we need to create a method that tells us if we're within the angle range, so type in "public bool IsWithinRange(float angle)". Inside, we simply "return angle >= MinimumAngle && angle When that's done, open up the "PlayerGroundedData" Script. We'll be creating 2 "Lists" of our class, one for Recentering Settings when moving Sideways and another one for Settings when moving Backwards, or "Downwards". So, to do that, type in "[field: SerializeField] public List SidewaysCameraRecenteringData { get; private set; }". Make sure you import the necessary namespace. Then, duplicate the property and swap "Sideways" with "Backwards", or "Downwards" if you prefer. When that's done, save everything and go back to Unity. Open up the second "Inspector" tab. In our "Sideways Recentering Data", we need 2 ranges, so add 2 elements to the List. The first one will be from "0" to "80", to which we'll pass in "-1" for both settings. This of course means they'll use the default values we'll define later. The second one will be from "80" to "90", to which we'll pass in "0" for the "Wait Time" and "0.3" for the "Recentering Time". That's it for our "Sideways Data", so for our "Backwards Data", we'll be having 1 range, so add in 1 element to the List. The range will be from "80" to "90" degrees, to which we'll pass in "0" for the "Wait Time" and "0.5" for the "Recentering Time". The "Horizontal Recentering" is disabled for any other angle range when moving "backwards". Also note that while we're setting the "Horizontal Recentering", these angle ranges are for the Camera "Vertical Angles". With that done, we'll set the Player Camera settings to their default values. So, select the "PlayerCamera" Game Object in the "Hierarchy" and open up the "Aim" Area. Don't forget to go to the first "Inspector" tab. In here, set the default "Horizontal Recentering" settings to "0" and "4". These are not really necessary as we'll be overriding them later, but it's always nice to have them set to the default values right away. We'll be defining these default values somewhere else as well, which will be in an Utility Script we'll be creating. This Utility Script will allow us to enable or disable our "Horizontal Recentering" option whenever we want to. To do that, open up the Player "Utilities" Scripts folder. In here, create a new folder named "Cameras". Inside, we'll create a new C# Script to which I'll name "PlayerCameraUtility". Open it up when you're done. Remove the default methods and inheritance and make it "[Serializable]". To get a reference to our "Horizontal Recenter" option, we'll need a reference to our Camera "POV" Aim. To get that, type in "private CinemachinePOV cinemachinePOV;". Make sure you import the "Cinemachine" namespace. Above, we'll get a reference to our Camera by typing in "[field: SerializeField] public CinemachineVirtualCamera VirtualCamera { get; private set; }". We'll also have a property for our default values, so type in "[field: SerializeField] public float DefaultHorizontalWaitTime { get; private set; }" and a default value of "0f". Then, duplicate this property and swap "Wait" with "Recentering" instead and default it to "4f". To get our "POV" reference, we'll create an Initialization method, so type in "public void Initialize()". Inside, type in "cinemachinePOV = VirtualCamera.GetCinemachineComponent();". All that's left now is to create a method for Enabling and Disabling our "Recentering" option, so start by typing in "public void EnableRecentering()". Pass in "float waitTime = -1f" as the first parameter and "float recenteringTime = -1f" as the second parameter. Then, under the Enable method, create another one by typing in "public void DisableRecentering()". To enable our option, we'll type in "cinemachinePOV.m_HorizontalRecentering.m_enabled = true;" We'll then copy this line and paste it in the "Disable" method, swapping "true" with "false" instead. That enables and disables our option, but when enabling it, we also need to set our setting values, as we'll need them if that's the case. The first thing we'll do is actually cancel any existing recentering that the camera is doing by typing in "cinemachinePOV.m_HorizontalRecentering.CancelRecentering(); ". I'm not entirely sure if Genshin does this or not, but I'll leave it as I think it fixed some weird Recentering that happened without this. For our values, we'll first need to check if they're "-1f" and if they are, we'll set them to their default values. To do that, start by checking "if (waitTime == -1f)" and if that's the case, we type in "waitTime = DefaultHorizontalWaitTime;". When that's done, duplicate this whole if statement and swap the "wait" parts with "recentering" instead. To set our "Horizontal Recentering" option values, we now type in "cinemachinePOV.m_HorizontalRecentering.m_WaitTime = waitTime;". Then, duplicate this line and swap "Wait" with "Recentering" instead. And that's all we need to enable or disable our "Horizontal Recentering" option. We'll now add this utility as a property of the "Player" Script, so open it up. In here, create a new property by typing in "[field: SerializeField] public PlayerCameraUtility CameraUtility { get; private set; }". I'll also add in a "[field: Header("Camera")]". Then, in the "Awake" method, we need to "Initialize" it by typing in "CameraUtility.Initialize();". This simply sets our "CinemachinePOV" Component. When that's done, save it all up and go to Unity. In here, select the "Player" Game Object and select or drag the "PlayerCamera" to the "Virtual Camera" field. That's our Camera Utility done, so we can now start thinking on actually Recentering our Camera. To do that, open up the "PlayerMovementState" Script. We'll create 2 methods here that will call our "Enable" and "Disable Recentering" methods, just so that we do not call in a big line every time and also because we'll need to do something else later on when Enabling it. To do that, go to our "Reusable Methods" region and type in "protected void EnableCameraRecentering(float waitTime = -1f, float recenteringTime = -1f)". Inside, call in "stateMachine.Player.CameraUtility .EnableRecentering(waitTime, recenteringTime);". Then, under this method, we'll create the "Disable" method by typing in "protected void DisableCameraRecentering()". Inside, we call in "stateMachine.Player.CameraUtility.DisableRecentering();". That's all that's needed for these 2 for now, so lets now create a method to update our Recentering values. Above, type in "protected void UpdateCameraRecenteringState()" and accept a "Vector2" named "movementInput". We'll understand in a bit why we'll be passing in a variable instead of using the "MovementInput" property right away. We'll have to disable or enable our "Horizontal Recentering" option depending on both the Camera Angle and our Movement Input. There's a situation that disabled our Recentering option which is when we stop moving. Instead of doing this here, we'll actually do that in the "OnMovementCanceled" option. However, we only have one in our "Grounded State" and none in our "Movement State", so we'll have to create another. To do that, head over to the "AddInputActionsCallbacks" method. Inside, add in a new callback by typing in "stateMachine.Player.Input.PlayerActions.Movement.canceled += OnMovementCanceled;". Don't forget to remove it as well. I'll rename the parameter to "context" and then add this method to the "Input Methods" region. Because we'll leave it as "private", it won't conflict with the "Grounded State" method. Inside, call in "DisableCameraRecentering();". Back into our "UpdateCameraRecenteringState" method, we'll still "return" if there's no Movement Input, so type in "if (movementInput == Vector2.zero)" and "return;" if that's the case. Next, we'll need to disable it when moving forwards, as it sometimes seemed to give us a weird Recentering even though it's supposed not to Recenter in this situation, so type in "if (movementInput == Vector2.up)" and call in "DisableCameraRecentering();", "return;"ing right after as well. Then, we'll do it for when moving "backwards" so type in "if (movementInput == Vector2.down)". In this case, we'll need to iterate through our "Backwards" list and see if it's within the ranges, Disabling in case it isn't. Of course, to do that, we need our camera angle, so above, outside of the if statement, type in "float cameraVerticalAngle = stateMachine.Player.MainCameraTransform.eulerAngles.x;". Note that "x" is the Camera Vertical Angle. Now, we'll need to know if the range is between "-90" and "90", as looking up has the same effect as looking down. However, we're only checking for positive angles in our lists, so we'll have to convert the negative values to positive using Absolutes. To do that, type in "cameraVerticalAngle = Mathf.Abs(cameraVerticalAngle);". The reason why we're doing this here instead of above is because we'll need to add something else before we do this. Our "eulerAngles" only return positive values. That means that "-90" will be returned as "270" and so on. However, we want to make it "90" instead of "270" when making them positive. So, we'll simply make it negative again, by typing in, above our second assignment, "if (cameraVerticalAngle >= 270f)". If that's the case, we subtract "360" degrees to the angle by typing in "cameraVerticalAngle -= 360f;". This should now get us an angle between "-90" and "90" and then make it positive so that we can compare it with our list angle ranges. We can now iterate through our settings list, so inside of the if statement, type in "foreach (PlayerCameraRecenteringData recenteringData in movementData.BackwardsCameraRecenteringData)". Inside, we'll check "if (!recenteringData.IsWithinRange(cameraVerticalAngle))" and "continue;" to the next one if that's the case. Otherwise, we call in "EnableCameraRecentering(recenteringData.WaitTime, recenteringData.RecenteringTime);". At the end, we "return;" to exit the loop and method, as we've found the desired angle range already. If we did end up outside of the loop, then it means that we didn't find any setting with this angle, so we call in "DisableCameraRecentering();" and "return;" right after. That's all there is for setting our values. We'll need to do this exact thing with our "Sideways" list, so select the code we've just added except the last "return;" statement and extract it to a new method. If you can't extract it, remove the "return;" of the "foreach" loop for a bit while you extract it. I'll name it "SetCameraRecenteringState();". I'll also pass in the list of the backwards recentering data and add it as a parameter. Then, I'll rename the parameter to "cameraRecenteringData" and import the "List" namespace. When that's done, swap the "Backwards List" usage with the "cameraRecenteringData" parameter. Don't forget to add the "return;" statement back in our "foreach" loop as well. I'll make this method "protected", just in case we ever need to reuse it somewhere. All that's left to do now is to add the setting for when we're moving Sideways. Because we've tested all other directions and also because diagonal directions seem to use the same settings as the Sideways directions, we can just copy the method call we did for our "Backwards" direction and paste it in right after the if statement without checking for anything. We'll then simply swap "Backwards" with "Sideways" to use our "Sideways Data" instead. And that's really all we need to do for our "UpdateCameraRecenteringState" Method. Of course, we still need to call this method somewhere. We'll call it both when our "Movement Input" has changed and whenever our mouse has moved, as both of these mean that either our Camera Angle or our Player Direction have changed. So, in our "AddInputActionsCallbacks" method, in the "Reusable Methods" region, we'll add 2 new calbacks: "stateMachine.Player.Input.PlayerActions.Look.started += OnMouseMovementStarted;". And then, "stateMachine.Player.Input.PlayerActions.Movement.performed += OnMovementPerformed;". Make sure you remove both callbacks. I'll rename both parameters to "context" and then add both methods to the "Input Methods" region. Again, the difference between "started" and "performed" in our "Movement" is that "started" would only be called on the first key press, while "performed" means it will call on every key press, like going from "W" to "WD". Our "mouse started" will be called quite a few times though, likely because of the way "Delta" or the mouse works. In the "OnMouseMovementStarted" method, we'll call in "UpdateCameraRecenteringState();" and pass in "stateMachine.ReusableData.MovementInput". In our "OnMovementPerformed" method, we'll call in "UpdateCameraRecenteringState();". This callback here is the reason why we needed to add the "Movement Input" as a parameter instead of using it right away. We're currently on the callback of the "Movement" action. This means that the "Movement Input" we're reading in the "Update" method isn't yet updated to the value that we have in this callback, as this callback is called before setting the value. This of course means that using the "MovementInput" property in our Recentering methods, would update the "Recentering State" using the old "MovementInput" value. Thankfully, we can easily get the updated value here from the "context" variable. To do that, pass in "context.ReadValue();". With that done, our Camera Recentering should now be working, so save it all up, go back to Unity and enter Play Mode. Moving the camera around in the vertical axis or changing movement directions should now move and rotate the Player according to the Settings List. However, we currently have a few problems in our implementation. The first problem you might've noticed is that our Camera Recentering Rotation Speed seems to depend in our current Player Speed. If we change between Walking, Running and Sprinting, we change how fast our Player or Camera rotates. We'll be fixing this by making our "Recentering Time" be dependent in our Player Speed. The way we'll do that is quite simple. Let's say that for the Speed of "5", which is our current Base Speed, we have a Recentering Time of "4". This means that for a Speed of "10", we have "x". This is of course a simple rule of 3. To get the "x", which in this case it's pretty easy for us, as it should be "8", we multiply "10" with "4" and then divide it all by "5". This gets us exactly what we need, which is "8". However, it seems that we actually want the opposite. The higher our Speed, the lower our Recentering Time should be. In this case, we want the result to be "2". To do that, we simply use the Inverse rule of 3. The way we do that is by multiplying "5" by "4" and then divide it all by "10". This gets us the result we want, which is "2". We'll be adding this formula to our Camera Utility Enable Recentering method, so open up the "PlayerCameraUtility" Script. In here, in the "EnableRecentering" method, we'll be adding two new parameters at the end: "float baseMovementSpeed = 1f" and "float movementSpeed = 1f". Then, before we assign our values to the "Horizontal Recentering" fields, we apply the inverse rule of 3 by typing in "recenteringTime = recenteringTime * baseMovementSpeed / movementSpeed;". Note that we do not use the "*=" operator as that would multiply the "recenteringTime" with the division of "baseMovementSpeed" by "movementSpeed", which is not what we want. When that's done, go to where we use our "EnableRecentering" method, which should be in our "PlayerMovementState" Script. In here, we'll create a new variable by typing in "float movementSpeed" and assign it to "= GetMovementSpeed();". Then, we check "if (movementSpeed == 0f)" and if that's the case, we set the "movementSpeed = movementData.BaseSpeed;". This will make it so that we don't divide by 0, which shouldn't be possible and instead divide by the Base Speed, which will make it so it returns the normal Recentering Time, as we're multiplying by Base Speed and then dividing by Base Speed again. When that's done, pass in to the method call "movementData.BaseSpeed" and "movementSpeed". This should take care of setting the correct values for different Speeds, so save it all up and go back to Unity. Entering Play Mode, our Speed should no longer make our Camera Rotation faster, although it might not be a completely perfect solution. This however brings us to our second problem: Changing States, like going from "Running" to "Walking", does not update the Recentering values until we change to a new "Movement" Input Key. So moving to one side and pressing the "Walk Toggle" keeps the Recentering Time the exact same, which means we still have our problem. The way we'll be fixing this is quite simple: Every time we enter a "Grounded State", we'll update our "Camera Recentering State". To do that, open up the "PlayerGroundedState" Script. In here, in our "Enter" method, at the bottom, call in "UpdateCameraRecenteringState(stateMachine .ReusableData.MovementInput);". This seems like it would be enough, but we need to remember that this uses our Speed Modifier now. The problem with that is that we are currently only setting our speed modifiers after we call the "base" method, which means, we won't have them updated by the time we call in the "UpdateCameraRecenteringState" method. To fix this, we'll go to every "Grounded State", starting with the "Idling State" and in the "Enter" method, we'll place the Speed Modifier line to be above our "base" method call. Repeat this process for every "Grounded State" that sets a speed modifier. With this done, we should no longer have the problem we had. However, we also need to remember something else: We can "Enter" and "Exit" Slopes, which update our Speed Modifier. This simply means that the moment the Ground angle changes, we should make sure we update the Recentering values to our new Player Speed. To do that is quite simple, as we have a method that returns us the new Slope Speed Modifier. So, start by opening up the "PlayerGroundedState" Script. In here, go to the "SetSlopeSpeedModifierOnAngle" method in the "Main Methods" region. Inside, add in a new if statement by typing in "if (stateMachine.ReusableData .MovementOnSlopesSpeedModifier != slopeSpeedModifier)". Make sure you place our current assignment in here. Then, we'll also call in "UpdateCameraRecenteringState(stateMachine.ReusableData .MovementInput);". This makes sure our values are updated if our slope speed modifier is changed. If we save and go back to Unity, entering Play Mode should now update the Recentering values correctly. We do have two other problems to solve though. The first one is that our Rotation Stops when we cancel Input, even if we currently are in the "Jumping State". So "Moving", "Jumping" and then unpressing the "Movement" Key will disable our Camera Recentering. In Genshin, this is not what happens when we're in the middle of the "Jump" and should instead keep it enabled and rotating. To fix this problem we'll simply need to "override" our "OnMovementCanceled" method and leave it empty. Of course, we currently have a private method in our "Movement State" and a "protected" method in our "Grounded State", which means that we can't currently override it in our "Jump State". Thankfully, in the "Grounded State" one, we no longer need to change to the "Idling State", as no State does that when cancelling Input anymore. So, we'll simply remove it and make our "Movement State" method "protected virtual" instead. To do that, open up the "PlayerGroundedState" Script. In here, in the "Input Methods" region, remove the "OnMovementCanceled" method. Make sure you also remove it from the "Add" and "RemoveInputActionsCallbacks" methods as well. When that's done, open up the "PlayerMovementState" Script. In the "Input Methods" region, make our "OnMovementCanceled" method a "protected virtual" method. We now need to make sure that our currently "overridden" methods need to call the base method, or be removed if they were left empty to not transition to the "Idling State". So, open up every "Grounded State" Script and do that. For the States that transition to other States in this method, we need to make sure we call the "base" method after we change States. This is because when changing States, we now call in the "UpdateCameraRecenteringState" method in the "Enter" method, so it would enable it back after disabling it. When the "Grounded States" are all updated, open up the "PlayerJumpingState" Script. In here, add in a new region at the bottom named "Input Methods". Inside, we'll "override" the "OnMovementCanceled" method and leave it empty. This should be enough, so save and go back to Unity. Entering Play Mode, "Moving", "Jumping" and releasing the "Movement" Key should no longer "Disable" our "Horizontal Recentering". There's now only one problem left, which is that in Genshin, the Walking State seems to have the default Recentering Time when we're looking down and move "backwards". The "Idling State" seems to also have a "Wait Time" when we're looking down and move "backwards". For the "Sideways" cases, both seem to still rotate fast and without Waiting before Recentering. The way we'll be doing this is quite simple: We'll simply create a reusable property that allows us to set the current "Backwards" and "Sideways Lists". To do that, open up the "PlayerGroundedData" Script. In here, copy the "Backwards" list property. Then, open up the "PlayerWalkData" Script. In here, paste the copied property under our "BaseSpeed" property and import the necessary namespace. We only need to override our "Backwards" Data so we don't need to create a property for the "Sideways" Data. For our "Idling" case, we'll need a new Data class, so go back to Unity. In here, open up the Player Grounded "Data" folder and create a new C# Script to which I'll name "PlayerIdleData". Open it up and remove the default methods and inheritance. We'll make the class "[Serializable]" as well. Then, paste the copied property here. When that's done, go back to the "PlayerGroundedData" folder and duplicate the "Walk" property. Swap the "Walk" parts with "Idle" instead. When that's done, open up the "PlayerStateReusableData" Script. We'll be creating a property to hold these rotation values, so under our Speeds and Forces properties, paste the "Backwards" property we've copied before twice. Then, swap the second one to be "Sideways" instead of "Backwards". This is just in case we ever need to "override" the "Sideways" Data as well. Remove the "[SerializeField]" attribute and give them a "public set". When that's done, go to the "PlayerMovementState" Script. In the constructor, after setting our other variables, we'll type in "stateMachine.ReusableData.BackwardsCameraRecenteringData = movementData.BackwardsCameraRecenteringData;". Then, duplicate this line and swap "Backwards" with "Sideways". I'll extract both lines to a new method to which I'll name "SetBaseCameraRecenteringData();". I'll make this method "protected" and add it to the "Reusable Methods" region. When that's done, open up the "PlayerIdlingState" Script. In here, we'll create a new variable by typing in "private PlayerIdleData idleData;". In the constructor, we then set the "idleData = movementData.IdleData;". Then, in the "Enter" method, before we call the "base" method, we type in "stateMachine.ReusableData.BackwardsCameraRecenteringData = idleData.BackwardsCameraRecenteringData;". Note that we need to make sure we set this before we call the "base" method as otherwise our "Enter" method "UpdateCameraRecenteringState" call would update with our old "Data List". We now need to reset this Data whenever we leave the "Idling State" by using our "SetBaseRotationData" method. However, we can't really do that when we "Exit" the "Idling State". This is because if we do that, then the moment we "Move", we'll set the values back to normal, meaning that our "Wait Time" will be set to "0" again, so we'll start rotating fast instantaneously. If we take a look at Genshin, if we wait until we're "Idle" and start "Moving backwards" while looking Down, then we'll Wait for a while before our Recentering happens. However, if we start "Moving backwards" while we're on a "Stopping State", it will be instantaneous and we won't Wait for the Recentering to start happening. This means that we can simply reset the values when we "Enter" our "Stopping States". To do that, open up the "PlayerStoppingState" Script. In the "Enter" method, before we call in the "base" method, call in "SetBaseCameraRecenteringData();". All that's left is our "Walking State", so start by copying the line in our "Enter" method and then open up the "PlayerWalkingState" Script. In here, in the "Enter" method, paste the line we've just copied before the "base" method call. Then, swap the "idleData" with "walkData" instead. Make sure you create the "walkData" variable and set it in the constructor. When that's done, "override" the "Exit" method. We'll need to reset the Data back to its default values, which we can do by calling in "SetBaseCameraRecenteringData();". That should set all the Data Lists correctly, but we of course can't forget to actually use the reusable properties in our methods. To do that, head back to the "PlayerMovementState" Script. In our "UpdateCameraRecenteringState" method, in the "Reusable Methods" region, swap our "movementData" usages with "stateMachine.ReusableData" instead. This should be enough to get it working, so save it all up and go back to Unity. In our second "Inspector" tab, for our "Idle Data", we'll only have one range, which will be from "80" to "90". For the "WaitTime", set it to "0.25" and for the "RecenteringTime", set it to "0.5". For our "Walk Data", we'll also only have one range, which will be from "80" to "90", with a "WaitTime" and "RecenteringTime" of "-1". If we now enter Play Mode, we should finally have our Recentering working fine. We're now almost ready to start adding animations, but before we go ahead and do that,  we'll first fix a few existing Bugs. The first bug we have is due to Gravity. This bug happens when we are near an Edge and our Capsule Collider starts falling  due to its "Spherical" bottom. This basically makes our Player fall and  also adds a bit of velocity due to Physics. This is a problem because this can happen  in the States where we are standing still, like the "Idling State" or the "Landing States", as it makes our Player slide while  we were supposed to not be moving, due to the velocity it added  when falling off the Edge. The fix for this bug is quite simple: If the Player is moving in one of  these states, we reset its velocity. Thankfully for us, we already have 2  methods that allow us to do just that. We only need to fix this problem in 3 States, which are the "Idling State" and the  "Light" and "Hard Landing States". Knowing that, start by opening up  the "PlayerIdlingState" script. In here, "override" the "PhysicsUpdate" method. Then, type in "if (!IsMovingHorizontally())" and if that's the case, we "return;". In the case that it "is Moving" though,  we simply call in "ResetVelocity();". And that's all we need to do, so now copy this whole "PhysicsUpdate" method and  go over to the "PlayerLightLandingState" Script. In here, paste the "PhysicsUpdate" we've  just copied into the "IState Methods" region. Then, do the same for our  "PlayerHardLandingState". We won't be doing this in our "PlayerLandingState"  because our "Rolling State" doesn't need it. That solves one bug, but we  still have a few more to fix. The next one is regarding our Dashing State. I thought I had fixed it but turns  out that there's still one bug, which happens when we "Move", Dash  and then Unpress the "Movement" Key. It doesn't always happen, but when it  does, we simply "Dash" at a low speed. I'm not entirely sure why but it's likely because  we are using the movement speed in this Dash and we just changed to a low speed modifier State. I've changed the code a bit and  hopefully it is enough to fix it, but in short: we'll simply always  add an initial force when "Dashing". To do that, open up the  "PlayerDashingState" Script. In here, rename our "AddForce"  method to "Dash" instead. Because we'll be adding a force in  case we have a movement input as well, place the current code that gets us the direction above our if statement and remove the "return;" statement. Inside of our if statement, we'll now call in "UpdateTargetRotation(GetMovementInputDirection());". This will simply update our target rotation to  be relative to our "Movement" Input and Camera, which will allow us to add a  Force towards that direction. Then, we'll set the "characterRotationDirection = GetTargetRotationDirection (stateMachine.ReusableData.CurrentTargetRotation.y)", which will get us the direction of our Input  relative to the Camera, as we've just updated it. Of course, our "characterRotationDirection"  variable name no longer makes sense, so I'll rename it to "dashDirection" instead. From what I've tried, it seemed to  work without this whole if statement, but I'll leave it as is,  as it makes more sense and to make sure it doesn't cause any rotation bug. With this, we're now always adding a force  to our "Dash" towards a certain direction, which depends on whether our "Movement"  Input is being pressed or not. This should be enough to fix it. However, we also need to take into  account the movement speed on slopes, as right now we'll be Dashing  slower when we're on a slope. So, go to the "GetMovementSpeed" method  in the "PlayerMovementState" Script. In here, add a new parameter by typing in "bool shouldConsiderSlopes = true". Then, create a new variable by  typing in "float movementSpeed" and assign to it the first multiplication. Then, we'll check "if (shouldConsiderSlopes)", and if that's true, we type in "movementSpeed *= stateMachine.ReusableData.MovementOnSlopesSpeedModifier;". At the end, we update the whole "return" statement to return this "movementSpeed" variable instead. When that's done, go back to  the "PlayerDashingState" Script and pass in "false" to the  "GetMovementSpeed()" method. And that's another Bug fixed, so for the next one,  I've previously added a "Mathf.Abs()" when Falling in case we fell into a Ground  that was above our current Ground. However, that's of course not valid as  if we ever "Fall" in such a situation, we don't want the Player to go to the "Hard  Landing State" or the "Rolling State", but only to the "Light Landing State",  as the Ground is above the Player. To fix that, open up the  "PlayerFallingState" Script. In here, in our "OnContactWithGround" method,  where we transition to the "Landing States", remove the "Mathf.Abs()" from  the "fallDistance" variable. We'll now always transition to the "Light  Landing State" in the cases we've just mentioned. That's all we need for this one, so for the next one, I've also previously said that we didn't need to update the target  rotation when we "Jumped" and had "Input", but with the addition of our "Landing States"  into our System, that's no longer the case. This is because our "Light Landing  State" does not allow "Movement", which means that our target  rotation will never be updated. This makes it so that if we  start "Jumping" to one side and we press the opposite side  key in the middle of the "Jump" and then "Jump" while in  the "Light Landing State", we will keep "Jumping" towards the old direction. To fix that, open up the  "PlayerJumpingState" Script. In the "Jump" method, inside  of the first if statement, before assigning our  "jumpDirection" a new value, type in "UpdateTargetRotation(GetMovementInputDirection());". This updates our target rotation to be  relative to our Input and the Camera, so replicating the situation we've  mentioned before should now work correctly. That should be enough but we  now have one final bug to fix. This bug has to do with our "Falling State". You might've noticed sometimes  that when we fall from a "Ramp", we don't enter the "Falling State". The problem lies in that we're  using the "bounds.extents" variable to set the size of our "OverlapBox". Apparently, Unity changes this variable, which means its values can  become something we don't want. I'm not entirely sure why,  but according to this post, it's because the bounds are  cubes in Unity and therefore when we rotate, it will change the extents. Our "CapsuleCollider" "Vertical Extents" property  is fine because we're using a cached extents, so even if it changes at runtime, we are not  getting that one but the initial extents. For our "OverlapBox" "bounds.extents"  though,, we'll have to cache it. Thankfully, we already have a reference to  our "BoxCollider", so we can easily do that. To do it, open up the  "PlayerTriggerColliderData" Script. In here, add in a new property by typing in "public Vector3 GroundCheckColliderExtents  { get; private set; }". Then, create an Initialization method by typing in "public void Initialize()" and inside type in "GroundCheckColliderExtents =  GroundCheckCollider.bounds.extents;". Of course, we need a way to call this method,  so open up the "CapsuleColliderUtility" Script. We could override the "Initialize" method but we  return here if the collider is already initialized so we'll create another method  instead, which we'll call here. So, under our "Initialize" method,  create a new method by typing in "protected virtual void  OnInitialize()" and leave it empty. Then, call it at the end  of our "Initialize" method. Back to our "PlayerCapsuleColliderUtility" Script, now "override" the "OnInitialize" method and call in "TriggerColliderData.Initialize();". If you use Composition, then you would  simply create an "Initialize" method here and call both properties  "Initialize" methods inside. We now need to use this new extents property,  so open up the "PlayerGroundedState" Script. In here, go to the "Main Methods" region  to our "IsThereGroundUnderneath" method. Inside, swap our  "groundCheckCollider.bounds.extents" with "stateMachine.Player.ColliderUtility .TriggerColliderData.GroundCheckColliderExtents". Our "bounds.center" is alright because we want  the center at the time we check for the "Ground". This should be enough to fix our bugs, so  saving it all up and going back to Unity, all of our Bugs should be solved. Of course, some of them were only visible with  Animations due to the Animation Transition Events. Talking about Animations, it's finally time for us to add them into our System. Not only will they make our system look better, they will also make it possible for us to transition to the States that required animation events. We've already downloaded our Animations before, so we're basically ready to use them. In case you haven't, make sure you do it first. In Unity, to control our animations, we need to create an "Animator Controller" and also add an "Animator" Component to an Object. The "Animator Controller" allows us to add transitions between multiple animations as well as set the conditions for those transitions. The "Animator" Component allows us to set the necessary data for those conditions through code or even Play or Stop a specific animation. In other words, it allows us to control the "Animator Controller" through code, among other things. It's also the Component that allows us to specify what's the "Animator Controller" of a specific Game Object. We'll be adding our "Animator" Component to our Character Model, so select it in our "Player" Game Object. In here, simply click on "Add Component" and search for "Animator". When that's done, simply add it by double clicking on it or pressing "Enter". I'll set the "Animator" "Culling Mode" to "Cull Update Transforms". Simply put, "Culling Mode" translates to "what should animations do when they're not on camera?". With our option, it will stop playing the animation when the Object exits the Camera, but still calculate where it should be, so that the next time it enters the Camera, it will start playing the animation at the right frame as if it never left in the first place. With that done, we now need an "Animator Controller" so go to the Player "Animations" Folder and create a new folder named "Controllers". Inside, we'll create a new "Animator Controller" by right clicking in the Project Window and going to "Create > Animator Controller", at around the middle area. I'll name it "PlayerAnimatorController". When that's done, select our "Character Model" and drag our "PlayerAnimatorController" to the "Animator" Component "Controller" field. Double clicking our Controller Asset should open up the "Animator" Window. I'll leave it docked right next to our "Input Actions" Window. Now, if this is your first time using an "Animator Controller", this Window is likely quite strange to you. While we won't be going through every available option or feature, we'll at least go through what we need to know. The Graph in the middle represents the area where our Animations and Transitions will stay. You can move it around by using the mouse middle button. Go ahead and open up the Player "Grounded" "Clips" Animations folder. Then, drag the "Idle" animation to this middle area in the "Animator" Window. Unity automatically creates a rectangle for us. This rectangle is called a "State". If we click on it, on the right side, we should see our "Idle" animation assigned to the "Motion" field. Unity also named our "State" the same as our dragged animation. With that, we know that in the "Animator Controller", we can have "States" that may have an animation attached to them. We can create a State without dragging an animation by right clicking on the Graph area and going to "Create State > Empty". We can rename it using the top field and we can attach a "Motion", or "Animation" if we want. So, "States" can represent "Animations" if a "Motion" field is attached, and it is through them that we'll add transitions to other "States". A "State" with no "Motion" attached to it can be used as a transition intermediary, such as "enter this State without animating and then check which State we should transition to next". To add a transition to another "State", simply right click on the "State" we want to transition from and choose "Make Transition". Then, left click on the "State" we want to transition to. This will create a transition from one "State" to another. We can see all of the transitions from the selected "State" to other "States" on the "Transitions" field or we can click a specific "Transition" line. In this "Transition", there are a few options that will be important for us to know, some which are within the "Settings" foldout. The first one is the "Has Exit Time" option. Simply put, this means that the "Transition" will only happen once the "Animation" of the "State" we're transitioning from has finished. This leads us to the "Exit Time" option, which controls what percentage of the "Animation" needs to be finished for the "Transition" to happen if we have the "Has Exit Time" option enabled. In this case, we aren't waiting for the whole "Animation" to finish but instead "75%" of it. We'll be disabling the "Has Exit Time" option for all of our "Animations", so this won't really matter too much for us. The next important option for our use cases is the "Transition Duration" option. This is simply how long does it take for this "Transition" to happen. The default "0.25" value means that it will take "0.25" seconds to "Transition" from one "State" to the other. A value of "0" would make it an instantaneous "Transition". Something to note here is that Unity seems to blend both "States" "Animations" when there is a "Transition Duration". This is cool because it makes our transitions look smoother. Of course, a big "Duration" would also mean it would take a long time to animate the Player with the second "State" "Animation". A value of "0" would also make it so there is no blending whatsoever and would make it a snappy "Animation Transition". Later on, we'll need to be careful when using "Animation Events" and setting up an "Animation Transition", but we'll understand why whenever we get there. The last important option for us is the "Conditions" field. Much like the name entitles, this specifies the "Condition" that we need to fulfill for this "Transition" to happen. This is what we'll be using to know if we should "Transition" from a "State" to another. Now, we do need something to "Condition" with. That "something" being a value, like a "boolean" value or a "float" value, which will allow us to add "Conditions" such as "Transition if we are walking" or "Transition if the speed is over 5 units". This is what the "Parameters" tab at the left side of the Window is for. To add a "Parameter", we click on the plus (+) icon and choose the type of value that we desire. Throughout all of this series we'll have one of our values be a "Trigger", while the rest of them will all be "Booleans". For our "Movement System" Part, we'll only be needing "Booleans". Lets add one. In our "Conditions", if we now add a new "Condition", our new "Boolean Parameter" should show there with a dropdown to choose if the parameter should be "true" or "false" for this "Transition" to happen. That's mostly all of the basics we'll need to know about the Animator Controller. However, right now, if we were to drag all of our animations into this area and make the necessary transitions, it would very likely end up being an horrendous mess, which people often call the "Animator Web". We'll be learning one of two things that can help us organize things better, which are "Sub-State Machines". We won't be taking a look at "Blend Trees", but we'll at least learn how to make our "Sub-State Machines" reusable. Of course, while this will reduce the amount of webs one area has, in a big game with a lot of animations, this will likely still cause an "Animator Web". There are probably a few assets out there, both free and paid, that can swap this "Animator" system from Unity with a cleaner solution, but we won't be using any. In case you're wondering what "Blend Trees" are, they are a nice way to blend between "Animations", often used in things such as "Movement" where you have a different "Movement Animation" per direction. If your Project is one of those, it will likely be worth it for you to go learn a bit more about them. Now, regarding about what are reusable "Sub-State Machines", they are simply normal "Sub-State Machines" made in a way that we can copy them into other "Animator Controllers" without caring about what "Transitions" to that specific "Sub-State Machine" exist nor what "Transitions" that "Sub-State Machine" has to the outside. It will likely become a bit more clear what this means when we start doing them. To create a "Sub-State Machine", we simply right click in the main area and select "Create Sub-State Machine". I'll name this one we've just created "Grounded". Feel free to remove the other "States" we've added. To go inside of this "Sub-State Machine", we simply double click on it. To go back, we either double click on the "(Up) Sub-State Machine", or we click once at the top in the "Base Layer" Tab. You might also have noticed that "Sub-State Machines" are represented by a diamond-like rectangle shape compared to the rounded rectangles that represent normal "States". Inside of this "Sub-State Machine", we can create other "States" or even other "Sub-State Machines". We're going to be doing both, as our animations will be organized much like our folders are. So, start by creating 3 other "Sub-State Machines": "Moving", "Stopping" and "Landing". For our normal "States", we'll drag our "Idle" and "Dash" animations to this Window area. Lets start by updating our "Moving Sub-State Machine". To do that, enter on it by double clicking it. Then, open up our Grounded "Moving" Animations folder and drag all of the animations here. I'll organize them from the left to the right following "Walk", "Run" and "Sprint". I'll also go ahead and organize the default "States" and "Sub-State Machines" from top to the bottom, where the "Up State" and the "Entry" Node will be on top and the "Any State" and "Exit" Nodes will be on the bottom. Because dragging our animations automatically sets the "States" with all the Data we need, all that's left to do is to set their "Transitions". When we've dragged our "Animations" in, Unity already set up a "Default Transition" to one of them, which is marked by an yellow arrow. A "Default Transition" means that when we "Enter" this "Sub-State Machine" and no other "Conditions" from the "Entry" Node are valid, or true, it will always enter the "Default Transition". This is why if we select that yellow "Transition", there is no "Conditions" field, or any field at all. If we wanted to change this "Default Transition" to some other "State", we would right click on the "Entry" Node and select "Set StateMachine Default State" and choose the "State" we want to "Transition" to by default. We'll set it to our "Run State", so if yours isn't, make sure it is. Now, from our previous transition code, we know that "Walking" can "Transition" to "Running" and "Running" can "Transition" to "Walking", but none of them can "Transition" to "Sprinting", as only "Dashing" transitions to "Sprinting". We also know that while "Sprinting" can go to both "Running" and "Walking", in Genshin it "Transitions" to "Running" and then the "Running State" takes care of "Transitioning" to the "Walking State". Knowing that, create a new "Transition" from the "Walk" to the "Run State". Then, create one from the "Run" to the "Walk State". Finally, create another one from the "Sprint" to the "Run State". We now need to set our "Conditions" for each of these "Transitions", so we need to create "Parameters". In the "Parameters" Tab, create 3 new "Parameters": "isWalking", "isRunning" and "isSprinting". If you haven't deleted the previous "Parameter" we created, feel free to do it now. When that's done, add these to the corresponding "Transitions", so: "Walk" to "Run" will have "isRunning is true". "Run" to "Walk" will have "isWalking is true". And finally "Sprint" to "Run" will have "isRunning is true". That's it for our "Transitions" between the 3 "States", but, why exactly did we create our "isSprinting" "Parameter" if we're not using it? The reason why is because "States" outside of this "Sub-State Machine" might want to "Transition" to a "State" that's inside of this "Sub-State Machine". Such is the case of the "Dash State", which can "Transition" to the "Sprint State". Whenever a "Transition" happens from the outside of this "Sub-State Machine" to its inside, it will first arrive at the "Entry" Node, which then takes care of "Transitioning" to the correct "States". So, add a "Transition" from the "Entry" Node to the "Walk State" and another one to the "Sprint State". Then, add the "Condition" of "isWalking is true" and the "Condition" of "isSprinting is true", accordingly. If none of these "Conditions" is valid, then our "Sub-State Machine" will transition to our "Run State", as it has the "Default Transition" set to it. Note that this also means that we only need the "isRunning Parameter" because we need it in the "Transitions Conditions" between the 3 "States" themselves, as the "Default Transition" doesn't require it. There's now only one "Transition" left, which is a "Transition" from our "States" to the outside "States". Now, normally, people would likely go ahead and add "Transitions" from these "States" directly to the outside "States". This can be done by creating a "Transition" to the "Up" Node. This should bring up a menu that shows every existing "State" and "Sub-State Machines" that are outside of this "Sub-State Machine". While this works fine, this is basically setting up a dependency, which makes it so that this "Sub-State Machine" is no longer reusable. The reason being that we're directly "Transitioning" to a "State" or "Sub-State Machine" from the outside of this "Sub-State Machine". So, if we copied this "Sub-State Machine" elsewhere, we would also copy those dependent "Transitions". This isn't what we want. What we want is to know that we indeed can "Transition" to a "State" from the outside, but we want to decide what that "State" is outside of this "Sub-State Machine". When we copy a "Sub-State Machine" that defined its "Transitions" outside, those "Transitions" won't be copied with it. However, we do need a way to tell our "Sub-State Machine States" that they can "Transition" to the outside, without directly telling them what "States" they can "Transition" to. The way we do that is by using the "Exit" Node. When a "Transition" goes to the "Exit" Node, it will "Exit" this "Sub-State Machine" and stays in the "(Up) Sub-State Machine" checking for whatever "Conditions" it has. This makes it so that we can add our "Transitions" outside of the "Sub-State Machine", which means that if we want to add a new "Transition", we don't need to update the insides of a "Sub-State Machine" and can simply add that "Transition" outside. Knowing that, create 3 new "Transitions", one from each "State" to the "Exit" Node. For our "Condition", we'll create a new "Parameter" of type "Bool" and name it "Moving". I'll move this "Parameter" up. Then, add it to every "Condition" of our new "Transitions" and set it be "Moving is false". Note that we need to add it one by one, as selecting all of them doesn't really edit them all. Now, this "Moving Parameter" is also how we're going to be transitioning to this "Sub-State Machine" without caring about what "States" are inside. If from the outside, we create a "Transition" from a "State" to this "Sub-State Machine" and make its "Condition" be "Moving is true", we can easily add or remove "States" from the inside of the "Moving Sub-State Machine" without breaking existing outside "Transitions", as those outside "Transitions" do not care at all what "States" we have inside, but simply that they need to "Transition" to that "Sub-State Machine" when we start "Moving". That's it for our "Moving Transitions", but we should of course set each "Transition" setting correctly. So, one by one, disable the "Has Exit Time" option, which renders the "Exit Time" field useless and leave the "Transition Duration" as "0.25". You might've noticed that some of our "Transitions" are greyed out and don't allow us to set any "Settings". This happens when our "Transition" is an "Entry Transition". The settings of these "Transitions" are set through our "Exit Transitions". This simply means that when we "Enter" a "Sub-State Machine" through a "Transition" from the outside, its "Entry" Node "Transition" settings were defined by that outside "Transition". So, for example, our "Moving Sub-State Machine Exit Transitions" define the settings of our future "Stopping Sub-State Machine Entry Transitions". This makes sense because our "Exit Transitions" are the "States" we're transitioning from. For example, we'll be "Transitioning" from the "Walk State" to an outside "State" and the "Walk State" defines that there should be no "Exit Time" when we're "Transitioning" from it. Of course, this also means that the "Entry Transitions" are the "States" we're transitioning to, so they shouldn't be the ones deciding the "Transition" settings. That's it for our "Moving Sub-State Machine", so in the "(Up) Sub-State Machine", lets add "Transitions" from the other "States" to this one. Before we do that though, I'll organize them a bit. I'll add the "Landing" to the "top-left", the "Moving" to the "bottom-left", the "Stopping" to the "top-right", the "Dash" to the "bottom-right" and the "Idle" to the "middle-up". I'll also make sure that the "Idle State" is the "Default State". The "States" or "Sub-State Machines" that can "Transition" to the "Moving Sub-State Machine" are: "Landing", "Stopping", "Idle" and "Dash". The condition for all of these 4 "Transitions" will be "Moving is true". Now, you might've noticed that only our "States" have a white transition. This is because in normal "States" there are no "Exit Transitions" as there's no "Exit" Node, so our created "Transition" can be considered their own "Exit Transition". This means that we need to define their "Transition" settings here. Because of that, in both the "Idle" and "Dash Transitions", we'll be disabling the "Has Exit Time" option. Make sure the "Transition Duration" is always set to "0.25", which should be the default value. As a test, if we were to duplicate our "Moving Sub-State Machine", it would come without the "Transitions", which basically makes it so that we can copy it to other "Animator Controllers". As a side note, if you're wondering about the "Any State" Node in the "Sub-State Machine", it means "Any State" in the whole "Animator Controller" and not for that specific "Sub-State Machine". As far as I know, there is no built-in Node for the later. That's basically all we need to know regarding our "Animator Controller", so all we need to do now is to add the remaining "Animations" and their "Transitions". Lets start with our "Stopping Sub-State Machine", so double click on it to open it up. Then, open the "Stopping" Clips Animations Folder and drag the "Animations" into the "Sub-State Machine". I'll order them as "Medium", "Light" and "HardStop" as I like to keep the "Default Transition" in the middle, which will be our "LightStop State". I'll also organize them before we keep going. When that's done, I'll add a "Transition" from the "Entry" Node to the other 2 "States", as all the "States" in this "Sub-State Machine" can be "Transitioned" into from outside "States". Then, add a "Transition" from all "States" to the "Exit" Node. We'll not add any "Transition" between the 3 "States" as they can't "Transition" between each other. In every white colored "Transition", disable the "Has Exit Time" option. We'll only need 3 new "Parameters" here, so add in "Stopping", "isMediumStopping" and "isHardStopping". From the "Entry" Node to the "States", add the "Condition" of "isMediumStopping is true" for one and "isHardStopping is true" for the other. From the "States" to the "Exit" Node, add the "Condition" of "Stopping is false". We don't need a "Parameter" for our "Light Landing State" because it uses the "Default Transition", which doesn't require a "Condition" and also because no other "State" inside of this "Sub-State Machine" "Transitions" to it. That's all we need here so go up to the "(Up) Sub-State Machine". The "States" that will be able to transition to this "Sub-State Machine" are: "Moving" and "Dash". Make sure you're "Transitioning" to the "Sub-State Machine" and not a specific "State". The "Transition Conditions" are of course "Stopping is true". Again, select the white "Transitions" and disable their "Has Exit Time" option. For our last Grounded "Sub-State Machine", we have the "Landing Sub-State Machine", so open it up. Open up the "Landing" folder and drag the existing "Landing Animations" here. I'll organize the area here as well. We'll make the "Light Landing" the default "Animation". Because it is the default one, we don't need a "Condition" nor a "Parameter" so simply "Transition" it to the "Exit" Node. Disable the "Has Exit Time" option from the "Transition" and then create a new "Parameter" named "Landing". Add a "Condition" of "Landing is false" to the "Transition". Add a "Transition" from the "Entry" Node to the other 2 "Landing Animations". Create 2 new "Parameters": "isRolling" and "isHardFalling". Add them in their respective "Conditions" in the "Entry Transitions". Then, add another "Transition" from the other 2 "Landing States" to the "Exit" Node and add the "Condition" of "Landing is false" to both "Transitions". Untick the "Has Exit Time" option in both of them as well. That's really it so go to the "(Up) Sub-State Machine". The following "States" can "Transition" to this "Sub-State Machine": "Jumping" and "Falling". Of course, we don't yet have our "Jump" and "Falling States" here, but that doesn't really matter, as all that we need to do is to add a "Transition" from the "Entry" Node to the "Landing Sub-State Machine" with a "Condition" of "Landing is true". This is the good thing about reusable "Sub-State Machines", we don't need to care what "States" from the outside transition to it. One thing you might've noticed is that inside of our "Sub-State Machines" our "(Up) Sub-State Machine" might transition to the "Entry" Node. These are the "Transitions" of the outside "States" or "Sub-State Machines" to this one, but we don't need to worry about them as if we duplicate the "Sub-State Machine", they won't be present in the new one. All that's left now for our "Grounded States" are our "Idle" and "Dash States". Because these aren't "Sub-State Machines", we don't really need to do anything else besides adding their "Transitions". Knowing that, we'll be able to "Transition" to the "Idle State" from: "Landing" and "Stopping". The other 2 will go to the "Stopping State" and not to the "Idle State". We need to create a "Parameter" for this "State" so add a new "Boolean" and name it "isIdling". I'll drag the "Parameter" to the top. Then, add the "Condition" of "isIdling is true" for both "Transitions". Because no "States" directly "Transition" to this one, none of them are white colored "Transitions", meaning that their settings were set from the respective "Sub-State Machine Transitions" to the "Exit" Node. For our "Dash State", we can "Transition" from: "Landing", "Stopping", "Moving" and "Idle". Create a new "Parameter" of type "Bool" named "isDashing". I'll move it under the "isIdling" "Parameter". Then, add the "Condition" of "isDashing is true" to every "Transition". For the "Transition" between the "Idle State" and the "Dash State", we'll also need to disable the "Has Exit Time" option. That's it for our "Grounded Sub-State Machine", so we now need to do our "Airborne Sub-State Machine". Of course, every "Grounded State" can enter the "Airborne State" known as "Jumping State" and also the "Falling State". This means that we need a "Transition" to the "Exit" Node from every "State" to be able to "Transition" to them, so go ahead and add a "Transition" from every "State" to the "Exit" Node. For the "Condition", add a new "Bool Parameter" named "Grounded" above all others and set the "Condition" for each "Transition" to be "Grounded is false". For the white "Transitions", make sure you also disable the "Has Exit Time" option. Back into our "Base Layer", add a new "Sub-State Machine". I'll name it "Airborne". I'll organize them a bit. All Nodes can be above the "Entry" Node as we won't use them. I'll also place the "Airborne Sub-State Machine" a bit to the left as later on in the series we'll add the "Swimming" one to the right. We'll need to "Transition" from and to the "Grounded Sub-State Machine", so add a "Transition" from both sides. Then, create a new "Bool Parameter" named "Airborne". For the "Transition" from the "Grounded Sub-State Machine" to the "Airborne Sub-State Machine", we'll add a "Condition" of "Airborne is true". For the opposite "Transition", we'll set a "Condition" of "Grounded is true". In my original system I also added the "Current State is false Condition" due to a bug that I don't really remember, but I couldn't seem to get any bug with the way we have things right now, so I'll leave it as is. If you do end up finding any bug regarding animations between different "Sub-State Machines", then you might want to try and add that extra "Condition". Unless my brain isn't thinking correctly though, there should be no problems and it's possible that the bug was fixed through finishing the System. Opening the "Airborne Sub-State Machine", we need to drag our "Jump" and "Fall Animations" here so make sure you go to the right folder and drag them in. I'll organize them a bit. We'll make our "Jump Animation" the default "Animation". We'll be able to "Transition" from the "Jump State" to the "Fall State", so add in a new "Transition" to it. Then, create a new parameter named "isFalling" and add it as a "Condition" to the "Transition" we've just created. Of course, it "Transitions" when it's "true". Make sure to untick the "Has Exit Time" option as well. Then, add another "Transition" from the "Entry" Node to the "Fall State" with the "Condition" of "isFalling is true". When that's done, add a "Transition" from both "Animations" towards the "Exit" Node with the "Condition" of "Airborne is false" in both of them. Again, make sure they have the "Has Exit Time" option unticked. With that done, our "Animator Controller" is finished and all we need to do now is to set the "Parameters" to either "true" or "false" through code and also set up our "Animation Events". Lets start by setting our "Parameters" first. We'll be doing that using the "Animator.SetBool()" method. This method accepts a "string", which is the name of the "Parameter", or an "int", which is the "ID" of the "Parameter". The "ID" here is an "Hash" that we can get from the "StringToHash" method. If we pass in a "string" to the "SetBool" method, it will call this "StringToHash" method to transform it into the "Hash" integer. That means that any "SetBool" call with a "string" as the argument will need to do that conversion. Because of that, it would be way faster for us if we instead passed in that "Hash" right away. Of course, we ourselves don't know that "Hash", but the fix for that is quite simple: We simply need to cache that "Hash" once by using the "StringToHash" method and then pass it in into the "SetBool" method. This ensures that the "StringToHash" method is only called once per "Parameter", instead of being called once on every "SetBool" call, which should lead to a better performance. Of course, this all assumes that the "StringToHash" method doesn't cache the data into an "HashSet". If it does, then there shouldn't really be much or any performance difference. However, regardless of it caching it or not, it's still a great idea to cache it in as that makes it possible to change its name through the "Inspector" whenever we want and the changes will be seen across all usages. To do this, in Unity, open up the Player "Data" Scripts folder and create a new folder named "Animations". Inside, create a new C# Script named "PlayerAnimationData". Open it up and remove the default methods and inheritance and make the class "[Serializable]". We'll need two things for each "Parameter": Its "name", which is the same as the one we've set in our "Animator Controller" and a property to store our "Hash". Lets start with our names. I'll create an "[Header()]" with the text of "State Group Parameter Names" and then, type in "[SerializeField] private string groundedParameterName;" and default it to "Grounded". Then, duplicate this line 4 more times and swap "grounded" with "moving", "stopping", "landing" and "airborne". Make sure the actual strings have the exact same name as the "Animator Controller Parameters", even the upper cased letters. I'll now create another "[Header()]" with the text of "Grounded Parameter Names". Copy one of the lines above and paste them here. I'll paste it in a total of "10" times. When that's done, we'll swap the variable names and default values with "idle" and "isIdling", "dash" and "isDashing", "walk" and "isWalking", "run" and "isRunning", "sprint" and "isSprinting", "mediumStop" and "isMediumStopping", "hardStop" and "isHardStopping", "roll" and "isRolling", "hardLand" and "isHardLanding" and "fall" and "isFalling". Remember that some of our "States" don't have a "Parameter" as they use "Default Transitions", so we don't need to add them here. If you ever intend to add "Parameters" for them in the future, then you can also create a variable for them if you want to, but I think you need to make sure they also exist in the "Animator Controller". That's it for our "Grounded strings", so we now need to create our "Airborne strings". To do that, copy the first two lines, including the "Header". Then, paste them in and swap "Grounded" with "Airborne" and set the variable name to be "fallParameterName" and its value to be "isFalling". With that done, we now need to create properties for our "Hashes". To do that, create a new public property by typing in "public int GroundedParameterHash { get; private set; }". We don't need the "[field: SerializeField]" here as we don't need to set these through the "Inspector", as they are generated by the "StringToHash" method. Duplicate this line for each variable we have above and rename them accordingly. That's done, so we now need to initialize them. To do that, create a new method by typing in "public void Initialize()" and inside type in "GroundedParameterHash = Animator.StringToHash(groundedParameterName);". The "Animator" here is the "Animator" class itself and the "StringToHash" is simply a "static" method. Do the same thing for every single "Parameter". When that's done, make sure the default names are correct and save. If you saved and they weren't correct, make sure that you update them correctly in the "Inspector" as well when we add it to the "Player" Script. To add it, open up the "Player" Script. In here, create a new "[field: SerializeField] public PlayerAnimationData AnimationData { get; private set; }". I'll also add in a "[field: Header("Animations")]". Now, in the "Awake" method, call in "AnimationData.Initialize();". We now have a reference to our "Parameters Hashes". Of course, we'll need to use the "Animator.SetBool" method, which means we need a reference to our "Animator". To do that, create a new property by typing in "public Animator Animator { get; private set; }". Then, in the "Awake" method, type in "Animator = GetComponentInChildren();". Note that "GetComponentInChildren" first searches in the parent and only then in its "Children", which doesn't really make sense given the name. If you wanted it to search only in the "Children", then you would probably iterate through its children with the "GetChild" method or through its transform, if that works. Because we really only call it once and because we're searching for the "Animator" of the Player, which there should likely only be one, I'll leave it as is. With that, we'll need to start setting our "Animator Parameters". To do that, first open up the "PlayerMovementState" Script. We'll create a method here to not need to call the "SetBool" method every time, so in our "Reusable Methods" region create a new method by typing in "protected void StartAnimation(int animationHash)". Inside, call in "stateMachine.Player.Animator.SetBool(animationHash, true);". We'll also need one to set it to "false", so duplicate this method and swap the "Start" with "Stop" and the "true" with "false". With that done, we simply need to call these methods in each State "Enter" and "Exit" methods, both to "Start" and "Stop", respectively. So, start by opening up the "PlayerGroundedState" Script. In the "Enter" method, type in "StartAnimation(stateMachine.Player.AnimationData .GroundedParameterHash);". When that's done, copy this line. Then, "override" the "Exit" method and paste the line we've just copied. In here, simply swap "StartAnimation" with "StopAnimation" instead. That's all we need to set a "Parameter" value, so all that's left is to do the exact same thing for the rest of the "States". So, open up every "State" and call both methods for the "States" that have a "Parameter". That's all we need to do, so when you're done doing that, save everything and go back to Unity. If we enter Play Mode, our "Animations" should now be playing whenever a "State" changes. However, you might be noticing a problem when doing something like "Running" and then "Jumping". This seems to happen because there's a prioritization order for the "Transitions", likely because you can have 2 conditions being "true" at the same time, so Unity needs to prioritize one of them. In this case, if it happens to you, it's likely that you're prioritizing a "Transition" to something like a "Stopping State" or "Idle State" instead of prioritizing to the "Jump State" or in this case the "Exit Node". To fix that, exit Play Mode and go to the "Animator Controller". Clicking any "State", we should see the "Transitions" it has. We can drag them and set their order by holding the 2 small bars icon. I'll be changing their order to be: "Exit", "Moving", "Dash", "Stopping", "Landing" and "Idle", in that order. Do it for every "Grounded State". For the "Airborne States", we'll focus on the "Exit" one and then on the "Fall". This should now make it work more correctly. Of course, we still need to take care of our "Animation Events". To do that, exit Play Mode if you entered it and select the "Character Model". Then, in the "Project Window" tab, right click to "Add a new Tab" and select "Animation" in case you don't have one open. This tab shows the animations that are in the "Animator Controller" of our currently selected Game Object. This is where we can add our "Animation Events". If we move to any frame that we desire and click on the last small icon with the Tooltip of "Add Event", we'll add an event to the selected frame, which should show in the frame column. Clicking on it shows us the "Animation Event" Window, where we can select a public method to call. We however have two problems here: The first problem is that we have no way of calling our "State Events" as only methods that are part of a Script in the same Game Object as the "Animator" Component show in this dropdown list. Because our Model is separate from our Player, this means that we can't access our Player Script code. Furthermore, none of our "States" are "MonoBehaviours", so it isn't possible for us to add them as Components, which means that we have no way of directly calling their methods. The second problem is regarding our "Animation Events" and the "Transition Duration", but we'll take a look at it after we fix the first problem. To fix it, we can create a public method in our "Player" Script that calls the State Machine "Animation Events" methods. However, our "Animator" currently sits on a child Game Object of the "Player" Game Object, which again, means that we have no access to our Player Script methods. To fix that, we'll have to create a new class that references the "Player" and calls its public methods. We can then attach that Script as a Component of the "Character Model", which allows us to use it in our "Animation Events". So, start by removing the "Animation Event" we've just added by right-clicking it and pressing "Delete". Then, open up the "Player" Script. In here, we'll need to create one method for each of the "Animation Events" methods, so type in, at the bottom, "public void OnMovementStateAnimationEnterEvent()" and inside, call in "movementStateMachine.OnAnimationEnterEvent();". Duplicate this method twice and swap "Enter" with "Exit" for the first one and then "Enter" with "Transition" for the second one. When that's done, save it up and go back to Unity. In here, navigate to the Player "Utilities" Scripts folder and create a new folder named "Animations". Inside, we'll create a new C# Script named "PlayerAnimationEventTrigger". Open it up and remove the default methods. Because we'll add this as a Component, we need to keep the "MonoBehaviour" inheritance. In here, create a "private" variable of type "Player" named "player". Then, in the "Awake" method, get it by typing in "player = transform.parent.GetComponent();". There's also the "GetComponentInParent()" method, but that one also calls the "GetComponent" in the Game Object we call it in, much like the "GetComponentInChildren()" method, so we'll use our current solution. When that's done, we'll create a new method to call the events by typing in "public void TriggerOnMovementStateAnimationEnterEvent()" and inside call in "player.OnMovementStateAnimationEnter();". Duplicate this method twice and swap "Enter" with "Exit" for the first one and "Enter" with "Transition" for the second one. When that's done, save it up and go back into Unity. Then, in our "CharacterModel" Game Object, add the "PlayerAnimationEventTrigger" as a new Component. Back to our "Animation" tab, we can now add in our "Animation Events". Click on the dropdown on the left and select "Dash". We'll need to transition to our "Sprinting State" here and we'll do it around half way into the "Animation", so select a frame around the middle, to which I'll chose the "0:11" mark and add a new "Animation Event". Then, select the "Transition Event" method in the "Animation Event" Window. Note that you can add multiple "Animation Events" to the same frame if you wish to. When that's done, open up the dropdown again and select "LightStop". The "Stopping States" will simply "Transition" to the "Idling State" at the end of their animation, so around the last frame or around the "1 second" mark, as the "Animation" is somewhat coming to a "Stop" here, we'll add a new "Animation Event". Call in the "Transition Event" method. Do the same in our "MediumStop Animation". In our "HardStop Animation" we'll add it a bit earlier, around the "0:20" mark. Then, in our "LightLanding Animation", we'll have an "Animation Event" "Transitioning" to the "Idling State" at the last frame. Next, in our "Rolling Animation", add a new "Animation Event" around the last frame, I'll place it at the "1:02" mark, "Transitioning" to the "Medium Stopping" or "Moving States". I added it at around the "1:02" mark as that looked a bit better for me. If the "Transitions" don't look too good, you can always add them earlier or later, as you desire. Next, for our "HardLanding Animation", we'll need 2 "Events". One will be for enabling our "Movement Input", while the other one will be to go to the "Idling State". I'll add the first one at around the "1:11" mark, which should be around when we're getting up and call in the "Exit Event" method. Then, I'll add the second one at around the "1:25" mark and call in the "Transition Event" method. And that should be it for our "Animation Events". However, we still need to talk about our problem regarding "Animation Events" and "Transition Durations". Lets enter Play Mode to see what the actual problem is. In here, lets "Dash", wait a bit and "Dash" again. We can see that our "Player" just stopped "Dashing" right after our second "Dash". The reason why is because "Animation Events" can still be called when we're in the middle of a "Transition". This is possible because while we're "Transitioning", our previous "Animation" is still happening, or running, until we fully transition to the other. An even bigger problem with this is that we'll already be in our new "State", so the event that will be called will be the one of that new "State". In our case, we entered the "Dashing State" and then the "Hard Stopping State" and then "Dashed" near the "Animation Event" of the "HardStop" animation. Because in code we have already swapped to the "Dashing State", the "Animation Event" ended up calling the "Dashing State Event" instead and entered the "Hard Stopping State" again, as that's what we do in the "Dashing State Transition Event" method. Thankfully, we can easily fix this by not calling an "Animation Event" if we're in the middle of a "Transition". To do that, open up our "PlayerAnimationEventTrigger" Script. Then, create a new "private" method that returns "bool" named "IsInAnimationTransition". We'll add a parameter of type "int" named "layerIndex" and default it to "0". While we won't be using "Layers" here, the method we'll call requires it so it's always nice to have the possibility of passing it in. To be honest, I don't really know much about "Layers", but this "Index" is the index of the "Layer" our "Animations" are in the "Animator Controller", which should be the first and only one, so it's the index of "0". Inside of this method, simply type in "return player.Animator.IsInTransition(layerIndex);". Then, in all of the other methods, before we call our Player "Animation Events", check "if (IsInAnimationTransition())" and "return;" if that's the case. Copy the if statement to the other 2 methods as well. When that's done, save it up and go back to Unity. Entering Play Mode, we should no longer have this problem and our "Animations" and "Transitions" should all be working fine. If you did want to call the previous "State Animation Event", you could do that by adding a new variable to our "State Machine" that held the "previousState" and simply call the "previousState Animation Event" method instead. Of course, you would need a new method in our "Player" Script to do that. Also note that if our "Transition Duration" was "0 seconds", this problem wouldn't happen as the "Transition" would be instantaneous. Someone also wondered if we were gonna add the double Animation that Genshin offers, but I have decided when I started trying to replicate this System that I would not do it. In case you're wondering what do I mean by "double Animation", it's simply that Genshin seems to have 2 animations in certain "States" like "Jumping" or "Stopping". If we take a look at it, we can see the arms and legs that go to the front can be different. I'm not sure if they do this using IK or simply have 2 Animations, but the leg and arm that go to the front seem to depend on the foot that was at the front as well. For example, if you "Jump" when your right foot is ahead, you seem to "Jump" with the "Left Knee" forward. It does seem to only happen after the feet has touched the "Ground", as if you "Jump" before that, you'll "Jump" with the same side "Knee" forward. According to a forum post, if you're using a "Generic" Model, which we are, for no particular reason besides me not really understanding very well how Models and Animations work, we can know which foot is at the front by using "Animation Events". You would simply go to each step in the "Moving States Animations", for example, and whenever the foot touches the "Ground" in the "Animation", call in an event that sets a variable that tells you what foot is currently at front. If you end up using an "Humanoid" Model, which is probably what we should've been using, you can know it using the "animator.pivotWeight" variable. Although, it seems that this doesn't work for some if they have the "Root Animation" option enabled and they had to use something else like the "Animator.GetBoneTransform()" method. For the animations itself, it's possible that they do it with IK by setting where the knee should go, or you could flip the animation and add both "Animations" to an array or simply 2 variables, which you would then choose an "Animation" depending on which foot is currently at the front. Of course, we won't be doing it ourselves as that's what I've decided myself before. Regardless, our "Animations" should now be working fine and we should now be able to "Transition" to every existing Player "State". With all that done, the first part of our Genshin Impact Movement System is finalized. This took quite a lot longer than I expected it would, but I hope you were able to understand most of it without too much trouble. Of course, we still have 2 Systems left to do, which are the "Gliding" and the "Swimming" Systems. Thankfully, both use the "Movement" System as their base, so they're not really very hard to create. However, I will be placing this series into an halt for a while. Both Systems do still need some fixing, as I've left them unfixed to be able to start the first part, but that's not really the reason why. The simple reasons for why it's coming to an halt is "Money" and "Motivation". A little over a year ago I got laid off from work and decided to try and get into game dev and keep going if I was able to earn a somewhat good enough income within around a year. Unfortunately, the amount of money I'm making right now is "0€", which is the currency my country uses. For those who are interested, it roughly translates to "0$". This is of course not sustainable, which means I can't really keep things the way they are, as otherwise I'll have to stop making videos as I'll have to go find work and well, work. Unfortunately, I can't really record audio after work hours, so doing both isn't very plausible, which means I would likely not do any more videos for a few years. Before that happens though, I'd like to try and earn some money through game dev so I'm thinking on trying to make a few small games in hopes that it gets me some income. I do plan on finishing the series if everything goes well, but if things don't go well then that'll depend on how bad it went, but hopefully I'll be able to finish it up before I leave to work. I did think about a Patreon but I know I won't get enough income with it to keep going so I would still have to leave, which would make me feel like I was kind-of scamming people, which I don't really enjoy the feeling, so I removed that consideration. Games are also what I'm mostly interested on and that's what I wanted to focus myself on when I got into game dev, but in this a little over a year I've mostly focused on tutorials and not on making games, which brings me to the second point of "Motivation". To be completely honest with you, I've been feeling quite unmotivated to edit videos and I think I've felt that in the last videos where I kinda just threw things around without caring too much even if there were errors on the videos. I also started slacking quite a lot and haven't slept very well for a while now, so I haven't really been enjoying these days. I will try to rest for a week or so and then likely try and make some small games to get my motivation up and hopefully get some income with it so that I can keep doing this. Because of that though, I won't be continuing with this series for a while so our "Gliding" and "Swimming" Systems won't be coming so soon. As a tip though, the main feature of the "Gliding" is simply limiting the velocity much like we did for our "Falling State" but with a lower value, while "Swimming" is mostly freezing your vertical position when you enter the water. I also added a GitHub repository where you can check the code we've done so far for our "Movement System" Of course, it does not contain the "Gliding" and "Swimming" Systems code, as they aren't really fixed yet. I do apologize for this outcome and I do hope that you can understand it. Regardless, I hope you've enjoyed the series so far and learned something new with it. The full video with the first part should also be coming somewhat soon. It's quite a long video to render so it will very likely come with a lot of audio artifacts so I apologize for that right now and hope that it doesn't come with any video artifact whatsoever. Until then though, see you in the next one.
Info
Channel: Indie Wafflus
Views: 23,871
Rating: undefined out of 5
Keywords: genshin impact movement in unity, genshin impact movement, genshin impact in unity, genshin impact unity tutorial, genshin unity, genshin in unity, genshin movement in unity, unity animations, unity 3d movement, unity floating capsule, unity physics based movement, unity rigidbody movement, unity genshin movement tutorial, unity 3d movement tutorial, unity third person movement, unity genshin impact, unity state machines, unity movement, unity physics, unity cinemachine
Id: kluTqsSUyN0
Channel Id: undefined
Length: 532min 52sec (31972 seconds)
Published: Fri Apr 15 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.