character controller slide down slope

Hi all, I am using the character controller and am attempting to have the character slide down slopes that are greater then the slope limit set in the character controller. I have tried numerous methods to get it to work consistently that usually involve spherecasts and raycasts to the surface and mathing out the vector with distance hits.

Is there a simpler way to detect the down slope angle of the surface the character controller is standing on so that it can slide down the correct direction? Depending on how the ‘Slope Limit’ option in the character controller works it is possible that it can be directly read from somewhere? Otherwise how does it know that it has hit that limit if it can’t measure it?

I have seen a few similar questions but have not found a way that worked consistently.

Thanks

This is something that is not available by default in the character controller. The problem with sliding off the slopes is that gravity pulls the player down, but to slide off the slope, the player has to be pulled sideways to the direction of the slope’s normal and that simply does not happen. We will be replacing the default controller.isGrounded and controller.slopeLimit with our own stuff. So set controller’s slope limit to something like 80 degrees because we will need that for out code to work.

From my Unet-Controller:

Get the ground normal using OnControllerColliderHit

void OnControllerColliderHit (ControllerColliderHit hit) {
	hitNormal = hit.normal;
}

Then, after the controller.Move, determine if the controller should be grounded or not:

isGrounded = (Vector3.Angle (Vector3.up, hitNormal) <= slopeLimit);

Then, before calling controller.Move, add sideways speed to allow it go down:

//Character sliding of surfaces
if (!isGrounded) {
	inpRes.speed.x += (1f - hitNormal.y) * hitNormal.x * (1f - slideFriction);
	inpRes.speed.z += (1f - hitNormal.y) * hitNormal.z * (1f - slideFriction);
}

Experiment with the slideFriction value, 0.3 works rather well.

This should do the trick for you.

@AurimasBlazulionis’ script worked very well, but I had to tweak it to work with the default FirstPersonController. To help newcommer to undate the actual Unity official script, I’ve made a few minor adjustments. Oppositly to the original slopeLimit, I’ve set a much lower value (like 40 instead of 80, for the script to work). First, we need to define the variables at the beginning of the scripts:

private bool isGrounded; // is on a slope or not
public float slideFriction = 0.3f; // ajusting the friction of the slope
private Vector3 hitNormal; //orientation of the slope.

Get the ground normal by adding this line in the void OnControllerColliderHit:

private void OnControllerColliderHit(ControllerColliderHit hit)
{
   hitNormal = hit.normal;
   ...

Then, after the m_CharacterController.Move (in the FixedUpdate), determine if the controller should be grounded or not:

isGrounded = Vector3.Angle (Vector3.up, hitNormal) <=  m_CharacterController.slopeLimit;

Then, before calling m_CharacterController.Move (in the FixedUpdate), add sideways speed to allow it go down:

if (!isGrounded) {
	m_MoveDir.x += (1f - hitNormal.y) * hitNormal.x * (speed - slideFriction);
	m_MoveDir.z += (1f - hitNormal.y) * hitNormal.z * (speed - slideFriction);
}

Thanks again @AurimasBlazulionis for your great help, all credit goes to that person !

To improve upon @AurimasBlazulionis answer I found it better to multiply a slide speed at the end of the formula. The problem with his friction method is it can only go so fast no matter how low you put the friction.

slideDirection.x = ((1f - hit.normal.y) * hit.normal.x) * slideSpeed;
slideDirection.z = ((1f - hit.normal.y) * hit.normal.z) * slideSpeed;

A slideSpeed of 6 works pretty well. This slides very fast and is much more realistic.

// ON START() CREATE SLIDE TRANSFORM

    slideGameObject = Instantiate(new GameObject(), transform);
    slideGameObject.name = "UsedForSliding";
    slideTransform = slideGameObject.transform;

// FIND THE POINT WE HIT
void OnControllerColliderHit(ControllerColliderHit hit) { collisionPoint = hit.point; }

RaycastHit groundHit;
Ray groundRay;

// on Update:

        groundRay.origin = collisionPoint + Vector3.up * .05f;
        groundRay.direction = Vector3.down;


        
        slideTransform.rotation = transform.rotation;



        // IF WE ARE ON A SURFACE, DETERMINE IF WE SHOULD BE SLIDING

        if (Physics.Raycast(groundRay, out groundHit, .6f)){


            

            slopeAngle = Vector3.Angle(transform.up, groundHit.normal); // The angle of the slope is the angle between up and the normal of the slope

            if(slopeAngle <= playerCC.slopeLimit) // if angles arent too high
            {
                sliding = false;
            }
            else if(slopeAngle > playerCC.slopeLimit) // if angles are too high
            {
                if (groundHit.transform.gameObject.tag.Contains("Sl") & playerCC.velocity.y <= 0)
                {
                    sliding = true;


                    Vector3 groundCross = Vector3.Cross(groundHit.normal, Vector3.up);
                    slideTransform.rotation = Quaternion.FromToRotation(transform.up, Vector3.Cross(groundCross, groundHit.normal)); // collect the transform of the ground we need to slide down
                }

            }


        }
        else
        {
            sliding = false;
        }

NOW WHEN WE CALL THE CHARACTER CONTROLLER MOVE:

// IF SLIDING, MOVE THE PLAYER ALONG SLIDE DIRECTION



            if (!haveGottenVelY) // if this is the first frame we are sliding
            {
                slideVel = playerCC.velocity.y; // get our vertical velocity
                slideVel = Mathf.Abs(slideVel); 
                haveGottenVelY = true; // tell computer to no longer get our vertical velocity so we can lerp it
            }


            slideVel = Mathf.Lerp(slideVel, 180, slideAcceleration); // accelerate the velocity of the slide from our previous Yveolcity to max fall speed which is around 180 (I reccomend slide acceleration of .005)
            slideDirection = -slideTransform.up * slideVel; // set slide vector


            // APPLY MOVEMENT 

            playerCC.Move(slideDirection * Time.deltaTime);

Lastly, make sure to include “Sl” in the tag of any game object you wish to allow sliding on. (Some objects like movable cubes you probably wouldn’t need to slide down).

Having trouble altering the new starter assets ThirdPersonController to slide down slopes. The m_moveDir.x I am having trouble as I am swapping it for _input.move.x, but the player automatically moves along that axis, so it is calling it to move somehow in my code or I’m putting that code in the wrong place. It would be nice to see a full script as to how this is all put together instead of snippets and saying it goes above calling controller.Move etc, as there is a call for Move(); in the update and also in the Move function itself controller.Move, so does the code go above and below inside the Move() function or in the actual update method, or should I remove Move() to a fixedUpdate for physics performance? The old Motor script had that ability so why take it out? I have tried half a dozen solutions and YT videos to no avail. It seems my Mathematics and Physics for Unity needs an upgrade, being an indie dev time is not on my side. I keep getting my player bobbing up and down above the surface or no reaction to sliding down the hill at all on the terrain.
2 weeks later and still can not get it to work. I will come back to this in another couple of weeks as my head is on fire. Thank you for everyone’s help so far!

If you are using the default CharacterController and don’t want it to jitter isGrounded, this solution slides the CharacterController down a slope while keeping it continuously grounded.

You can add a collisionflags check to prevent jumping when you are sliding.

    public float slideSpeed = 2f;

    private void OnControllerColliderHit(ControllerColliderHit hit)
    {
        // Check if we are sliding.
        var hitNormal = hit.normal;
        var angle = Vector3.Angle(Vector3.up, hitNormal);
        var isSliding = (angle > hit.controller.slopeLimit && angle <= 90f);
        if (isSliding)
        {
            // Slide along the slopes surface. This ensures the character stays grounded.
            var slopeRotation = Quaternion.FromToRotation(Vector3.up, hitNormal);
            var slopeVelocity = slopeRotation * new Vector3(hitNormal.x, 0f, hitNormal.z) * slideSpeed;
            //yourScript.velocity += slopeVelocity;

            // Prevent jumping if we are sliding, but not if we are walking against a slide.
            var collisionFlags = hit.controller.collisionFlags;
            if (!(collisionFlags.HasFlag(CollisionFlags.Below) && collisionFlags.HasFlag(CollisionFlags.Sides)))
            {
                //yourScript.PreventJump();
            }
        }
    }