Trying to physics-ify player controller with a Rigidbody

Hi there - relative newbie here,

My first-person character controller will respond to inputs to move/jump/fire/etc. (mapped to an Input Actions Editor, called through the “StarterAssetsInputs” script) but there are some bugs introduced when I try to add a Rigidbody to its hierarchy so that it will be affected by things like AddExplosionEffect, or taking a split second to stop movement because of its own mass, etc.

Admittedly, setting up player controllers is kind of a blind spot for me, so initially I looked for a controller that appeared to be decently modular, and I went with Unity’s “Starter Assets - FirstPerson” controller. It works pretty well out of the box.

I want to add a rigidbody but the way it’s set up, it appears devoid of any physics-based movement whatsoever - shortly I’ll show you some code so you see what I mean.

In it’s somewhat-expanded version now, the player hierarchy looks like this in my project (with relevant components mentioned):

(Parent)Player (empty game object with scripts: First Person Controller, Basic Rigid Body Push, Starter Assets Inputs

Move code only: StarterAssetsInputs
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif

namespace StarterAssets
{
	public class StarterAssetsInputs : MonoBehaviour
	{
            public Vector2 move;

#if ENABLE_INPUT_SYSTEM
		public void OnMove(InputValue value)
		{
			MoveInput(value.Get<Vector2>());
		}
#endif
		public void MoveInput(Vector2 newMoveDirection)
		{
			move = newMoveDirection;
		} 
Move code only: FirstPersonController
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif

namespace StarterAssets
{
	[RequireComponent(typeof(CharacterController))]
#if ENABLE_INPUT_SYSTEM
	[RequireComponent(typeof(PlayerInput))]
#endif

	public class FirstPersonController : MonoBehaviour
	{
		[Tooltip("Move speed of the character in m/s")]
		public float MoveSpeed = 4.0f;

#if ENABLE_INPUT_SYSTEM
		private PlayerInput _playerInput;
#endif
		private CharacterController _controller;
		private StarterAssetsInputs _input;

		private void Move()
		{
			// set target speed based on move speed, sprint speed and if sprint is pressed
			float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;

			// a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon

			// note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
			// if there is no input, set the target speed to 0
			if (_input.move == Vector2.zero) targetSpeed = 0.0f;

			// a reference to the players current horizontal velocity
			float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;

			float speedOffset = 0.1f;
			float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;

			// accelerate or decelerate to target speed
			if (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset)
			{
				// creates curved result rather than a linear one giving a more organic speed change
				// note T in Lerp is clamped, so we don't need to clamp our speed
				_speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * SpeedChangeRate);

				// round speed to 3 decimal places
				_speed = Mathf.Round(_speed * 1000f) / 1000f;
			}
			else
			{
				_speed = targetSpeed;
			}

			// normalise input direction
			Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;

			// note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
			// if there is a move input rotate player when the player is moving
			if (_input.move != Vector2.zero)
			{
				// move
				inputDirection = transform.right * _input.move.x + transform.forward * _input.move.y;
			}

			// move the player
			_controller.Move(inputDirection.normalized * (_speed * Time.deltaTime) + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
		}

NOTE: I’m not necessarily expecting the answer to be in the code, but I figured it may be relevant. If it would help for me to show the entire code, I’ll do it. I just don’t want to make the post too long on first glance :slight_smile:

(Child)PlayerCameraRoot (empty game object that is the target of the cinemachine brain that comes with the prefab)

(Child)Capsule (capsule mesh - no rigidbody by default)

Adding a rigidbody to the parent “Player” removes the ability to jump again after you do so once, causes the “grounded” bool to flicker every frame afterwards, and causes some wicked slowdown going up ramps, and adding it to “Capsule” apparently does nothing except cause the capsule to drift from the actual player position the more I move.

I’m mildly concerned that since the controller prefab appears devoid of any physics logic, this is a case of technical debt, and I’ll have to start over with a controller that handles physics from the get-go (better to catch tech debt early, though!). It wouldn’t be the worst thing in the world but it would be a little annoying to reconfigure the weapon system with the new player controller.

Let me know what you think can be done, if I can clarify anything, or if it should be blown up.

Thanks! :smiley:

The starter assets character controller isn’t physics based. Meaning if you want a physics based one, you would have to write that from scratch, or find an off-the-shelf on the asset store/elsewhere.

Though the bottom line is that you would have to rewrite the FirstPersonController to act upon a rigidbody and not a CharacterController component (removing it from the player as well).

If you wanted to use API methods such as AddExplosionEffect then you would also not be able to move it via its Rigidbody.velocity (or linearVelocity in Unity 6) as doing so would override any forces.

Thank you for the response! Very helpful.
Two quick questions -

  1. If I’m understanding you correctly, using Rigidbody.linearVelocity for movement would essentially override/nullify AddExplosionForce? So is there any other rigidbody-moving method available that may not be as explosion-y but still conceivably induce knockback for a moment?

  2. Probably easier - would there be a way to simulate knockback on a non-rigidbody controller that using some method other than AddExplosionForce ?

If I misunderstood anything, please let me know. Ultimately I’m not married to having a rigidbody or not havng one. I’ll go with whichever can bring about this little feature most feasibly. Thanks again for your help!

Manually setting Rigidbody.linearVelocity overrides any AddForce or similar forces. This is because what AddForce and similar are doing is modifying the existing velocity. If you assign the velocity, then this just overrides any calculations to the velocity applying forces regularly would do.

For the most consistent behaviour you want to use only Rigidboy.linearVelocity OR only AddForce and similar, though the latter can work with the former. Namely you can have a regular AddForce based movement, with some situations where you might directly assign a velocity, so long as you understand the behaviour this will have with other systems trying to move the player via forces.

So to answer this question:

No. If you assign velocity directly, any AddForce methods will do nothing. You would have to integrate a system that records forces and adds them together, and applies that to the players movement velocity before being given to the rigidbody.

You would have to do the same as mentioned above, but with the CharacterController component instead.