Getting the Angle of Sloped Ground Beneath a Player in Unity
With a player character who’s traversing up and down hills, knowing how steep the incline is that they’re walking on can be useful. Perhaps the character can slide or perform some other action, only when the angle is above a certain number of degrees. Or there could be a vehicle involved, that needs adjustments made to it.
I first accomplished this in a complex way using two downward raycasts, Atan2, etc. But while digging through the forums, I came across a simpler way to calculate the vectors using the hit normal. Then there was the issue of accuracy on edges and other anomalies. In the end, I wrote a more comprehensive class that uses a spherecast and two additional raycasts below the player. It calculates the angle using some of the vectors related to those tests.
The code in this post is using a CharacterController. It’s tied to the function OnControllerColliderHit:
OnControllerColliderHit is called when the controller hits a collider while performing a Move.
This helps us simply by only calling the ground testing code only when we really need it. The CharacterController is also used for the positioning of the raycast and spherecast later on. If you’re using a rigidbody-based controller or something else, then you’ll have to modify when the code is called and the positioning to suit your needs.
The (Too) Simple Way
This is some helpful code that I found on the Unity forums. At first, it seemed to work:
But there is a problem. This function is called when hitting any sort of collider. That could be the wall to the sides, or an unfriendly monster with no regard for personal space. In testing, this seemed to usually be the ground (I’m guessing it was the last time this is called), so it’s deceptive. If you’re colliding with other objects, it’s not guaranteed to be what’s underneath you.
The other problem is that you’ll get really wrong angles when standing over small holes/gaps or edges.
Revised Method: A SphereCast and Some RayCasts
In the above screenshot, the dark grey sphere is a gizmo visualization of the spherecast as it moves downwards. The red lines are the raycasts. It’s not obvious in the image, but the raycasts are also spread out a little in the Z axis.
The following GroundChecker class is heavily commented and should explain everything. Basically what’s happening is:
- In FixedUpdate, call the GroundCheck function with an origin point near the bottom of the player. In my case, I’m doing Move() in FixedUpdate(). If you’re moving your controller in Update(), then you’ll likely want to put this code there.
- The GroundCheck function:
- Does a SphereCast downwards and sees if it hits anything. If it does, find slope angle based on the hit normal, where this collision occurs
- For better accuracy, does two raycasts downwards. If only one hits, average that slope angle with the slope angle from the SphereCast.If both raycasts hit something, then choose the median value between the three. This is to throw out rogue outliers, when one value is really wrong—for example, if two of the casts are returning the more correct angle 11.5 degrees, and the third is returning 70 degrees.
- Stores the resulting slope angle in the member variable groundSlopeAngle (and the slope vector as groundSlopeDir)
- If the showDebug option is enabled in the component, the gizmos are drawn to visualize the casts, when the object is selected.
I hope this can be of use for others that are trying to test the ground under the player. Using similar techniques, one could check for other information relevant to gameplay, such as the type of ground.