Checking for Grounded with Raycasts and Jumping with the Character Controller
Being able to jump is ubiquitous with videogames. Mario's gigantic leaps have helped define play for generations. We should be able to incorporate a jump into a game then. Luckily, we can use the Character Controller and Raycasts to do so.
Raycasting

Raycasting in Unity is like shining an invisible laser from a point in space to detect objects. It helps determine what the laser hits, useful for game mechanics such as checking for obstacles, determining line of sight, or interacting with game objects.
To use a ray, I recommend you make a variable to contain the ray's information; specifically it's origin (where it is 'shot' from) and its direction.
Ray groundedCheckRay;
This is a variable (declared at the top of a class) of type Ray which will hold this information. We have not initialised it yet.
We also need a variable to hold information about where the ray hit:
RaycastHit hitData;
This is a variable of type RaycastHit, and will hold information about where and what the ray hit.
Next, In our Update() section, we can assign our Ray using the following syntax:
groundedCheckRay = new Ray(transform.position, Vector3.down);
This will mean our ray will shoot out of our position (this is at our origin at the bottom of the character for Character Controller objects typically) in the direction of down.
We haven't actually shot the ray yet though. We're aiming, but we haven't shot.
To do this, we need to use the following code in, for example, the Update()
message:
if (Physics.Raycast(groundedCheckRay, out hitData))
{
//do something.
}
Firstly, we have to wrap our Raycast
into an if()
statement. This is because Raycasting can, in theory, hit nothing. This is less likely on a grounded check, but more likely with a gun raycast. If we hit nothing, our Raycast's output doesn't exist, so the out
value will actually cause you a syntax error in your game.
Our hitData
variable, as mentioned, will save information about what our Ray hit. For example, it can save:
The object
The distance between the ray's origin and the hit point
the point of intersection
This means we are able to check the distance from the player and the floor fairly effectively. In our if() statement, we can therefore check if we are grounded or not, or to be precise, if we are sufficiently close to the ground be to considered touching the floor.
First, I'm going to make a boolean to save that information in:
public bool isGrounded = false;
Then, in the if()
statement of our Raycast:
if (hitData.distance < 0.1f)
{
isGrounded = true;
}
else
{
isGrounded = false;
}
Note that I am not checking that the distance is zero; collision and distance is a little tricky to get precise, particularly as colliders can have skin width which means the collider itself has thickness. It's entirely possible that your character touching the ground won't have a distance to the floor of exactly zero.
Next we need to incorporate the jump functionality.
CharacterController.Move() and Time.deltaTime
So far, we've been using CharacterController.SimpleMove()
to move characters around. Did you notice that SimpleMove()
automatically adds gravity? Also, did you notice that your movement speed is applied every second when passed to the function argument? It means that SimpleMove(0, 0, 1)
will move you one Unity unit (metre) every second.
SimpleMove() is great for many scenarios, but it does have some limits. Specifically, it doesn't have any way to jump; as it actually ignores y-axis input you pass it in the argument, and instead just applies gravity.
This means we need to use CharacterController.Move() instead. This is a function that takes a movement Vector3 as before, but it has some key differences:
Doesn't inherently add gravity.
Moves on the given Vector3 every frame instead of every second.
This second point here is particularly important to explore. It is quite dangerous to tie game logic to frame rate, because frame rates are not necessarily consistent in most games. Different computer set ups have different maximum frame rates. If your game was being run at 240fps, and the character would be moved with cc.Move()
, the character would move more (4x faster) every second compared to the game running at 60fps.
Similarly, you might have a game with slowdown due to too many elements on screen at once. In this instance, your walk speed would actually slow down along with the frame rate.
The solution to this is simple; we can multiply our movement vector by Time.deltaTime
.
Time
is a class built into the Monobehaviour class.deltaTime
is the time between the previous frame/update and the frame before it.
When we multiply our movement vector by Time.deltaTime, two things happen:
We move by the amount in our vector every second, like
SimpleMove()
, instead of every frame.We guarantee our movement speed per second is consistent regardless of frame rate.
Let's imagine I have a Vector3
to handle movement called moveDir. Our Move()
call might look like this:
cc.Move(moveDir * Time.deltaTime);
SimpleMove()
including a built-in frame rate normalisation is not common with functions, so when in doubt, check internet resources on a per-function basis. For example,
transform.Rotate(0, 0, 0);
does not have built-in frame rate normalisation. To call this function and make an object spin 90 degrees a second, we would need a delta time multiplication:
transform.Rotate(0, 90 * Time.deltaTime, 0);
Jumping and Manual Gravity
If Move()
doesn't add gravity automatically, we need to add it ourselves. It's common to use a float to manage our velocity on the y-axis.
float yVelocity;
Because gravity is an accelerating force, what we might want to do is constantly decrease the value of yVelocity, therefore making us move downward increasingly faster. Of course, gravity does not accelerate bodies of mass infinitely, we can also reach a terminal velocity of -9.81m/s.
Below, we have a simple implementation of gravity, gradually reducing the yVelocity every frame until it reaches a minimum value, defined by a clamp:
yVelocity -= Time.deltaTime * -Physics.gravity.y;
yVelocity = Mathf.Clamp(yVelocity, Physics.gravity.y, float.MaxValue);
Firstly, Physics.gravity.y returns a value of -9.8f. If we put a minus in front of it, it makes it the positive equivalent of 9,8f.
Next, I want my object reach terminal velocity after 1 second of freefall. This is not based in any specific physics, and actually I would strongly recommend not rigidly sticking to real-world physics, particularly at the cost of fun and playability.
Finally, I clamp our maximum and minimum velocity on the y-axis. I don't clamp the upwards velocity in my example here: it's essentially set to as big as the variable will hold. If you did want to, you could clamp the upwards velocity with whatever value you want.
Now, we need to implement a jump. This is fairly straightforward once our yVelocity is set up correctly set up:
if (isGrounded && Input.GetKeyDown(KeyCode.Space))
{
yVelocity = 5;
}
This sets our upwards velocity to whatever upwards force you choose; I arbitrarily chose the number 5 as a jump force in this instance. This would likely be something you tinker with.
Combined up with other Character Controller and Animator commands, should now be able to create a fairly substantial third or first person character!
I highly recommend locking down movement, particularly jump height, before starting level design. Changing the height of certain jumps and gaps can be an expensive thing to do during games production, particularly if assets need to be remodelled. I would always recommend perfecting all movement in a graybox setting before going into level design production.
Reference Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
// Reference to the CharacterController component attached to the player
CharacterController cc;
// Holds data about what the raycast hits (e.g., position, collider)
RaycastHit hitData;
// Ray used to check if the player is grounded
Ray groundedCheckRay;
// Direction vector for player movement
Vector3 moveDir;
// Vertical velocity of the player, used for gravity and jumping
float yVelocity;
// Boolean to track if the player is grounded
public bool isGrounded = false;
// Called once when the script initializes
void Start()
{
// Get the CharacterController component attached to the player GameObject
cc = GetComponent<CharacterController>();
}
// Called once per frame
void Update()
{
// Create a ray starting at the player's position, pointing downward
groundedCheckRay = new Ray(transform.position, Vector3.down);
// Draw the ray in the Scene view for debugging purposes (red color)
Debug.DrawRay(groundedCheckRay.origin, groundedCheckRay.direction, Color.red);
// Perform a raycast to detect if the player is grounded
if (Physics.Raycast(groundedCheckRay, out hitData))
{
// Log the distance to the hit object in the console
Debug.Log(hitData.distance);
// If the distance to the ground is less than 0.1, consider the player grounded
if (hitData.distance < 0.1f)
{
isGrounded = true;
}
else
{
isGrounded = false;
}
}
// Apply gravity to the vertical velocity
yVelocity -= Time.deltaTime * -Physics.gravity.y;
// Clamp the vertical velocity to prevent it from exceeding gravity's pull
yVelocity = Mathf.Clamp(yVelocity, Physics.gravity.y, float.MaxValue);
// Allow the player to jump if grounded and the spacebar is pressed
if (isGrounded && Input.GetKeyDown(KeyCode.Space))
{
yVelocity = 5; // Set an upward velocity for the jump. This is just an arbitrary number chosen.
}
// Get input for horizontal and vertical movement and include vertical velocity
moveDir = new Vector3(Input.GetAxis("Horizontal"), yVelocity, Input.GetAxis("Vertical"));
// Rotate the player based on mouse X-axis movement
transform.Rotate(new Vector3(0, Input.GetAxis("Mouse X"), 0));
// Adjust the movement direction to match the player's current rotation
moveDir = transform.rotation * moveDir;
// Move the player using the CharacterController
cc.Move(moveDir * Time.deltaTime);
}
}
Further Resources
Last updated