Moving Characters with the Character Controller Component

Now that we can create animated characters, we can also move those characters around using scripts. We have explored the use of Rigidbody to move an object around using force, and in many instances you may want to move a character around using physics.

Traditionally though, player characters in particular haven't been 'physics' characters, and instead have displayed simpler, less realistic movement. The 'doom slayer' from Doom (1993) is a perfect example of this; he moves as fast a car!

Character Controller

The Character Controller component, added the normal way you add components to objects, allows you to move characters around in this way:

The Character Controller component has several important parameters that define how a character moves:

  • Height: Specifies the height of the character's capsule collider. This defines how tall the character is perceived to be in the game world.

  • Radius: Sets the radius of the capsule collider, which affects how the character collides with the environment.

  • Center: Defines the center point of the capsule collider relative to the transform's position, useful for adjusting the collider's position vertically. Notice how I have changed mine to 1.1 on the Y axis; often the origin of a 3D character is their feet instead of their centre, meaning the collider and the 3D model are usually not aligned at a Y axis of zero.

  • Slope Limit: Determines the maximum angle of a slope the character can walk up. This prevents the character from climbing steep surfaces.

  • Step Offset: Sets the maximum height the character can step over, allowing for smoother navigation over small obstacles.

  • Skin Width: Represents the thin space between the character collider and obstacles, which helps prevent jittering during movement.

  • Min Move Distance: The minimum distance a character can move. Movements smaller than this value are ignored, which can help optimize performance.

  • Contact Offset: Adjusts the collider's margins slightly for better stability when moving.

These parameters enable fine-tuning of the character's interaction with the game world, providing both realistic and customizable movements.

To access the Character Controller and control an object with it, we need a script attached to the same object as the Character Controller component, and to follow the normal GetComponent pattern:

//After the opening braces of your class...

CharacterController cc;

//Then in the Start() message...

cc = GetComponent<CharacterController>();

As always, this allows us to access the functions and variables of the Character Controller component using our variable (in this case called cc).

SimpleMove(), Structs and Vector3

One of the most useful functions of Character Controller is called SimpleMove(). SimpleMove() is a function that requires a Vector3 as an argument.

A Vector3 is a struct. Structs are essentially variables that contain more than one pieces of data. Specifically, Vector3s store three floats at the same time instead of one; an x, y or z.

Declaring a Vector3 is fairly straightforward:

//Type, followed by a name of your choice.
Vector3 moveDirection;

Initialising or assigning values to Vector3 is a little more complicated than a simple variable. We have to 'package' up the whole Vector3 each time with the new keyword. It looks like this:

// Initialisation (setting a variable's contents on declaration)
Vector3 moveDirection = new Vector3 (0, 1, 0);

//Assignment (setting a variable's contents after it's declared)
moveDirection = new Vector3 (0, 0, 1);

It's very common to assign a Vector3 using Input Axes. This would look like this:

// Set the z-axis in the Vector3 to either 0, 1 or -1, depending on input.
moveDirection = new Vector3 (0, 0, Input.GetAxis("Vertical");

Transform Functions

We have been using functions from other components, but one component we've largely ignored is the Transform component, holding an object's position, rotation and scale.

Because all GameObjects have to have a Transform component attached, we actually don't need to use the GetComponent() pattern, because every script can by default access the Transform function using the transform class in script.

One useful function of the transform component is called Rotate(). It allows us to pass some arguments to rotate along the x, y, or z axis respectively.

// Call this in Update() to make an object spin on its y axis, like a planet.
transform.Rotate(0, 1, 0);

More usefully though, we can rotate an object using input, specifically mouse movement. This can be done using the Input Axis called 'Mouse X'. Using the mouse to rotate an object will look something like this:

// Call this in Update() to rotate an object with the mouse.
transform.Rotate(0, Input.GetAxis("Mouse X"), 0);

Quaternion Rotation and Making a Direction Local

We need to now combine up our rotation with our direction to ensure that we move in the direction we are facing.

By default, the SimpleMove() function of the Character Controller moves along world x, y and z coordinates. This means that if our character changes direction, it will keep moving the same direction, not the new direction its facing.

We can get around this by multiplying our move direction vector with the direction we are facing, specifically the quaternion value of our rotation:

//Modify our movement direction by our character's direction, so 'forward' becomes a local forward not a global one.
moveDirection = transform.rotation * moveDirection;

This sounds fairly complicated, but all we really need to know is that a quaternion, which is a way of representing rotation as data, uses numbers between 0 and 1 to 'point' in a specific direction in 3D space.

Watch this embedded video for more information.

A quaternion in Unity is a complex number system used to represent rotations. Unlike traditional Euler angles, quaternions avoid issues like gimbal lock, making them ideal for smooth and continuous rotations in 3D space. They are defined by four values: x, y, z, and w, and provide a more efficient and accurate way to handle rotations.

Putting It All Together

Our four steps are as follows then:

//Rotate our character on the Y axis using mouse movement on the X axis.
transform.Rotate(0, Input.GetAxis("Mouse X"), 0);

//Set up our Vector3 to all us to move forward and backward on the Z axis.
moveDirection = new Vector3(0, 0, Input.GetAxis("Vertical"));

//Modify our movement direction by our character's direction, so 'forward' becomes a local forward not a global one.
moveDirection = transform.rotation * moveDirection;

//Function built into Character Controller, requiring a Vector3, moving some amount in Unity meters every second.
cc.SimpleMove(moveDirection);

Note that you can combine this with Animator management to move a Character around, and with animations!

Reference Script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnimationManagement : MonoBehaviour
{
    //Container to put the Animator component into.
    Animator anim;

    //Container to put the Character Controller component into.
    CharacterController cc;
    
    //Vector3 used to save x, y, and z co-ordinates that determine our movement direction.
    Vector3 moveDirection;
    
    void Start()
    {
        //Grab the Animator component, put it inside our container.
        anim = GetComponent<Animator>();

        //Grab the Character Controller component, put it inside our container.
        cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        //Change the 'Blend' parameter in the Blend tree to whatever is in the Axis called 'Vertical'.
        anim.SetFloat("Blend", Input.GetAxis("Vertical"));

        //Rotate our character on the Y axis using mouse movement on the X axis.
        transform.Rotate(0, Input.GetAxis("Mouse X"), 0);

        //Set up our Vector3 to all us to move forward and backward on the Z axis.
        moveDirection = new Vector3(0, 0, Input.GetAxis("Vertical"));

        //Modify our movement direction by our character's direction, so 'forward' becomes a local forward not a global one.
        moveDirection = transform.rotation * moveDirection;

        //Function built into Character Controller, requiring a Vector3, moving some amount in Unity meters every second.
        cc.SimpleMove(moveDirection);
    }
}

Last updated