Character movement and slopes.

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.



^^ you just made me save 1 whole day!! hurray!

Thanks, this helped me too.

Just want to say thanks, this helped me a lot.

just a little suggestion, you can calculate slope angle by Vector3.Cross / SignedAngle instead of those tan,sin,cos

// Getting the forward(pitch angle), based on current ground normal(raycastHit result).
SlopeForward = Vector3.Cross(transform.right, GroundNormal);

// therefore
SlopeAngle = Vector3.SignedAngle
(transform.forward, SlopeForward,Vector3.up);

I get why you had to script something for those goals, but what I don't get is why there was a problem with char. walking up a slope set at a current degree, that's included with unity out of the box,,a variable, vector..

I'm having trouble though, so maybe that is what prompted this ? lol

I know the angle my char. can not walk up, is only about 20 degrees,,just can't, YET he is able to walk up a 45 degree slope, WHICH yes is set in slope variable .

Why is char having trouble with a 20 degree slope which is a lot smaller than 45 ?


1 Like


Check this first... is fairly complex, because it uses heightmap data

Further problems arose for me because I center my terrain, and normalized only works for positive values (eg, only in the upper right quadrant of the terrain in my case).

However, I may end up using your solution, since I need to know whether my unit is traveling uphill, or traveling downhill. (which i just figured out it would be better to record and compare Y values over time)

I’ve got a simple one, what exactly does SlopeRayHeight do?

Hey all, figured I'd add to this. I've solved the walking up and down slope issue by setting the local forward direction to be parallel the slope I'm walking on. I also set the gravity force direction to be perpendicular to the slope to avoid any potential slowdown issues. But I've stumbled upon another problem, walking up and down the slope is fine, but when trying to walk across/along the slope (in real life one foot would be lower than the other) the character slows down a lot...and I can't figure out why this is happening. If anyone has any tips I'd love to hear them, also if people here as still stuck on walking up and down slopes smoothly and would like to see the code I've got feel free to ask.

Yes would like to see that please.I want my character to ultimately switch to climb animation when the slope excceds a certain point.