Player "bouncing" down slopes

Hello, my player is not running down slopes correctly. He is “bouncing” down them almost, and I believe this is as a result of “isGrounded” in the CharacterController. Has anyone got a fix for this?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    public CharacterController controller;
    public float InputX;
    public float InputY;
    public float speed;
    public float RotateSpeed;
    public float maxSpeed;
    public float maxRunSpeed;
    public float acceleration;
    public float deceleration;
    public bool running;
    public float jumpPower;
    public float gravity;
    public Vector3 moveDirection;
    public float groundDetection;

    // Start is called before the first frame update
    void Start()
    {
        controller = GetComponent<CharacterController>();
        moveDirection = Vector3.zero;
    }

    // Update is called once per frame
    void Update()
    {
        InputX = Input.GetAxis("Horizontal");
        InputY = Input.GetAxis("Vertical");
        //old, doesn't take rotation into account
        //moveDirection = new Vector3(InputX, moveDirection.y, InputY);
        moveDirection = transform.TransformDirection(new Vector3(InputX, moveDirection.y, InputY));
        moveDirection.x *= speed;
        moveDirection.z *= speed;
        running = Input.GetButton("Fire3");

        if(controller.isGrounded)
        {
            moveDirection.y = 0.0f;

            if (Input.GetButtonDown("Jump"))
            {
                moveDirection.y = jumpPower;
            }
        }

        //moveDirection.y -= gravity * Time.deltaTime;
        moveDirection.y -= gravity;

        if (InputX != 0 || InputY != 0)
        {
            speed += acceleration;

            if (running == true)
            {
                if (speed >= maxRunSpeed)
                {
                    speed = maxRunSpeed;
                }
            }
            else
            {
                if (speed >= maxSpeed)
                {
                    speed = maxSpeed;
                }
            }
        }
        else
        {
            speed -= deceleration;

            if (speed <= 0)
            {
                speed = 0;
            }
        }

        controller.Move(moveDirection * Time.deltaTime);

        //Rotate the player on the X axis (originally in camera)

        //Set up X input
        float MouseX = Input.GetAxis("Mouse X");

        //Set up input and rotate if LMB is held down
        float LookX = MouseX * RotateSpeed;

        if (Input.GetButton("Fire1"))
        {
            transform.Rotate(0, LookX, 0);
        }

    }
}

P.S. if this code is bad or hard to read, I am sorry. I’m not very experienced.

1 Like

Add debugging to investigate what isGrounded is doing. Also play with some of the charactercontroller properties such as skin width.

You’re moving your character without taking into consideration the slope of the ground you’re running on, meaning that if you’re moving down the slope, your character will necessarily come away from the slope a bit, and then fall back down onto it. The bouncing is occurring as a side effect of cancelling out the y velocity when you’re grounded (which is correct to do).

To fix it, make your movement direction conform to the slope you’re standing on, rather than being flat in all scenarios.

2 Likes

This is the correct answer. But to elaborate, here’s how I handle this: In my code that checks whether the player is grounded, I’m using a Raycast (more of less) to see if I hit the ground just below my feet. One of properties on a Raycast is the “normal” direction of the hit, which tells you whether the ground under your feet is horizontal, or somewhat vertical. Based on that angle, you can then redirect your “walking forward/backward/left/right” vector to be parallel to the surface you’re standing on.

Essentially, if you’re always applying horizontal force, it’s a bit like you’re jumping out into thin air, then falling a tiny bit onto the sloped surface, over and over again. But if you were applying that force diagonal, parallel to the sloped surface, you’d stay on the surface the whole time.

1 Like

Stumbling across this problem myself, and after the pointers given by dgoyette and Madgvox, i was able to come up with a few lines of code that could fix this “grounding problem” that the charactercontroller has… atleast, working for me. Tested it along with wallrunning, climbing, floating, falling and jumping, so it may not be 100% fool proof.Also, debug.log the character controller .isgrounded and see that its not as buggy as stated in 5k other threads!

First I shoot a ray down (already had one for the states mentioned above).

rayDown = new Ray(transform.position, Vector3.down * <insert value>);

Then I make a basic Physics.RayCast (since im using multiple properties in the out info)

Physics.Raycast(rayDown, out hitDownInfo, <insert value>);

Now I assign a RayCastHit Normal Normalized! Y value to a float value. ONLY if its less than 0 (there is no problem, jumping or stutter while moving up, if so you could try to always apply this force)

if(hitDownInfo.normal.Y < 1)
{
    <float> moveY = hitDownInfo.normal.normalized.y;
}
else
{
 moveY = 0;
}

When applying the movement direction. Before Controller.Move() ( I apply gravity before this, might work in another way havent tested it)

moveDirection = new Vector3(horizontalMovement, -moveY, vertMovement).normalized

I hope this will help you or anyone else out having the same problem!

EDIT: Tested this against a slope with a steepness i couldnt even move up to, but was able to walk down perfectly. this may or may not be something you want, but atleast youll fix the jitter/stutter when moving down using the standard character controller.

I’m glad that works, but I’m struggling to explain why it necessarily works in a specific/accurate way. Nothing against your approach, I just don’t follow some of the decisions you’ve made in this code.

In general, I think what you’re ultimately doing is just pushing the character down somewhat if they’re walking down a slope, but I wouldn’t be convinced it’s the right amount of downward force. The values you chosen seem a bit arbitrary to me. Some thoughts:

  • I think a ‘normal’ coming off a raycast is already normalized, so I don’t think you need to normalize it.
  • Taking the y-component of a normal vector seems a bit unusual. A normal vector just refers to a direction, without any sense of magnitude, so the y-component of that is just a somewhat indirect way to scaling based on the angle of the slope. A high y-value means mostly flat, where a lower y-value means a steeper slope. I think it’s probably okay to use the y-value to assess the slope, but you’re then using it to control how much downward movement to apply. That seems a bit weird to me. I’d expect that depending on the slope, that might work fine in some cases, but not as well in other cases. And sometimes, it might result in too much downward force, preventing the player from moving reliably. It’s hard to say.
  • It seems really weird that you’re setting moveDirection’s z-component to the vertical movement. Why would the vertical movement be placed into z, which is generally a horizontal axis?
  • Finally, I don’t understand why you’d normalize that moveDirection.

In total, I can see how this might work, but it feels like there are a few parts to this that merely happen to work. I’d be happy to understand the reasoning better for all of these decisions, since to me it just kind of feels like it works more by chance (by, generally, pushing the player down somewhat), and not because it’s provably or clearly the right approach.

vertMovement refers to the vertical input axis, not the vertical world axis.

Thats correct, it refers to the vertical input axis.

Im normalizing my movement input to prevent walking faster while walking diagonally.

About normalizing, guess that was pretty much overdoing it.
You were right on setting normal directly to the movedirection, after some testing i found out it slows down my player when moving up or down on slopes. (this is probably due to using normalized movedirection) trying to fix this i’ve come up with this. Its working so far, but should do some more testing when i got the time. For now, as far as I can tell there is no bounce, it doesnt slow down the player, it adds force down based on movement speed only if the velocity Y is less than 0 (“falling state”) and the controller.isGrounded stays true except for when im “sprinting” from flat to a slope downwards which seems pretty “realistic” to me. (just try running down an elevator and youre bound to miss a few steps x).

moveDirection = new Vector3(horizontalMovement, 0, vertMovement).normalized;
                moveDirection = transform.TransformDirection(moveDirection);
                moveDirection *= speed;

                if (controller.velocity.y < 0)
                {
                    if (hitDownInfo.normal.y < 1)
                    {
                        slopeMovementY = hitDownInfo.normal.y * setSpeed;
                        moveDirection.y = -slopeMovementY;
                    }
                    else
                    {
                        slopeMovementY = 0;
                    }
                }

Gravity is applied after this btw. SlopeMovementY could be replaced by setting it to the moveDirection.y directly, but for now (while testing) it just makes it easier to read atleast for me.

1 Like