help with slope "bouncing" ?

I am trying to use the following code to prevent the player from jumping up VERY steep parts of a terrain ( X Angle 65 or greater ) and STOP “bouncing” when moving back down the terrain. How WITH THIS CODE do I fix this?

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

public class PlayerMovementAdvanced : MonoBehaviour
{
    [Header("Movement")]
    private float moveSpeed;
    private float desiredMoveSpeed;
    private float lastDesiredMoveSpeed;
    public float walkSpeed;
    public float turnSpeed;
    public float sprintSpeed;
    public float slideSpeed;
    public float wallrunSpeed;
    public float climbSpeed;
    public float vaultSpeed;
    public float airMinSpeed;
    public float slopeAngle;
    public float slopeAngleIncrease;

    public float speedIncreaseMultiplier;
    public float slopeIncreaseMultiplier;

    public float groundDrag;

    [Header("Jumping")]
    public int maxJumps = 3;
    public int currentJumpCount = 0;
    public float jumpForce;
    public float jumpCooldown;
    public float airMultiplier;
    public bool canJump = true;

    [Header("Crouching")]
    public float crouchSpeed;
    public float crouchYScale;
    private float startYScale;

    [Header("Keybinds")]
    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode sprintKey = KeyCode.LeftShift;
    public KeyCode crouchKey = KeyCode.LeftControl;

    [Header("Ground Check")]
    public float playerHeight;
    public LayerMask whatIsGround;
    public bool grounded;

    [Header("Slope Handling")]
    public float maxSlopeAngle = 65.0f;

     // Setting up some values
     private Quaternion beforeMovement;
     private Quaternion target;

     private RaycastHit hit;
     private Ray ray;
     private LayerMask layer;

    private RaycastHit slopeHit;
    private bool exitingSlope;

    [Header("References")]
    public Climbing climbingScript;
    private ClimbingDone climbingScriptDone;

    public Transform orientation;

    float horizontalInput;
    float verticalInput;

    Vector3 moveDirection;

    Rigidbody rb;

    public MovementState state;
    public enum MovementState
    {
        freeze,
        unlimited,
        walking,
        sprinting,
        wallrunning,
        climbing,
        vaulting,
        crouching,
        sliding,
        air
    }

    public bool sliding;
    public bool crouching;
    public bool wallrunning;
    public bool climbing;
    public bool vaulting;

    public bool freeze;
    public bool unlimited;
   
    public bool restricted;

    public TextMeshProUGUI text_speed;
    public TextMeshProUGUI text_mode;

    private void Start ( ) {

        climbingScriptDone = GetComponent <ClimbingDone> ( );
        rb = GetComponent <Rigidbody> ( );
        rb.freezeRotation = true;

        canJump = true;

        startYScale = transform.localScale.y;

    }

    private void Update()
    {
        // ground check
        grounded = Physics.Raycast(transform.position, Vector3.down, playerHeight * 0.5f + 0.2f, whatIsGround);

        MyInput();
        SpeedControl();
        StateHandler();
        TextStuff();

        // handle drag
        if (state == MovementState.walking || state == MovementState.sprinting || state == MovementState.crouching)
            rb.drag = groundDrag;
        else
            rb.drag = 0;
    }

    private void FixedUpdate()
    {
        MovePlayer();
    }

    private IEnumerator CoolDown ( ) {
        currentJumpCount = 0;
        canJump = false;
        yield return new WaitForSeconds ( jumpCooldown );
        canJump = true;
    }

    private void MyInput ( ) {

        horizontalInput = Input.GetAxisRaw ( "Horizontal" );
        verticalInput   = Input.GetAxisRaw ( "Vertical"   );

        float slopeAngle = Vector3.Angle ( Vector3.up, slopeHit.normal );

        // When to Jump

        if ( ( Input.GetKeyDown ( jumpKey ) && canJump ) && grounded && currentJumpCount < maxJumps ) {
            Jump ( );
        }

        if ( currentJumpCount == maxJumps ) {
            StartCoroutine ( CoolDown ( ) );
        }

        // start crouch
        if (Input.GetKeyDown(crouchKey) && horizontalInput == 0 && verticalInput == 0)
        {
            transform.localScale = new Vector3(transform.localScale.x, crouchYScale, transform.localScale.z);
            rb.AddForce(Vector3.down * 5f, ForceMode.Impulse);

            crouching = true;
        }

        // Stop Crouch
        if ( Input.GetKeyUp(crouchKey ) ) {
            transform.localScale = new Vector3(transform.localScale.x, startYScale, transform.localScale.z);
            crouching = false;
        }

    }

    bool keepMomentum;
    private void StateHandler()
    {
        // Mode - Freeze
        if (freeze)
        {
            state = MovementState.freeze;
            rb.velocity = Vector3.zero;
            desiredMoveSpeed = 0f;
        }

        // Mode - Unlimited
        else if (unlimited)
        {
            state = MovementState.unlimited;
            desiredMoveSpeed = 999f;
        }

        // Mode - Vaulting
        else if (vaulting)
        {
            state = MovementState.vaulting;
            desiredMoveSpeed = vaultSpeed;
        }

        // Mode - Climbing
        else if (climbing)
        {
            state = MovementState.climbing;
            desiredMoveSpeed = climbSpeed;
        }

        // Mode - Wallrunning
        else if (wallrunning)
        {
            state = MovementState.wallrunning;
            desiredMoveSpeed = wallrunSpeed;
        }

        // Mode - Sliding
        else if (sliding)
        {

            state = MovementState.sliding;

            // increase speed by one every second
            if (OnSlope() && rb.velocity.y < 0.1f)
            {
                desiredMoveSpeed = slideSpeed;
                keepMomentum = true;
            }

            else
                desiredMoveSpeed = sprintSpeed;


        }

        // Mode - Crouching
        else if (crouching)
        {
            state = MovementState.crouching;
            desiredMoveSpeed = crouchSpeed;
        }

        // Mode - Sprinting
        else if (grounded && Input.GetKey(sprintKey))
        {
            state = MovementState.sprinting;
            desiredMoveSpeed = sprintSpeed;
        }

        // Mode - Walking
        else if (grounded)
        {
            state = MovementState.walking;
            desiredMoveSpeed = walkSpeed;
        }

        // Mode - Air
        else
        {
            state = MovementState.air;

            if (moveSpeed < airMinSpeed)
                desiredMoveSpeed = airMinSpeed;
        }

        bool desiredMoveSpeedHasChanged = desiredMoveSpeed != lastDesiredMoveSpeed;

        if (desiredMoveSpeedHasChanged)
        {
            if (keepMomentum)
            {
                StopAllCoroutines();
                StartCoroutine(SmoothlyLerpMoveSpeed());
            }
            else
            {
                moveSpeed = desiredMoveSpeed;
            }
        }

        lastDesiredMoveSpeed = desiredMoveSpeed;

        // deactivate keepMomentum
        if (Mathf.Abs(desiredMoveSpeed - moveSpeed) < 0.1f) keepMomentum = false;
    }

    private IEnumerator SmoothlyLerpMoveSpeed()
    {
        // smoothly lerp movementSpeed to desired value
        float time = 0;
        float difference = Mathf.Abs(desiredMoveSpeed - moveSpeed);
        float startValue = moveSpeed;

        while (time < difference)
        {
            moveSpeed = Mathf.Lerp(startValue, desiredMoveSpeed, time / difference);

            Debug.Log ( $"OnSlope ( ) : { OnSlope ( ) }" );

            if (OnSlope())
            {
                slopeAngle = Vector3.Angle(Vector3.up, slopeHit.normal);
                slopeAngleIncrease = 1 + (slopeAngle / 90f);

                time += Time.deltaTime * speedIncreaseMultiplier * slopeIncreaseMultiplier * slopeAngleIncrease;
            }
            else
                time += Time.deltaTime * speedIncreaseMultiplier;

            yield return null;
        }

        moveSpeed = desiredMoveSpeed;
    }

    private void MovePlayer()
    {
        if (climbingScript.exitingWall) return;
        if (climbingScriptDone.exitingWall) return;
        if (restricted) return;

        // calculate movement direction
        moveDirection = orientation.forward * verticalInput + orientation.right * horizontalInput;

        // on slope
        if (OnSlope() && !exitingSlope)
        {
            rb.drag = groundDrag;
            rb.AddForce(GetSlopeMoveDirection(moveDirection) * moveSpeed * 20f, ForceMode.Force);

            if (rb.velocity.y > 0)
                rb.AddForce(Vector3.down * 80f, ForceMode.Force);
        }

        // on ground
        else if (grounded)
            rb.AddForce(moveDirection.normalized * moveSpeed * 10f, ForceMode.Force);

        // in air
        else if (!grounded)
            rb.AddForce(moveDirection.normalized * moveSpeed * 10f * airMultiplier, ForceMode.Force);

        // turn gravity off while on slope
        if(!wallrunning) rb.useGravity = !OnSlope();

    }

    private void SpeedControl()
    {
        // limiting speed on slope
        if (OnSlope() && !exitingSlope)
        {
            if (rb.velocity.magnitude > moveSpeed)
                rb.velocity = rb.velocity.normalized * moveSpeed;
        }

        // limiting speed on ground or in air
        else
        {
            Vector3 flatVel = new Vector3(rb.velocity.x, 0f, rb.velocity.z);

            // limit velocity if needed
            if (flatVel.magnitude > moveSpeed)
            {
                Vector3 limitedVel = flatVel.normalized * moveSpeed;
                rb.velocity = new Vector3(limitedVel.x, rb.velocity.y, limitedVel.z);
            }
        }
    }

    private void Jump ( ) {

        canJump = false;

        exitingSlope = true;

        // Reset Y - Velocity

        rb.velocity = new Vector3 (
            rb.velocity.x,
            0.0f,
            rb.velocity.z
        );

        rb.AddForce (
            transform.up * jumpForce,
            ForceMode.Impulse
        );

        canJump = true;

    }

    private void ResetJump ( ) {

        currentJumpCount = 0;
        canJump = true;
        exitingSlope = false;

    }

    public bool OnSlope()
    {
        if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, playerHeight * 0.5f + 0.3f))
        {
            float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
            return angle < maxSlopeAngle && angle != 0;
        }

        return false;
    }

    public Vector3 GetSlopeMoveDirection(Vector3 direction)
    {
        return Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;
    }

    private void TextStuff()
    {
        Vector3 flatVel = new Vector3(rb.velocity.x, 0f, rb.velocity.z);

        if (OnSlope())
            text_speed.SetText("Speed: " + Round(rb.velocity.magnitude, 1) + " / " + Round(moveSpeed, 1));

        else
            text_speed.SetText("Speed: " + Round(flatVel.magnitude, 1) + " / " + Round(moveSpeed, 1));

        text_mode.SetText(state.ToString());
    }

    public static float Round(float value, int digits)
    {
        float mult = Mathf.Pow(10.0f, (float)digits);
        return Mathf.Round(value * mult) / mult;
    }
}

MUCH appreciation and thanks for all of the help!

I would just add a simple Raycast down to sense the ground when you’re walking around.

As long as it’s not suddenly massively lower (eg, you stepped off a cliff), then constantly force the player to track the ground based on what is returned from the RaycastHit object.

how would you do that exactly? and is this accurate? i just wanna use the variable, “maxSlopeAngle” to detect the angle and then use that to somehow disable Jump. Can you show me a simple example of how to do what you are describing above?

Just use a collider as a zone…when you’re in the zone disable jumping.

how do you use a collider as a zone? i also need to stop the player from bouncing when moving down a terrain.

What exactly do you mean bouncing? Does it happen if you were just to try a controller from unity?

what i mean is when my player should be moving down the terrain, instead of sliding down the terrain, it lets the player “fly” off the terrain in a forward direction. its as if the player is moving down a set of stairs.

how with my above script do i stop my player from bouncing down the terrain like walking down a set of steps?

https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html

yes, i know about ontriggerenter. but how do i use a collider as a zone? i would think just checking if the player is colliding with the terrain, that it wouldnt do anything to stop bouncing down the hill?

Here’s a small clip of what’s going on :

ringedachingasianelephant

no matter what i try, it keeps bouncing when moving down or up the terrain…

You should try what I suggested in my first post;

1- every time you move, raycast to the ground

2- as long as it is not “too much lower” than the previous frame, set your player’s Y position to match the RaycastHit point.

If you’re struggling to implement that, set your project / scene aside, put a cube in the world and make it move over an uneven surface using raycast. There’s tutorials on raycast and on RaycastHit and how to read and process the results.

1 Like

What?? I do not understand psuedo english…

Being rude because you don’t understand isn’t going to get you the help you need. It’d be much nicer if you simply said you don’t follow and ask more questions.

If you are comfortable wrangling a 400+ line character controller, I assume you’re capable of a few extra lines of code to do a raycast, consider the result of the point in RaycastHit.point, and extract the y coordinate for your player.

If you are not, then perhaps you want to try out some other character controllers, but your original post was extremely poignantly focused on “WITH THIS CODE” so I didn’t suggest that immediately.

I stand with what I posted above.

number 1: I am not trying to be rude, I am sorry you read it that way.
number 2 : i am only trying to fix my code so the player stops bouncing down the hill.

Fair enough. It did come across as rude but I understand it’s just text and can sound wrong. :slight_smile:

@Kurt-Dekker : 2- as long as it is not "too much lower" than the previous frame, set your player's Y position to match the RaycastHit point.

how do i detect if it’s not “too much lower” than the previous frame and how do i set the position y to match the hit point? thats the part i don’t understand. #1 i understand clearly.

You should be able to adjust some of those variables in the inspector to get the movement you want…or at least a happy medium. Or use a collider to create a zone to then change some of those values then when exit change them back.

The zone is just the collider…when your player is inside it you can trigger something…like a door opening. Just make it bigger.

The pseudo English @Kurt-Dekker was telling you is so you get the hang of doing what needs to get done. Getting outside of your scene and test using the ray cast, instead of doing it in your scene. It’s a good way to practice.

i still don’t understand : 2- as long as it is not "too much lower" than the previous frame, set your player's Y position to match the RaycastHit point.

how do i detect if it’s not “too much lower” than the previous frame and how do i set the position y to match the hit point?