Unity: CHARACTER CONTROLLER vs RIGIDBODY (2024)

Unity: CHARACTER CONTROLLER vs RIGIDBODY (1)

When you’re creating a new project with Unity, one of the first things you have to do is code your avatar’s controller. It’s very important and you can’t rush it or your gamefeel will probably be bad. There are a lot of different things to do, ranging from the inputs acquisition to the movements and feedbacks.
One of the first question I often ask myself is: “Should I use aCharacter Controlleror aRigidbody? ”.
Today, through the example of a simple moving, jumping and dashing avatar, we will explore the two approaches.

Here is a github of the complete project:
https://github.com/valgoun/CharacterController

Setup

Before anything let’s just setup the project and a basic test scene. As written in our8 Essential Gamedev Tips,we must be organized. Create these folders:

Unity: CHARACTER CONTROLLER vs RIGIDBODY (2)

Then create a very simple scene with a ground, a capsule (our Player) and a stair to play with.
Adding some verticality with the stairs will allow us to experiment with a lot of different and important things when coding our character:The way it collides with objects, the gravity when falling from a distance, and also to test the jumping mechanic if we want one.

Unity: CHARACTER CONTROLLER vs RIGIDBODY (3)

I also put a simple box as a child of our player so we can see its orientation when lookingaround).

Unity: CHARACTER CONTROLLER vs RIGIDBODY (4)

Keep your scene clean, useparents.

The Character Controller

TheCharacter Controlleris a component you can add to your player. Its function is to move the player according to the environment (thecolliders).
It doesn’t respond nor uses physics in any way.
On top of that, theCharacter Controllercomes with a Capsule Collider. Before seeing how it works, I recommend you to take a look at themanualand thescripting API, it’s always a good thing to do.

For this example, I used the default parameters but feel free to play with them to understand how they work.

Unity: CHARACTER CONTROLLER vs RIGIDBODY (5)

The core concept behind theCharacter Controlleris that it provides basic collider responses without any physics. Basically, you will move your player like you would do with aTransform, but you can’t go through colliders.
The main advantage of using this technique is the amount of control we’ll have on how your player behaves, but the downfall is that you’ll have to code practically everything.

TheCharacter Controllerincludes 2 methods used to move the character:SimpleMoveandMove.

SimpleMovetakes the speed as parameter and will move the character accordingly. On top of that, the character will respond to gravity. That’s the only physic you’ll get with the Character Controller. The downside is that the Y axisvelocity is ignored by this method.

Moverequires a little bit more work but is less limited. It takes in parameters the absolute movement. Therefore, it’s framerate dependent and you have to implement gravity on your own.

Even if it’s the more complicated method, it’s the one I prefer becauseSimpleMovebecomes very quickly limiting by the fact that you have no effect on the Y axis velocity.

Basic Movements

Let’s implement a very basic movement. For this, let’s create a new C# script named “Character” that we add to our Player/capsule. Then, we’ll need a public variable so we can tweak the speed directly from the editor and a private variable to store a reference to ourCharacter Controller.
(don’t forget to get the Character Controller in the Start method, see code below)

In theUpdatefunction, we get the Inputs, that we then store into aVector3. After, we call theMovemethod from ourCharacterControllerpassing it the Input vector multiplied by the speed and theDeltaTimeto be framerate independent and “voilà”, we have our basic movements:

public class Character : MonoBehaviour{ private CharacterController _controller;void Start() { _controller = GetComponent<CharacterController>(); } void Update() { Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); _controller.Move(move * Time.deltaTime * Speed); } }

Our character moves but doesn’t steer according to its movement. It’s easy to change the forward vector of the transform to be the movement vector:

 Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); _controller.Move(move * Time.deltaTime * Speed); if (move != Vector3.zero) transform.forward = move;

Gravity

Let’s add gravity. For that, we’ll need a… gravity variable (or you can use the global gravity with Physics.gravity.y) and private variable to store the velocity of the character.
Then, we just have to add the gravity to our player’s velocity at each update and to apply the velocity with theMovemethod:

 _velocity.y += Gravity * Time.deltaTime; _controller.Move(_velocity * Time.deltaTime);

If you try, you will probably feel the gravity as a bit weird. It’s because even when the player is grounded, the velocity is still increasing following the gravity.

To resolve this, we can reset the y velocity to 0 when the player is grounded.
TheCharacterControlleralready has a variable to know if the character is grounded but I found it buggy and I tend to determine myself if the player is grounded. I Like to use theCheckSpheremethod from the Physics Class. It returns true if any colliders intersect the sphere defined by the parameters. I like to use an emptygameObjectchild of my player as center of the sphere and then I use a variable for the radius and the layer. This way I can control in editor the way I define the grounded status of my character. (If this part seems hard to understand,take a look at my projectand recreate it on yours).

_isGrounded = Physics.CheckSphere(_groundChecker.position, GroundDistance, Ground, QueryTriggerInteraction.Ignore); if (_isGrounded && _velocity.y < 0) _velocity.y = 0f;

Here,_isGroundedis a bool variable created in the class,_groundCheckeris a reference to the child of the player (the center of the sphere),GroundDistanceis the radius of the sphere andGroundis the layer where ground objects are.

Jump

Adding a jump is pretty easy.When the jump button is pressed and the player grounded, we change the y velocity.To know which value we choose to set our velocity we can use this formula:

“velocity = JumpHeight * -2 * Gravity”

if (Input.GetButtonDown("Jump") && _isGrounded) _velocity.y += Mathf.Sqrt(JumpHeight * -2f * Gravity);

Here I chose to use some fancy math so I have a more convenient variable to control my jump (JumpHeight). Other would have chosen to use a direct variable (JumpForce) to control the jump: easier to code, trickier to control.

if (Input.GetButtonDown("Jump") && _isGrounded) _velocity.y += JumpForce;

Dash andDrag

One last example of movement you can implement: a dash. When you press the dash button your velocity will increase towards the direction you are going. Like the jump you could choose a direct implementation or use math and find a formula to make it simple to control. The maths here are a bit more complicated and I found this to works well.

 if (Input.GetButtonDown("Dash")) { Debug.Log("Dash"); _velocity += Vector3.Scale(transform.forward, DashDistance * new Vector3((Mathf.Log(1f / (Time.deltaTime * Drag.x + 1)) / -Time.deltaTime), 0, (Mathf.Log(1f / (Time.deltaTime * Drag.z + 1)) / -Time.deltaTime))); }

What we’re doing here is scaling the forward vector (our direction) following a dash vector. The dash vector depends on 2 variables, the dash distance and the drag. We need to have a drag or otherwise, the player would never stop. the idea behind the drag is to “simulate” the friction forces (with air, ground etc…). Drag is an arbitrary value between 0 and Infinity with 0 meaning no drag at all. Here I chose to use a Vector3to represent the drag so I can specify different drag values along the axis. Then to apply the drag we just need to add this at the end of the update function:

_velocity.x /= 1 + Drag.x * Time.deltaTime;_velocity.y /= 1 + Drag.y * Time.deltaTime;_velocity.z /= 1 + Drag.z * Time.deltaTime;

Character Controller Conclusion

What did we learn with this? Well, when we’re using the character controller we have a lot of freedom but at the same time we have to code a lot of stuff ourselves even for simple actions like jumping or gravity.

Now that we have made our controller with the character lets see how to make the same with aRigidbody, and what are the differences between the two approaches.

Setup

The scene setup is practically the same. The main difference is that we don’t have aCharacter Controllercomponent attached to the PlayerGameObjectbut aCapsule Colliderand aRigidbody.
On theRigidbody, set the X and Z axis rotations to be locked. Also note that I changed theDrag, we’ll see why later. The other options are default values. We can leave theCapsule Colliderto its default values.

Unity: CHARACTER CONTROLLER vs RIGIDBODY (6)

Be sure to lock the X and Z axis rotations

Rigidbody Controller

This time, I won’t explain every step of the process because it’s very similar to what we’ve done in theCharacter Controller. I’ll focus on the differences between the two. Here’s the full script to give you an overview:

public class RigidbodyCharacter : MonoBehaviour{ public float Speed = 5f; public float JumpHeight = 2f; public float GroundDistance = 0.2f; public float DashDistance = 5f; public LayerMask Ground; private Rigidbody _body; private Vector3 _inputs = Vector3.zero; private bool _isGrounded = true; private Transform _groundChecker; void Start() { _body = GetComponent<Rigidbody>(); _groundChecker = transform.GetChild(0); } void Update() { _isGrounded = Physics.CheckSphere(_groundChecker.position, GroundDistance, Ground, QueryTriggerInteraction.Ignore); _inputs = Vector3.zero; _inputs.x = Input.GetAxis("Horizontal"); _inputs.z = Input.GetAxis("Vertical"); if (_inputs != Vector3.zero) transform.forward = _inputs; if (Input.GetButtonDown("Jump") && _isGrounded) { _body.AddForce(Vector3.up * Mathf.Sqrt(JumpHeight * -2f * Physics.gravity.y), ForceMode.VelocityChange); } if (Input.GetButtonDown("Dash")) { Vector3 dashVelocity = Vector3.Scale(transform.forward, DashDistance * new Vector3((Mathf.Log(1f / (Time.deltaTime * _body.drag + 1)) / -Time.deltaTime), 0, (Mathf.Log(1f / (Time.deltaTime * _body.drag + 1)) / -Time.deltaTime))); _body.AddForce(dashVelocity, ForceMode.VelocityChange); } } void FixedUpdate() { _body.MovePosition(_body.position + _inputs * Speed * Time.fixedDeltaTime); }}

FixedUpdate

The first notable difference is theFixedUpdatefunction. This function is called by Unity before every “physic update”. Indeed, physic updates and classic updates are not synced. To achieve a convincing physic simulation, we need to calculate it smoothly. Unity decided to pull apart the physic update from the classic update. This way if the frame rate is too low or too fast, it won’t impact the simulation.

Unity: CHARACTER CONTROLLER vs RIGIDBODY (7)

Quick tip: you can change the physic frame rate (called “Fixed Timestep”) in theTime Managerin the project settings.

The idea behind theFixedUpdatefunction is that you put the physic code here. But as you can see, I still have a classicUpdatefunction. Indeed, the input system of Unity isn’t synced with theFixedUpdateso we have to retrieve the inputs inUpdate, stock them into a variable (here_inputs) and use it inFixedUpdate. You could manage the inputs directly inFixedUpdatebut you would most likely have inconvenient behaviors.

Jumping &Dashing

if (Input.GetButtonDown("Jump") && _isGrounded){_body.AddForce(Vector3.up * Mathf.Sqrt(JumpHeight * -2f * Physics.gravity.y), ForceMode.VelocityChange);}if (Input.GetButtonDown("Dash")){Vector3 dashVelocity = Vector3.Scale(transform.forward, DashDistance * new Vector3((Mathf.Log(1f / (Time.deltaTime * _body.drag + 1)) / -Time.deltaTime), 0, (Mathf.Log(1f / (Time.deltaTime * _body.drag + 1)) / -Time.deltaTime)));_body.AddForce(dashVelocity, ForceMode.VelocityChange);}

Why didn’t you put this into Fixed Update? It’s physic related.

Yes, but here I used these function in a discrete way: theseAddForcecalls are instantaneous, therefore it’s not frame dependent.AddForceis a function used to apply a force toRigidbody. There are 4 different ways to apply a force:

  • Force: continuous and mass dependent

  • Acceleration: continuous and mass independent

  • Impulse: instant and mass dependent

  • VelocityChange: instant and mass independent

Here you can see I’m usingvelocityChangebecause I want to have an instant reaction when jumping or dashing, and I don’t care about the player’s mass.

void FixedUpdate(){_body.MovePosition(_body.position + _inputs * Speed * Time.fixedDeltaTime);}

To move the character according to the player’s inputs, we use theMovePositionfunction.This function tries to move the player to a given position while respecting the collisions rules. It also doesn’t change the velocity of theRigidbody. This way, the player’s movement are “separated” from the other physic interaction.

You can see we don’t bother ourselves with gravity or drag. That’s because theRigidbodyalready does it for us. You can change the drag on theRigidbodyand the gravity in the project settings.

(Be careful with the drag, unlike the one we implemented with the Character Controller, this one isn’t split into different axes, so it’ll affect all three axes at the same time!)

Unity: CHARACTER CONTROLLER vs RIGIDBODY (8)

Main differences

Even if we want to achieve the same goals with both techniques, they won’t behave exactly the same.

Collisions

They both react with colliders but there are some slight differences. While theRigidbodywill react very precisely and even use the physics material property to calculate the reaction, theCharacter Controllerwill be more permissive: It will automatically climb slopes and steps (according to its parameters).

Unity: CHARACTER CONTROLLER vs RIGIDBODY (9)

Change these parameters to get closer to the feeling you are lookingfor.

Extensibility

In this example we didn’t have a lot of features to code and theCharacter Controllersolution was the easiest one to do, but if we were to implement more mechanics, theRigidbodywould probably be the best way to go. It offers a lot more functions to interact with physic whereas theCharacter Controllerdoesn’t, therefore we’d need to code more for the same feature using theCharacter Controller.

As you can see, we can achieve the same things using both techniques but the way we work will change according the option we choose.
That’s why I strongly advise you to always take some time to think about before starting to code any player controller script!

We hope this article was of some use to you!
If you want more details on this tutorial, or would like to request another one, please get in touch with us, on social media, the comments, etc. We’ll see it.

Follow us onTwitterandFacebook!

Unity: CHARACTER CONTROLLER vs RIGIDBODY (2024)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Lilliana Bartoletti

Last Updated:

Views: 6502

Rating: 4.2 / 5 (53 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Lilliana Bartoletti

Birthday: 1999-11-18

Address: 58866 Tricia Spurs, North Melvinberg, HI 91346-3774

Phone: +50616620367928

Job: Real-Estate Liaison

Hobby: Graffiti, Astronomy, Handball, Magic, Origami, Fashion, Foreign language learning

Introduction: My name is Lilliana Bartoletti, I am a adventurous, pleasant, shiny, beautiful, handsome, zealous, tasty person who loves writing and wants to share my knowledge and understanding with you.