Search Unity

Character movement and slopes.

Discussion in 'Physics' started by skusku, Jan 11, 2015.

  1. skusku

    skusku

    Joined:
    Jan 4, 2015
    Posts:
    17
    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.

    Code (CSharp):
    1.  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.

    Code (CSharp):
    1.  
    2.  
    3.         bool checkMoveableTerrain(Vector3 position, Vector3 desiredDirection, float distance)
    4.         {
    5.                 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.
    6.    
    7.                 RaycastHit hit;
    8.    
    9.                 if (Physics.Raycast(myRay, out hit, distance))
    10.                 {
    11.                     if (hit.collider.gameObject.tag == "Ground") // Our Ray has hit the ground
    12.                     {
    13.                       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.
    14.  
    15.                       float radius = Mathf.Abs(slopeRayHeight / Mathf.Sin(slopeAngle)); // slopeRayHeight is the Y offset from the ground you wish to cast your ray from.
    16.  
    17.                        if (slopeAngle >= steepSlopeAngle * Mathf.Deg2Rad) //You can set "steepSlopeAngle" to any angle you wish.
    18.                       {
    19.                          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.
    20. // The slopeThreshold helps kills some bugs. ( e.g. cosine being 0 at 90° walls) 0.01 was a good number for me here
    21.                          {
    22.                           return true; // return true if we are still far away from the slope
    23.                          }
    24.  
    25.                         return false; // return false if we are very near / on the slope && the slope is steep
    26.                       }
    27.  
    28.                      return true; // return true if the slope is not steep
    29.    
    30.                    }
    31.    
    32.                 }
    33.         }
    34.  
    35.  

    So all we gotta do in the end is

    Code (CSharp):
    1.                 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.
    2.                 {
    3.                     rigidbody.velocity = desiredVelocity;
    4.                 }

    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
     
    Last edited: Jan 11, 2015
  2. ulysses24769

    ulysses24769

    Joined:
    Aug 3, 2016
    Posts:
    2
    ^^ you just made me save 1 whole day!! hurray!
     
  3. yotingo

    yotingo

    Joined:
    Jul 10, 2013
    Posts:
    44
    Thanks, this helped me too.
     
  4. XerxesBreak

    XerxesBreak

    Joined:
    Jun 14, 2015
    Posts:
    1
    Just want to say thanks, this helped me a lot.
     
  5. canis

    canis

    Joined:
    Oct 25, 2013
    Posts:
    79
    just a little suggestion, you can calculate slope angle by Vector3.Cross / SignedAngle instead of those tan,sin,cos

    Code (CSharp):
    1. // Getting the forward(pitch angle), based on current ground normal(raycastHit result).
    2. SlopeForward = Vector3.Cross(transform.right, GroundNormal);
    3.  
    4. // therefore
    5. SlopeAngle = Vector3.SignedAngle
    6. (transform.forward, SlopeForward,Vector3.up);  
     
  6. neighborlee

    neighborlee

    Joined:
    Jan 26, 2016
    Posts:
    50
    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 ?

    ty
     
    Last edited: Aug 29, 2018
    Pastor117 likes this.
  7. Ne0mega

    Ne0mega

    Joined:
    Feb 18, 2018
    Posts:
    755
    terrainData.GetSteepness()

    https://docs.unity3d.com/ScriptReference/TerrainData.GetSteepness.html


    Check this first... ..it is fairly complex, because it uses heightmap data
    https://answers.unity.com/questions/452204/terraindatagetsteepness-usage.html

    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)
     
    Last edited: Dec 18, 2018
  8. gregpr07

    gregpr07

    Joined:
    Dec 16, 2018
    Posts:
    1
    I’ve got a simple one, what exactly does SlopeRayHeight do?
     
  9. Relativ99

    Relativ99

    Joined:
    Apr 26, 2019
    Posts:
    1
    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.
     
  10. mashley53

    mashley53

    Joined:
    Sep 22, 2020
    Posts:
    1
    Yes would like to see that please.I want my character to ultimately switch to climb animation when the slope excceds a certain point.