As I had some problems with this, and came up with a solution to it, I wanted to share it with you guys.
What I wanted:
The character should be able to walk up slopes to a set degree.
The character should slide down slopes that are too steep.
The character should not be able to “stick” to slopes that are too steep while keeping the movement buttons pressed.
The character should not be able to jump up slopes that are too steep.
The solution:
The character movement is handled by setting the velocity directly.
desiredVelocity.Set(desiredVelocity.x, rigidbody.velocity.y, desiredVelocity.z);
The 2nd parameter of the velocity set is used to preserve any upwards or downwards velocity while moving. However this is the part that messed up the slopes. When you jumped onto a slope, the moment you hit the slope the downwards velocity would become 0, keeping the movements buttons pressed, would preserve this y-velocity of 0, making the character stick to the slope.
I first tried finding out if we are currently standing on a slope and make that prevent all movement. However that did not work out very well, the character would often sit next to a slope, unable to move at all.
The thing that worked:
Find out if there is a slope in the direction we are heading. If yes, prevent movement in that direction.
bool checkMoveableTerrain(Vector3 position, Vector3 desiredDirection, float distance)
{
Ray myRay = new Ray(position, desiredDirection); // cast a Ray from the position of our gameObject into our desired direction. Add the slopeRayHeight to the Y parameter.
RaycastHit hit;
if (Physics.Raycast(myRay, out hit, distance))
{
if (hit.collider.gameObject.tag == "Ground") // Our Ray has hit the ground
{
float slopeAngle = Mathf.Deg2Rad * Vector3.Angle(Vector3.up, hit.normal); // Here we get the angle between the Up Vector and the normal of the wall we are checking against: 90 for straight up walls, 0 for flat ground.
float radius = Mathf.Abs(slopeRayHeight / Mathf.Sin(slopeAngle)); // slopeRayHeight is the Y offset from the ground you wish to cast your ray from.
if (slopeAngle >= steepSlopeAngle * Mathf.Deg2Rad) //You can set "steepSlopeAngle" to any angle you wish.
{
if (hit.distance - collider.radius > Mathf.Abs(Mathf.Cos(slopeAngle) * radius) + slopeThreshold) // Magical Cosine. This is how we find out how near we are to the slope / if we are standing on the slope. as we are casting from the center of the collider we have to remove the collider radius.
// The slopeThreshold helps kills some bugs. ( e.g. cosine being 0 at 90° walls) 0.01 was a good number for me here
{
return true; // return true if we are still far away from the slope
}
return false; // return false if we are very near / on the slope && the slope is steep
}
return true; // return true if the slope is not steep
}
}
}
So all we gotta do in the end is
if (checkMoveableTerrain(player.position, new Vector3(desiredVelocity.x, 0, desiredVelocity.z), 10f)) // filter the y out, so it only checks forward... could get messy with the cosine otherwise.
{
rigidbody.velocity = desiredVelocity;
}
So I hoped that helped anybody who is in the same position I was yesterday. Keep in mind, this is just code snippets, helping to give a general feel of the solution. You will have to adjust it. I am aware of not all code paths returning a bool value for the function, it’s because I split that code into 2 functions, just trying to make it more readable here.
Skusku