Search Unity

Proper Velocity-Based Movement 101

Discussion in 'Community Learning & Teaching' started by RobAnthem, Mar 23, 2017.

  1. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    Basic Movement 101
    In this tutorial we will cover the main aspects of a basic movement script, that will allow user input to make a 3D object, or "player" move around in a 3D world.

    If you don't already have a Unity project started, go ahead and create a new one.



    This tutorial is for Novices and requires at least basic knowledge of C#, it will cover the following things.

    • Script Structure
    • Input
    • Rigidbodies
    • Player Movement
    • Jumping
    Section 1 - Script Structure
    Create a new script in Unity by right clicking on the Project file-system and click C# Script. It should look like this.



    By default, Unity creates new scripts as MonoBehaviours, these are scripts intended to be attached to a Unity GameObject in a scene or on a Prefab. If you have no need of the base properties that come with a MonoBehaviour, then another type of class is the optimal choice such as ScriptableObject, or a standard derived/non-derived class.

    The main reason to make a MonoBehaviour is if you have need of one or more of the following Unity Functions that comes with the base class, the most common functions are:

    • Awake() is called once during the lifetime of the script, on its initial awake state, prior to any other functions.
    • Start() - This function is called after Awake() when the script is started for the first time in an instance. Think of it as a secondary initialization.
    • OnEnable() or OnDisable() - These functions are called whenever the object gets enabled or disabled. OnEnable() will also be called from the first enabled instance of a class.
    • Update(), FixedUpdate(), and LateUpdate() are all called every frame. Update() is called at the start of each frame, FixedUpdate() is called at a fixed frame time, allowing for smoother Updates, and LateUpdate() is intended to be a final Update, incase you need something to occur after your other Updates.
    Because a MonoBehaviour MUST be attached to a GameObject, it provides us with functionality to reference the GameObject, or any script attached to it, its parents, or its children.

    Most common practice is to put your variables at the top of your class, and all of your functions under them. Example below.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Movement : MonoBehaviour {
    6.  
    7.     //Variables at the top
    8.    public float Speed;
    9.    //Methods below
    10.      void Start () {
    11.  
    12.     }
    13.    void FixedUpdate () {
    14.  
    15.     }
    16. }
    This covers basic script structure.

    Section 2 - Input
    Unity, by default, has most input axes that you would need already setup. You can add new ones any time you like and reference them through script the same as you would the default ones.

    The Input class of Unity is located in Edit > Project Settings > Input.



    Throughout this tutorial, we will be using the Horizontal, Vertical, and Jump input axes. The sensitivity can be altered both from the input manager, and from our own classes speed variables. These same axes are used on all of Unitys supported platforms, making the cross-platform development process much easier.

    Section 3 - Rigidbodies
    There are multiple ways in Unity to move a GameObject, but only the ways that use a Rigidbody are actually capable of physics and collision detection. Because our player needs to be able to collide with walls, other entities, or whatever else might be in your worlds like Terrain, we will need physics to move the player.

    By default any object with a Rigidbody component will have physics applied to it, however to create collisions, the object must contain both a Rigidbody AND a collider.

    Rigidbodies have a lot of options, especially through code, and there is more than one way to move a Rigidbody. We are going to use the Velocity method, because this gives us those most control with the least jitters.

    Section 4 - Player Movement
    To move our player we are going to need some variables and a reference to the in-scene Rigidbody object attached to the player. A quick breakdown of the variables we will be including:

    • body - This is what we will call the Rigidbody component attached to the players GameObject.
    • speed - This will be the speed at which the player moves.
    • rotationSpeed - This will be the speed at which the player rotates left or right as it moves.
    • Time.fixedDeltaTime - This is the actual time in milliseconds between each fixed update frame, we will be using this to scale the movement to look smooth.
    • transform.forward - This is the forward Vector3 of the transform attached to the GameObject we are referencing, this will allow us to move the object Player on its own forward axis.
    • transform.up - This is the upwards axis of the player object, based on its forward position, we will be using this for rotation purposes.
    • Rigidbody.velocity - The velocity parameter of the rigidbody in a Vector3. If the entity is not moving, this will equal (x = 0, y = 0, z = 0).
    So start by adding the variables body, speed, and rotationSpeed to your script.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Movement : MonoBehaviour {
    6.  
    7.     private Rigidbody body;
    8.      public float speed;
    9.      public float rotationSpeed;
    10.      //Methods below
    11.      void Start () {
    12.  
    13.     }
    14.      void FixedUpdate () {
    15.  
    16.     }
    17. }
    Now none of these variables have been assigned values yet, by default a float is never NULL, so they automatically equal zero, of course if we left it this way, then the player would never move. Since we made our Rigidbody private, we have to acquire our reference through code, which we can do in our Start() function and using a GetComponent call like below. GetComponent should be used as little as possible because it does have some overhead. Essentially what it does is it iterates through all MonoBehaviours attached to the GameObject and comparing the class type, to the type you are looking for, and if it finds it, then that is the component returned.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Movement : MonoBehaviour {
    6.  
    7.     private Rigidbody body;
    8.      public float speed;
    9.      public float rotationSpeed;
    10.      //Methods below
    11.      void Start () {
    12.  
    13.         body = GetComponent<Rigidbody>();
    14.  
    15.     }
    16.      void FixedUpdate () {
    17.  
    18.     }
    19. }
    Now to assign our speed and rotationSpeed, we can assign them in the inspector once the script is attached to a GameObject. If you do not already have a Player object, add standard Unity Cube to the scene.



    Then add the script of Movement to the Cube in the Inspector Tab.



    Then add a Rigidbody to it, the same way as you did the Movement. It should look something like this.

    .

    As you can see, I've set Speed to 50, and Rotation Speed to 25. These are the in-editor representations of our variables speed and rotationSpeed.

    The next section of our script will be acquiring the information about an input axis, and using it to move the character. The important usage will be Input.GetAxis() method. This returns a float number between -1.00f and 1.00f. With a lot more zeros. So we will be making our velocity of our RIgidbody based on our forward position, multiplied by our input, multiplied by our speed, and then multiplied by our time so that our speed becomes a consistent amount over the course of a single second, and makes the movement smooth. Because we are going to be calling our vertical and horizontal movements each frame, it makes more sense to make them a variable of he entire scope, so we will add them as well.

    So now we will add our first stages of movement, being able to move Forward or Backward.

    So go ahead and update you Movement script to look more like this.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Movement : MonoBehaviour
    6. {
    7.  
    8.     private Rigidbody body;
    9.     public float speed;
    10.     public float rotationSpeed;
    11.     private float vertical;
    12.     private float horizontal;
    13.     void Start()
    14.     {
    15.         body = GetComponent<Rigidbody>();
    16.     }
    17.     void FixedUpdate()
    18.     {
    19.         vertical = Input.GetAxis("Vertical");
    20.         horizontal = Input.GetAxis("Horizontal");
    21.         body.velocity = (transform.forward * vertical) * speed * Time.fixedDeltaTime;
    22.     }
    23. }
    Before we test the script, we will need to make it so our camera can see our movement, better yet, let's make the camera follow us. The simplest method is childing the Camera to the player object in the Hierarchy, by dragging it onto the player Cube. It should look like this.



    Now we can test the cubes movement, press the big Player button and move your cube with WASD, if the cube flips over, it is because we haven't locked the X or Y axis rotations yet. To do so, click on the cube and in the Inspector, edit the properties of the Rigidbody to look more like this.



    Now lets get our player turning! To turn the player, we are going to want to rotate the player on its up axis. A simple rotation would be to rotate the object according to its transform.up multiplied by the players horizontal input, multiplied by our speed, multiplied by our time. So our FixedUpdate looks like this.
    Code (CSharp):
    1.  
    2.     void FixedUpdate()
    3.     {
    4.         vertical = Input.GetAxis("Vertical");
    5.         horizontal = Input.GetAxis("Horizontal");
    6.         body.velocity = (transform.forward * vertical) * speed * Time.fixedDeltaTime;
    7.         transform.Rotate((transform.up * horizontal) * rotationSpeed * Time.fixedDeltaTime);
    8.     }
    Now you can go back to play mode and enjoy the fruits of your effort, as your cube can now move AND rotate. The next step is to inregrate Jumping.

    Section 5 - Jumping
    There are multiple ways of handling a jump from a player, AddForce(), Velocity, RelativeForce, etc. The main crux of jumping is knowing when the player is on the ground. The two most common methods for checking if a player is on the ground is to either use the OnCollisionEnter event, or constantly LineCasting towards the grounds to check for distance from a collider. We will be using the more basic Collision method. To do this, you will need to implement the OnCollisionEnter and OnCollisionExit functions. Like this.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Movement : MonoBehaviour
    6. {
    7.  
    8.     // The Rigidbody attached to the GameObject.
    9.     private Rigidbody body;
    10.     /// <summary>
    11.     /// Speed scale for the velocity of the Rigidbody.
    12.     /// </summary>
    13.     public float speed;
    14.     /// <summary>
    15.     /// Rotation Speed scale for turning.
    16.     /// </summary>
    17.     public float rotationSpeed;
    18.     // The vertical input from input devices.
    19.     private float vertical;
    20.     // Initialization function
    21.     void Start()
    22.     {
    23.         // Obtain the reference to our Rigidbody.
    24.         body = GetComponent<Rigidbody>();
    25.  
    26.     }
    27.     // Fixed Update is called a fix number of frames per second.
    28.     void FixedUpdate()
    29.     {
    30.         vertical = Input.GetAxis("Vertical");
    31.         horizontal = Input.GetAxis("Horizontal");
    32.         body.velocity = (transform.forward * vertical) * speed * Time.fixedDeltaTime;
    33.         transform.Rotate((transform.up * horizontal) * rotationSpeed * Time.fixedDeltaTime);
    34.     }
    35.     // This function is a callback for when an object with a collider collides with this objects collider.
    36.     void OnCollisionEnter(Collision collision)
    37.     {
    38.  
    39.     }
    40.     // This function is a callback for when the collider is no longer in contact with a previously collided object.
    41.     void OnCollisionExit(Collision collision)
    42.     {
    43.  
    44.     }
    45. }
    Now add the boolean variable isGrounded to your variables at the top of the class.

    private bool isGrounded;
    This will be used for declaring whether or not our player is grounded, or in the air.

    The important part of this, is checking information about the collider in our callback. As you can see in our collision functions, we have an input variable of Collision named collision and this is what we will be using to obtain our information from. In more advanced techniques, you may need to setup ground detection in a way that only the feet of the player can detect ground, otherwise, the the player hitting an object head-on could also trigger the isGrounded. To prevent this you would apply the same logic to a separate collider at the feet of the player. In some cases it may also be pertitent to find our what the player collided with. As such for this tutorial, we will be checking if the player collided with the Terrain object.

    There are a few ways to check what we collided with, without digging into the data of the actual object like what type it may be, or what components it may have. Unity provides a naming and tagging system to handle some of this, but the naming system is not advised in these situations because names can be easily changed, thus effecting the code. So we will check for the Tag of Ground. Since our terrain doesn't have a Tag yet, we will need to add it. So select the terrain, and in the inpsector, set the tag to Ground, like this.



    The Tag won't exist yet, so you will need to add it. Simply click Add Tag, a new menu will appear and you will see a + and - symbol, select the + and it will ask for a name for your new Tag. Once added, you can return to the Terrain and set the tag to your newly added Ground.

    Now we can go back to Visual Studios and add our code to check for the ground. We will be referencing the GameObject attached to the collision, but to do that we will need to reference the collider, because the collision itself is information about the collision physics.

    You collision functions should now look like this.

    Code (CSharp):
    1.    // This function is a callback for when an object with a collider collides with this objects collider.
    2.     void OnCollisionEnter(Collision collision)
    3.     {
    4.         if (collision.gameObject.tag == ("Ground"))
    5.         {
    6.             isGrounded = true;
    7.         }
    8.     }
    9.     // This function is a callback for when the collider is no longer in contact with a previously collided object.
    10.     void OnCollisionExit(Collision collision)
    11.     {
    12.         if (collision.gameObject.tag == ("Ground"))
    13.         {
    14.             isGrounded = false;
    15.         }
    16.     }
    Now we can add the part where the player actually jumps. We'll use AddForce to get a decent looking upwards jump. First we must create a check for the players jump input, to do so we will create an IF statement to check if the player is pressing the jump key. We will also need an amount of jump force to send the player upwards. So we must add another float to our Movement script called jumpForce and alter it in the inspector to be at least 500.

    Finally because we are using Velocity, if we set the Velocity manually it will override gravity. So to add gravity back in, we just need to inherit from the existing Y-Axis velocity.
    So instead of directly setting the Velocity, we will set it like this.

    Code (CSharp):
    1. Vector3 velocity = (transform.forward * vertical) * speed * Time.fixedDeltaTime;
    2. velocity.y = body.velocity.y;
    3. body.velocity = velocity;
    So alter the script to reflect our new changes.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Movement : MonoBehaviour
    6. {
    7.  
    8.     // The Rigidbody attached to the GameObject.
    9.     private Rigidbody body;
    10.     /// <summary>
    11.     /// Speed scale for the velocity of the Rigidbody.
    12.     /// </summary>
    13.     public float speed;
    14.     /// <summary>
    15.     /// Rotation Speed scale for turning.
    16.     /// </summary>
    17.     public float rotationSpeed;
    18.     /// <summary>
    19.     /// The upwards jump force of the player.
    20.     /// </summary>
    21.     public float jumpForce;
    22.     // The vertical input from input devices.
    23.     private float vertical;
    24.     // The horizontal input from input devices.
    25.     private float horizontal;
    26.     // Whether or not the player is on the ground.
    27.     private bool isGrounded;
    28.     // Initialization function
    29.     void Start()
    30.     {
    31.         // Obtain the reference to our Rigidbody.
    32.         body = GetComponent<Rigidbody>();
    33.  
    34.     }
    35.     // Fixed Update is called a fix number of frames per second.
    36.     void FixedUpdate()
    37.     {
    38.         vertical = Input.GetAxis("Vertical");
    39.         horizontal = Input.GetAxis("Horizontal");
    40.         if (Input.GetAxis("Jump") > 0)
    41.         {
    42.             if (isGrounded)
    43.             {
    44.                 body.AddForce(transform.up * jumpForce);
    45.             }
    46.         }
    47.         Vector3 velocity = (transform.forward * vertical) * speed * Time.fixedDeltaTime;
    48.         velocity.y = body.velocity.y;
    49.         body.velocity = velocity;
    50.         transform.Rotate((transform.up * horizontal) * rotationSpeed * Time.fixedDeltaTime);
    51.     }
    52.     // This function is a callback for when an object with a collider collides with this objects collider.
    53.     void OnCollisionEnter(Collision collision)
    54.     {
    55.         if (collision.gameObject.tag == ("Ground"))
    56.         {
    57.             isGrounded = true;
    58.         }
    59.     }
    60.     // This function is a callback for when the collider is no longer in contact with a previously collided object.
    61.     void OnCollisionExit(Collision collision)
    62.     {
    63.         if (collision.gameObject.tag == ("Ground"))
    64.         {
    65.             isGrounded = false;
    66.         }
    67.     }

    We should now have a working movement script to walk, turn, and jump.

    Go ahead and press play and test it out. If you followed these steps so far, you should also working movement.

    Thus concludes the basics of creating a movement script in Unity3D. In later tutorials we will discuss separate camera movements, as well as integrating animations into our movement.
     
    Last edited: Jun 25, 2017
  2. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    If anyone has questions or sees any improper information, please let me know.
     
  3. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    Updated to inherit gravitational velocity.
     
  4. heidira

    heidira

    Joined:
    Sep 13, 2018
    Posts:
    1
    Hi, in my game I used FixedUpdate and multiplied the speed with Time.fixedDeltaTime. I have also tried Update with Time*DeltaTime. In the Editor it works fine and compiled with best graphics. If I'm starting the game with "Graphics quality: Fast oder normal" the game is much to fast. Did you know this is an issue and how to solve it?
    rb2D.AddForce(movement * Speed * Time.fixedDeltaTime*30);
     
  5. RegahProd23

    RegahProd23

    Joined:
    Jul 22, 2019
    Posts:
    5
    Do you know why do these numbers have to be so big? It's not a big problem, but i thought it was weird i had to set 500 units just to get a decent jump, and setting speed to 50 and rotational speed to 25 gives you a pretty slow zombie like movement. Thanks for the guide!
     
  6. Worstharbor

    Worstharbor

    Joined:
    Jan 29, 2020
    Posts:
    3
    Wow. But what about the animator tutorial?
     
  7. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    You know, I actually forgot about this. I'll do an animator tutorial soon. With some video parts to help.
     
  8. WhatisitStudios

    WhatisitStudios

    Joined:
    Jul 17, 2020
    Posts:
    1
    can you give me the code?
     
  9. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    It's in the tutorial, the line of code was

    Code (CSharp):
    1.         velocity.y = body.velocity.y;
    2.  
     
  10. Demohd07

    Demohd07

    Joined:
    May 27, 2020
    Posts:
    1
    when the mouse is not locked the camera moves to the left slowly
     
  11. burntoast421

    burntoast421

    Joined:
    Jan 3, 2021
    Posts:
    1
    Do u know how to make it so instead of making the A and D rotate left and right make it move the player left and right?
     
  12. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    Might be your mouse? It's reading directly from the input system so it can't rotate unless the axis is considered to be moving.

    Yeah you'd simply do
    Code (CSharp):
    1. Vector3 velocity = (transform.forward * vertical) + (transform.right * horizontal) * speed * Time.fixedDeltaTime;
    And then replace the turning with something like
    Code (CSharp):
    1.         transform.Rotate((transform.up * Input.GetAxis("MouseX")) * rotationSpeed * Time.fixedDeltaTime);
    2.  
     
  13. hamza_abouamal_Organization

    hamza_abouamal_Organization

    Joined:
    Dec 12, 2017
    Posts:
    4
    For rb velocity if there's no incrementation do not multiply it with frame or fixed duration but if you have velocity+=.... Then you duration should multiply with frame or fixed depending on where you incremente.

    Velocity is always applied in fixedupdate even if you modify it in update, it value change but its applied to rigidbody tille the fixed update happend
     
  14. Aitssam

    Aitssam

    Joined:
    Sep 3, 2020
    Posts:
    5
    its not working smoothl its have jerk
     
  15. tentagrrrl

    tentagrrrl

    Joined:
    Feb 16, 2022
    Posts:
    1
    Trying this in Unity Editor 2020.3.30f1

    Another tutorial (that I cannot find again) said that input should go into Update, and physics should go into fixedUpdate (since Update checks every frame and fixedUpdate does not, i think they said this was to make sure no inputs are missed and to make sure the movement is smooth). How would I alter the code to reflect that? Also, I experience a regular jitter or stutter as the character moves.
     
  16. Squiddu

    Squiddu

    Joined:
    Jan 10, 2020
    Posts:
    5
    Added this in (alongside a somewhat-janky speed variable), but for whatever reason (horizontally) the character never stops moving.
    Oh and also the speed whenever 'idle' is now incredibly high, so the speed never multiplies velocity by 0 as I intend it to.
     
  17. NotreCoeurBaise

    NotreCoeurBaise

    Joined:
    May 30, 2022
    Posts:
    1
    semantics first: in section 5, your notes are missing the declared variable "float horizontal"

    second: line 48 of your final, complete code, you have what looks like a vector variable called vector. Is this correct? if so, then it is also undeclared and lead to my confusion.
     
  18. drfgthulkjhgfds

    drfgthulkjhgfds

    Joined:
    Sep 10, 2022
    Posts:
    1
  19. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    Then you should know that hijacking threads is something you should not do here. Please create your own threads rather than jumping on others out-of-context.

    Thanks.
     
  20. AnonymousGameDev711

    AnonymousGameDev711

    Joined:
    Apr 4, 2023
    Posts:
    2
    I used this script and modified it slightly but I still have no idea how to make this more realistic as the decceleration and acceleration speeds arent very realistic and the jumping doesnt work either.

    please help.
     
  21. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    This is a very simple version of how it would work, but one thing you can do is create a float that scales up and down as you give input, so while input is being given it scales to max value, and while input is not being given it scales to 0. You can use a Lerp for this to make it smooth. As for the jumping, I think it just requires a larger number but you can fix that by doing something similar, even including an AnimationCurve to get a smooth jump up and nice falling speed. If you're confused about any of this go ahead and send me a message and I could help on discord if I got time.
     
  22. DZZc0rd

    DZZc0rd

    Joined:
    Sep 4, 2021
    Posts:
    59
    Hi, I used your move with velocity, but when I try to move on a ramp the player stops
     
  23. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    Yeah that won't really work with a box collider, you'll want to use a capsule or sphere for ground movement
     
  24. DZZc0rd

    DZZc0rd

    Joined:
    Sep 4, 2021
    Posts:
    59
    I used mesh collider
     
  25. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    You shouldn't really use mesh colliders with rigidbodies, it's very expensive.
     
  26. Aethenosity

    Aethenosity

    Joined:
    Jun 2, 2017
    Posts:
    16
    Velocity is in meters per second. You should not multiply it by deltaTime or fixedDeltaTime