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
(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!