I know this is a common issue and while searching for solutions every single one of’em seem to be so complicated… Is there any simple way of fixing this please? I’m using a Capsule Collider (3D), Rigidbody and here is my full movement code if you wanna take a look at it.
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
private PlayerInput _inputManager;
private InputAction _inputAction;
[Header("Public References")]
public LayerMask groundLayer;
public PhysicsMaterial defaultMat;
public PhysicsMaterial slopeMat;
[Header("Movement ")]
public float speed;
public float airMultiplier;
[Header("Jumping")]
public float gravityValue;
public float jumpForce;
public float jumpCooldown;
bool _readyToJump;
[Header("Slope Handling")]
Vector3 moveDirection;
public float maxSlopeAngle;
private RaycastHit _slopeHit;
public float playerHeight;
[Header("Raycast")]
public float downForce;
public float sphereDistance;
public float radius = 5f;
public float distance = 1.0f;
private const string _horizontal = "Horizontal";
private const string _vertical = "Vertical";
private const string _lastVertical = "LastVertical";
private const string _lastHorizontal = "LastHorizontal";
private Rigidbody _rb;
private CapsuleCollider _capsuleCollider;
private Animator _animator;
private bool isSloped()
{
//Cast a ray downwards to detect the slope's angle.
if (Physics.Raycast(transform.position, Vector3.down, out _slopeHit, playerHeight))
{
float angle = Vector3.Angle(Vector3.up, _slopeHit.normal);
return angle < maxSlopeAngle && angle != 0;
}
return false;
}
private Vector3 GetSlopeMoveDirection()
{
//Projects a Vector3 following the slope's angle
return Vector3.ProjectOnPlane(_rb.linearVelocity.normalized, _slopeHit.normal).normalized;
}
void Start()
{
_capsuleCollider = GetComponentInChildren<CapsuleCollider>();
_readyToJump = true;
_animator = GetComponent<Animator>();
_rb = GetComponent<Rigidbody>();
_inputManager = GetComponent<PlayerInput>();
_inputAction = _inputManager.actions.FindAction("Move");
}
private void Jump(InputAction.CallbackContext context)
{
//If you are holding the jump key, you are grounded and ready to jump
if (context.performed && isGrounded() && _readyToJump)
{
_rb.linearVelocity = new Vector3(_rb.linearVelocity.x, jumpForce, _rb.linearVelocity.z);
Invoke(nameof(ResetJump), jumpCooldown);
}
//If you stop pressing the jump key as long as you are jumping. Gotta tweak this a little bit because of the "isGrounded()" part it dont work really well
if (context.canceled && _rb.linearVelocity.y > 0 && isGrounded())
{
_readyToJump = false;
_rb.linearVelocity = new Vector3(_rb.linearVelocity.x, jumpForce * 0.5f, _rb.linearVelocity.z);
Invoke(nameof(ResetJump), jumpCooldown);
}
}
private void MovePlayer()
{
Vector2 _inputValue = _inputAction.ReadValue<Vector2>();
//GROUND MOVEMENT
Vector3 _groundMovement = new Vector3(_inputValue.x * speed * 5, _rb.linearVelocity.y, _inputValue.y * speed * 5) * Time.deltaTime;
//AIR MOVEMENT
Vector3 _airMovement = new Vector3(_inputValue.x * speed * 5 * airMultiplier, _rb.linearVelocity.y, _inputValue.y * speed * 5 * airMultiplier) * Time.deltaTime;
//SLOPE MOVEMENT
Vector3 _slopeMovement = new Vector3(GetSlopeMoveDirection().x * speed * 5, _rb.linearVelocity.y, GetSlopeMoveDirection().z * speed * 5) * Time.deltaTime;
//Manage player's walking animations
_animator.SetFloat(_horizontal, _inputValue.x);
_animator.SetFloat(_vertical, _inputValue.y);
if (_inputValue != Vector2.zero)
{
_animator.SetFloat(_lastHorizontal, _inputValue.x);
_animator.SetFloat(_lastVertical, _inputValue.y);
}
//Handle both air movement and ground movement
if (isGrounded())
{
_rb.linearVelocity = new Vector3(_groundMovement.x, _rb.linearVelocity.y, _groundMovement.z);
}
//If is on the ground and on the slope
else if (isGrounded() && isSloped())
{
_rb.linearVelocity = new Vector3(_slopeMovement.x, _rb.linearVelocity.y, _slopeMovement.z);
}
//If is not grounded
else
{
_rb.linearVelocity = new Vector3(_airMovement.x, _rb.linearVelocity.y, _airMovement.z);
}
//Handle (fake) gravity for when the player is falling
if (_rb.linearVelocity.y < 0)
{
_rb.AddForce(Vector3.down * gravityValue, ForceMode.Impulse);
}
}
void Update()
{
if (isSloped())
{
_capsuleCollider.material = slopeMat;
}
else
{
_capsuleCollider.material = defaultMat;
}
Debug.DrawRay(transform.position, -Vector3.up * distance, Color.blue);
HandleDirection();
}
public void HandleDirection()
{
//This whole code is a complete mess, but with it I can sorta get the player's facing direction
if (_rb.linearVelocity.z > 1)
{
Debug.DrawRay(transform.position, Vector3.forward, Color.green);
moveDirection = Vector3.forward;
}
else if (_rb.linearVelocity.z < 0)
{
Debug.DrawRay(transform.position, -Vector3.forward, Color.green);
moveDirection = -Vector3.forward;
}
else if (_rb.linearVelocity.x > 1)
{
moveDirection = Vector3.right;
Debug.DrawRay(transform.position, Vector3.right, Color.green);
}
else if (_rb.linearVelocity.x < 1)
{
moveDirection = -Vector3.right;
Debug.DrawRay(transform.position, -Vector3.right, Color.green);
}
}
void ResetJump()
{
_readyToJump = true;
}
void FixedUpdate()
{
MovePlayer();
}
void LateUpdate()
{
}
public bool isGrounded()
{
RaycastHit hit;
Physics.Raycast(transform.position, -Vector3.up, out hit, distance);
if (hit.collider != null)
{
Debug.Log("is Grounded");
return true;
}
return false;
}
}
Thanks in advance!