issue with slope function

so ive got this onSlope ( ) function. im trying to stop the player from bouncing down the Terrain slope when moving down a terrain. i don’t understand why this function is not working? I’m trying to prevent the player from bouncing down the terrain and bouncing up when moving up and over the edge of a steep slope.

public bool OnSlope ( ) {
    Vector3 Pivot = orientation.transform.position;
    Vector3 TerrainSize = terrain.terrainData.size;
    float x = Pivot.x / TerrainSize.x;
    float z = Pivot.z / TerrainSize.z;
    Vector3 InterpolatedNormal = terrain.terrainData.GetInterpolatedNormal ( x, z );
    if ( Physics.Raycast ( transform.position, Vector3.down, out slopeHit, playerHeight / 2.5f ) ) {
        if ( InterpolatedNormal != Vector3.up ) {
            return true;
        }
        else {
            return false;
        }

    }
    return false;
}

Any help is absolutely appreciated!

MUCH thanks and appreciation!

Hello,

Maybe like this?

https://www.youtube.com/watch?v=PEHtceu7FBw

Thank you but that uses a Character Controller. I am using a Rigidbody. It must work for a terrain created in Unity. not a game object.

Have you tried using a similar down ray, and use the hit.normal to get the direction of the slope, to add force in the same directions as the slope? Or why not make the rigidbody kinematic while walking if you don’t want the physics behaviour?

let me try that.

so turning on kinematics while moving only froze my capsule in place.

This one contains some more tips.

https://www.youtube.com/watch?v=xCxSjgYTw9c

Yea, I saw this already. I'm actually using that very same project. The problem is the code he has for slopes only works on a game object slope. not a unity terrain slope...

Physics.Raycast works on Unity Terrain the same as on Unity GameObjects, therefore you don’t need special handling for Terrains.

@Kreshi then why is my player bouncing up and down when moving up and down the terrain? Here’s my full code :

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

public class PlayerMovementAdvanced : MonoBehaviour {

    [ Header ( "Mouse Settings" ) ]
    public Texture2D cursorTexture;
    public CursorMode cursorMode = CursorMode.Auto;
    public Vector2 hotSpot = Vector2.zero;

    [Header("Movement")]
    public float walkSpeed;
    public float turnSpeed;
    public float sprintSpeed;
    public float slideSpeed;
    public float wallrunSpeed;
    public float climbSpeed;
    public float vaultSpeed;
    public float airMinSpeed;
    private float moveSpeed;
    private float desiredMoveSpeed;
    private float lastDesiredMoveSpeed;
    private 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 Terrain terrain;
    public float maxSlopeAngle = 45.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;

    public static bool IsNumFloat ( object val ) {
        if ( val.GetType ( ) == typeof ( float ) ) { return true; }
        return false;
    }

    public static bool IsNumInt ( object val ) {
        if ( val.GetType ( ) == typeof ( int ) ) { return true; }
        return false;
    }

    public static bool IsNum ( int val ) {
        return (
            IsNumFloat ( val ) ||
            IsNumInt   ( val ) ?
            true : false
        );
    }

    public static bool IsNum ( float val ) {
        return (
            IsNumFloat ( val ) ||
            IsNumInt   ( val ) ?
            true : false
        );
    }

    public static bool StringIsFloat ( string value ) {
        float floatValue;
        return (
            float.TryParse ( value, out floatValue )
        );
    }

    public static bool StringIsInt ( string value ) {
        int intValue;
        return (
            Int32.TryParse ( value, out intValue )
        );
    }

    private void OnValidate ( ) {

        // Ternary Format : Boolean ? true : false
        var isVarNum = (
            IsNumFloat ( maxSlopeAngle ) ||
            IsNumInt   ( maxSlopeAngle ) ?
            true : false
        );

        if ( isVarNum == true ) {

            if ( maxSlopeAngle <= 0.0f || maxSlopeAngle > 90.0f ) {

                maxSlopeAngle = 1.0f;

                Debug.LogError ( $"ERROR :: `{ maxSlopeAngle }` is not a valid `maxSlopeAngle...`" );
                Debug.LogError ( $"Please make sure `maxSlopeAngle` is `greater than` `0` and less than or equal to `89`..." );
                Debug.LogWarning ( $"I have automatically set `maxSlopeAngle` to `{ maxSlopeAngle }` for you..." );

            }

        }

    }

    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 ( );

        // FAILED ATTEMPT ???
        // BounceControl ( );

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

        // Handle Drag
        if ( state == MovementState.walking || state == MovementState.sprinting || state == MovementState.crouching ) {
            rb.drag = groundDrag;
        }
        else
        {
            rb.drag = 0.0f;
        }

    }

    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 ( !OnSlope() )
        {
            // turn gravity off while on slope
            if (!wallrunning) rb.useGravity = !OnSlope();
        }

        if ( ( Input.GetKeyDown ( jumpKey ) && canJump ) && grounded && currentJumpCount < maxJumps )
        {
            if ( slopeAngle < maxSlopeAngle ) { 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);

            Vector3 Pivot = transform.position;

            Vector3 TerrainSize = terrain.terrainData.size;

            float x = Pivot.x / TerrainSize.x;
            float z = Pivot.z / TerrainSize.z;

            Vector3 InterpolatedNormal = terrain.terrainData.GetInterpolatedNormal ( x, z );

            Gizmos.DrawSphere ( Pivot, 0.2f );
            Gizmos.DrawRay ( Pivot, InterpolatedNormal );

            GUI.color = Color.blue;

            UnityEditor.Handles.Label (
                Pivot,
                InterpolatedNormal + " : Normal Vector of terrain"
            );

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

                // Debug.Log ( $"slopeAngleIncrease : { slopeAngleIncrease }" );

                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 ).normalized * moveSpeed * 1000f, ForceMode.Force );

            // add additional gravity on slope
            if ( rb.velocity.y > 0.0f )
            {
                rb.AddForce (
                    Vector3.down * 80f,
                    ForceMode.Force
                );
            }

        }

        // On Ground

        else if ( grounded )
        {

            rb.AddForce (
                moveDirection.normalized * moveSpeed * 10.0f,
                ForceMode.Force
            );

        }

        // In Air

        else if ( ! grounded )
        {

            rb.AddForce (
                moveDirection.normalized * moveSpeed * 10.0f * airMultiplier,
                ForceMode.Force
            );

        }

        if ( grounded && ! OnSlope ( ) )
        {

            rb.AddForce (
                moveDirection.normalized * walkSpeed *
                speedIncreaseMultiplier,
                ForceMode.Acceleration
            );

        }

        if ( grounded && OnSlope ( ) )
        {

            rb.AddForce (
                GetSlopeMoveDirection ( moveDirection ).normalized *
                sprintSpeed * speedIncreaseMultiplier,
                ForceMode.Acceleration
            );

        }

        if ( ! grounded )
        {

            rb.AddForce (
                moveDirection.normalized * walkSpeed *
                speedIncreaseMultiplier * airMultiplier,
                ForceMode.Acceleration
            );

        }

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

    }

    // private void BounceControl ( ) {

        // limiting `bounce` on slope
        // if (OnSlope() && !exitingSlope)
        // {

            // if the `ray` `hit` the `slope`
            // if ( Physics.Raycast ( transform.position, Vector3.down, out slopeHit, playerHeight / 2.5f ) )
            // {
                // STOP BOUNCE ???
                // Physics.gravity = new Vector3 ( 0.0f, -300.0f, 0.0f );
            // }
            // else
            // {
                // RETURN GRAVITY TO NORMAL ...
                // Physics.gravity = new Vector3 ( 0.0f, -9.81f, 0.0f );
            // }

        // }

    // }

    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;

    }

    // ORIGINAL SLOPE-BOUNCE PREVENTION ...
    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;
    }

    // ALTERNATIVE SLOPE-BOUNCE PREVENTION ???
    // public bool OnSlope ( ) {
        // if ( Physics.Raycast ( transform.position, Vector3.down, out slopeHit, playerHeight / 2.5f ) ) {
            // if ( slopeHit.normal != Vector3.up ) {
                // return true;
            // }
            // else
            // {
                // return false;
            // }

        // }
        // 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;
    }

}

I haven’t analyzed your code yet, but probably because you haven’t checked “Terrain” layer for the whatIsGround LayerMask I could imagine :).

@Kreshi : How would I do that?

You can configure the whatIsGround LayerMask variable in the inspector (of your script).

The image is not working for me…

@Kreshi : Unless you mean THIS, I attached the photo.

Your Terrain has the layer Terrain, so if you click on your playerobject that has your script “PlayerMovementAdvanced” attached to it, you can configure the whatIsGround variable on its inspector. Just select the Terrain layer additionally to what was already selected.

@Kreshi : Here's a small clip of what's going on :

parallelsizzlingakitainu

@Kreshi : I already have whatisGround set

8249643--1079424--terrain-layer-2.jpg

And this didn’t work? Ok, I will check your script then…

ok