Dennis Hackethal’s Blog

My blog about philosophy, coding, and anything else that interests me.

History of post ‘Controlling a Character’s Movements in Unity’

Versions are sorted from most recent to oldest with the original version at the bottom. Changes are highlighted relative to the next, i.e. older, version underneath. Only changed lines and their surrounding lines are shown, except for the original version, which is shown in full.

Revision 3 · · View this (the most recent) version (v2)

@@ -182,22 +182,24 @@ Known problems with this code (most of which I mentioned at the beginning of thi

- Can't look up or down yet
- Can't jump yet
- Can -'straferun'[^1]+'straferun'\*
- No gravity, so probably couldn't move up/down slopes

-[^1]:+----
+
+<small>* You may notice that when you move diagonally, your character moves faster than if you move him only forward/backward or sideways. Why is that?

As I learned [here](https://youtu.be/rY71U1nHrBc?t=1060), it has to do with the Pythagorean theorem. If you simultaneously go one step forward and one step to the right, we can calculate the length of the resulting diagonal like this: `1² + 1² = diagonal²`. Meaning `diagonal` comes out to the square root of 2, which is ~1.414. So while you move one unit forward and one unit sideways, combining the two into a diagonal results in moving 1.414 units! As pointed out in the video linked above, this is called 'straferunning', which was used in Doom to move faster and jump farther, as explained [here](https://doom.fandom.com/wiki/Straferunning):

> Straferunning is running forward and moving sideways ([strafing](https://doom.fandom.com/wiki/Strafing)) at the same time, which results in moving faster than is possible in either direction alone. One of the advantages is being able to jump farther than is otherwise possible.

Fixing this is easy by *normalizing* the movement vector:

```c#
Vector3 moveDirection = new Vector3(moveX, 0, moveZ).normalized;
                                                     ^
```

A normalized vector (also called a [unit vector](https://mathworld.wolfram.com/NormalizedVector.html)) points in the same direction as its non-normalized counterpart but always has length 1, no matter the values of `moveX` and `moveZ`.

The problem with this fix is that it can add a delay to your movements. You will notice that your character doesn't always stop immediately upon letting go of whatever keys you held to move. I don't know yet how to fix that and will need to review the above-linked [video](https://youtu.be/rY71U1nHrBc?t=1060) in which I learned about normalization.</small>

Revision 2 · · View this version (v3)

@@ -186,18 +186,18 @@ Known problems with this code (most of which I mentioned at the beginning of thi
- No gravity, so probably couldn't move up/down slopes

[^1]: You may notice that when you move diagonally, your character moves faster than if you move him only forward/backward or sideways. Why is that?
   
   As I learned [here](https://youtu.be/rY71U1nHrBc?t=1060), it has to do with the Pythagorean theorem. If you simultaneously go one step forward and one step to the right, we can calculate the length of the resulting diagonal like this: `1² + 1² = diagonal²`. Meaning `diagonal` comes out to the square root of 2, which is ~1.414. So while you move one unit forward and one unit sideways, combining the two into a diagonal results in moving 1.414 units! As pointed out in the video linked above, this is called 'straferunning', which was used in Doom to move faster and jump farther, as explained [here](https://doom.fandom.com/wiki/Straferunning):
   
   > Straferunning is running forward and moving sideways ([strafing](https://doom.fandom.com/wiki/Strafing)) at the same time, which results in moving faster than is possible in either direction alone. One of the advantages is being able to jump farther than is otherwise possible.
   
   Fixing this is easy by *normalizing* the movement vector:
   
   ```c#
   Vector3 moveDirection = new Vector3(moveX, 0, moveZ).normalized;
                                                        ^
   ```
   
   A normalized vector (also called a [unit vector](https://mathworld.wolfram.com/NormalizedVector.html)) points in the same direction as its non-normalized counterpart but always has length 1, no matter the values of `moveX` and `moveZ`.
   
   The problem with this fix is that it can add a delay to your movements. You will notice that your character doesn't always stop immediately upon letting go of whatever keys you held to move. I don't know yet how to fix that and will need to review the above-linked [video](https://youtu.be/rY71U1nHrBc?t=1060) in which I learned about normalization.</small>

Revision 1 · · View this version (v2)

Convert asterisk to proper markdown footnote

@@ -182,24 +182,22 @@ Known problems with this code (most of which I mentioned at the beginning of thi

- Can't look up or down yet
- Can't jump yet
- Can -'straferun'\*+'straferun'[^1]
- No gravity, so probably couldn't move up/down slopes

-----+[^1]: You may notice that when you move diagonally, your character moves faster than if you move him only forward/backward or sideways. Why is that?

   -<small>* You may notice that when+As I learned [here](https://youtu.be/rY71U1nHrBc?t=1060), it has to do with the Pythagorean theorem. If you -move diagonally, your character moves faster than if+simultaneously go one step forward and one step to the right, we can calculate the length of the resulting diagonal like this: `1² + 1² = diagonal²`. Meaning `diagonal` comes out to the square root of 2, which is ~1.414. So while you move -him only forward/backward or sideways. Why+one unit forward and one unit sideways, combining the two into a diagonal results in moving 1.414 units! As pointed out in the video linked above, this is -that?+called 'straferunning', which was used in Doom to move faster and jump farther, as explained [here](https://doom.fandom.com/wiki/Straferunning):

   -As I learned [here](https://youtu.be/rY71U1nHrBc?t=1060), it has to do with the Pythagorean theorem. If you simultaneously go one step forward and one step to the right, we can calculate the length of the resulting diagonal like this: `1² + 1² = diagonal²`. Meaning `diagonal` comes out to the square root of 2, which+> Straferunning is -~1.414. So while you move one unit+running forward and -one unit sideways, combining+moving sideways ([strafing](https://doom.fandom.com/wiki/Strafing)) at the -two into a diagonal+same time, which results in moving -1.414 units! As pointed out+faster than is possible in +either direction alone. One of the -video linked above, this+advantages is -called 'straferunning', which was used in Doom+being able to-move faster and jump -farther, as explained [here](https://doom.fandom.com/wiki/Straferunning):+farther than is otherwise possible.

   -> Straferunning+Fixing this is -running forward and moving sideways ([strafing](https://doom.fandom.com/wiki/Strafing)) at+easy by *normalizing* the -same time, which results in moving faster than is possible in either direction alone. One of the advantages is being able to jump farther than is otherwise possible.+movement vector:

   -Fixing this is easy by *normalizing* the movement vector:+```c#
+   Vector3 moveDirection = new Vector3(moveX, 0, moveZ).normalized;
+                                                        ^
+   ```

-```c#
-Vector3 moveDirection = new Vector3(moveX, 0, moveZ).normalized;
-                                                     ^
-```   A normalized vector (also called a [unit vector](https://mathworld.wolfram.com/NormalizedVector.html)) points in the same direction as its non-normalized counterpart but always has length 1, no matter the values of `moveX` and `moveZ`.

   The problem with this fix is that it can add a delay to your movements. You will notice that your character doesn't always stop immediately upon letting go of whatever keys you held to move. I don't know yet how to fix that and will need to review the above-linked [video](https://youtu.be/rY71U1nHrBc?t=1060) in which I learned about normalization.</small>

Original · · View this version (v1)

# Controlling a Character's Movements in Unity

*Disclaimer: I'm a mere beginner at Unity and game development generally. I wrote this for myself as I am learning and figured others might find it useful as well. Exercise caution when running this code!*

Using Unity version 2020.3.2f1 and based on [this tutorial](https://www.youtube.com/watch?v=qc0xU2Ph86Q) – with some changes – I want to show you how you can make your character do the following:

- Move forward/backward
- Move sideways and diagonally
- Sprint while holding left shift
- Rotate by moving the mouse

My code does *not* implement:

- Jumping
- Looking up or down
- Going up or down slopes

Here's the resulting code without comments (I will walk you through it step by step further down):

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
  public CharacterController controller;

  void Start()
  {
    Cursor.lockState = CursorLockMode.Locked;
  }

  void Update()
  {
    Move();
    Rotate();
  }

  private void Move() {
    float moveZ = Input.GetAxis("Vertical");
    float moveX = Input.GetAxis("Horizontal");

    Vector3 moveDirection = new Vector3(moveX, 0, moveZ);

    moveDirection = transform.TransformDirection(moveDirection);

    if (Input.GetKey(KeyCode.LeftShift)) {
      moveDirection *= 2;
    }

    controller.Move(moveDirection * 5 * Time.deltaTime);
  }

  private void Rotate() {
    float mouseX = Input.GetAxis("Mouse X") * 250 * Time.deltaTime;

    transform.Rotate(Vector3.up, mouseX);
  }
}

```

As you can see, it's not that much. Let's go over it step by step – actually, there's a few things we need to do before we start coding:

1. Create a terrain by right-clicking on your hierarchy in Unity, choosing '3D object', then 'Terrain'
2. Create another 3D object that you'll place on top of the terrain. This could be a cylinder, for example: '3D object' > 'Cylinder'. Call it 'Player'.
3. Drag the main camera into the player. This way, whenever the player moves, the camera follows him
4. Add a Rigidbody component to the player and check its 'Is Kinematic' property
5. Add a Character Controller component to the player. This component comes with built-in support you'll need to control your player. Likewise leave its settings as is
6. Add a script called 'PlayerController' to the player
7. Now we can start coding. Open the script in your favorite text editor and add this public property:

    ```c#
    public CharacterController controller;
    ```

    Save your changes.

8. Back in Unity, drag the Character Controller into the `controller` property on your script
9. In your script, make the following change to the `Update()` method:

    ```c#
    // Update is called once per frame
    void Update()
    {
      Move();
    }
    ```

10. Add the corresponding private method `Move()`:

    ```c#
    private void Move() {
      // When hitting 'w' or the up arrow, this will evaluate to 1.
      // When hitting 's' or the down arrow, it will evaluate to -1.
      // (To be precise, it will return a float which gradually
      // increases to 1 as you hold 'w' or gradually decreases to -1
      // as you hold 's', respectively. This makes movement smoother.
      // To get only 1 and -1, you can use `Input.GetAxisRaw` instead.)
      float moveZ = Input.GetAxis("Vertical");

      // Our controller will expect a vector telling it in which
      // directions to move. Here we prepare a new vector that only
      // moves along the z axis.
      Vector3 moveDirection = new Vector3(0, 0, moveZ);

      // We need to move along the *player's* z axis, not the world's.
      // Once rotated, your player's z axis may be different from
      // the world's.
      moveDirection = transform.TransformDirection(moveDirection);

      // Execute the move. We multiply by 5 since otherwise the move
      // speed is a bit slow.
      // We then multiply by `Time.deltaTime`, which is a float that
      // returns the number of seconds that have passed since the
      // previous frame was rendered. This is crucial, otherwise
      // the player would move faster on a monitor with a higher
      // frame rate as this runs once per frame!
      controller.Move(moveDirection * 5 * Time.deltaTime);
    }
    ```

    Four lines of code without comments. Not bad, right? Save these changes and run the project in Unity. You should be able to move forward and backward using <kbd>w</kbd> and <kbd>s</kbd> or the up and down arrow keys.

11. What about moving sideways? Easy. First, add this line below the call to `Input.GetAxis`:

    ```c#
    float moveX = Input.GetAxis("Horizontal");
    ```

    This works just like `Input.GetAxis("Vertical")` except it listens for keys <kbd>a</kbd> and <kbd>d</kbd> as well as the left and right arrow keys.

12. Change the line in which we declare `moveDirection` to:

    ```c#
    Vector3 moveDirection = new Vector3(moveX, 0, moveZ);
    ```

    Note that we've replaced the first `0` with `moveX`. You can now move sideways, and also diagonally.

13. Next, let's implement running while holding left shift. Add the following before the call to `controller.Move`:

    ```c#
    // Run while holding shift
    if (Input.GetKey(KeyCode.LeftShift)) {
      moveDirection *= 2;
    }
    ```

14. Lastly, we'll need to rotate the player left and right whenever we move our mouse. For this, we will add a second method called `Rotate`, along with a couple other changes:

    ```c#
    void Start()
    {
      // Lock cursor to the screen's center point and hide cursor.
      // You can get your cursor back while playing by pressing
      // the escape key.
      Cursor.lockState = CursorLockMode.Locked;
    }

    void Update()
    {
      Move();
      Rotate(); // <- added this line
    }

    private void Rotate() {
      // Determine how much we want to rotate based on the mouse's
      // movement. Multiply by 250 to make more sensitive – depending
      // on your hardware and settings you may wish to tweak this number.
      float mouseX = Input.GetAxis("Mouse X") * 250 * Time.deltaTime;

      // Rotate around the x axis
      transform.Rotate(Vector3.up, mouseX);
    }
    ```

That's it!

Known problems with this code (most of which I mentioned at the beginning of this post):

- Can't look up or down yet
- Can't jump yet
- Can 'straferun'\*
- No gravity, so probably couldn't move up/down slopes

----

<small>* You may notice that when you move diagonally, your character moves faster than if you move him only forward/backward or sideways. Why is that?

As I learned [here](https://youtu.be/rY71U1nHrBc?t=1060), it has to do with the Pythagorean theorem. If you simultaneously go one step forward and one step to the right, we can calculate the length of the resulting diagonal like this: `1² + 1² = diagonal²`. Meaning `diagonal` comes out to the square root of 2, which is ~1.414. So while you move one unit forward and one unit sideways, combining the two into a diagonal results in moving 1.414 units! As pointed out in the video linked above, this is called 'straferunning', which was used in Doom to move faster and jump farther, as explained [here](https://doom.fandom.com/wiki/Straferunning):

> Straferunning is running forward and moving sideways ([strafing](https://doom.fandom.com/wiki/Strafing)) at the same time, which results in moving faster than is possible in either direction alone. One of the advantages is being able to jump farther than is otherwise possible.

Fixing this is easy by *normalizing* the movement vector:

```c#
Vector3 moveDirection = new Vector3(moveX, 0, moveZ).normalized;
                                                     ^
```

A normalized vector (also called a [unit vector](https://mathworld.wolfram.com/NormalizedVector.html)) points in the same direction as its non-normalized counterpart but always has length 1, no matter the values of `moveX` and `moveZ`.

The problem with this fix is that it can add a delay to your movements. You will notice that your character doesn't always stop immediately upon letting go of whatever keys you held to move. I don't know yet how to fix that and will need to review the above-linked [video](https://youtu.be/rY71U1nHrBc?t=1060) in which I learned about normalization.</small>