Dennis Hackethal’s Blog

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

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 – 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):

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:

    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:

    // Update is called once per frame
    void Update()
    {
      Move();
    }
    
  10. Add the corresponding private method Move():

    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 w and s or the up and down arrow keys.

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

    float moveX = Input.GetAxis("Horizontal");
    

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

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

    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:

    // 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:

    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

* 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, 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:

Straferunning is running forward and moving sideways (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:

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

A normalized vector (also called a unit vector) 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 in which I learned about normalization.


What people are saying

What are your thoughts?

Preview

Markdown supported. cmd + enter to submit. You are responsible for what you write.

Preview