Last active
July 15, 2023 03:24
-
-
Save jawinn/f466b237c0cdc5f92d96 to your computer and use it in GitHub Desktop.
Unity - Ground Checker: Get angle of sloped ground underneath the player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
// Finds the slope/grade/incline angle of ground underneath a CharacterController | |
public class GroundChecker : MonoBehaviour { | |
[Header("Results")] | |
public float groundSlopeAngle = 0f; // Angle of the slope in degrees | |
public Vector3 groundSlopeDir = Vector3.zero; // The calculated slope as a vector | |
[Header("Settings")] | |
public bool showDebug = false; // Show debug gizmos and lines | |
public LayerMask castingMask; // Layer mask for casts. You'll want to ignore the player. | |
public float startDistanceFromBottom = 0.2f; // Should probably be higher than skin width | |
public float sphereCastRadius = 0.25f; | |
public float sphereCastDistance = 0.75f; // How far spherecast moves down from origin point | |
public float raycastLength = 0.75f; | |
public Vector3 rayOriginOffset1 = new Vector3(-0.2f, 0f, 0.16f); | |
public Vector3 rayOriginOffset2 = new Vector3(0.2f, 0f, -0.16f); | |
// Component reference | |
private CharacterController controller; | |
void Awake() | |
{ | |
// Get component on the same GameObject | |
controller = GetComponent<CharacterController>(); | |
if (controller == null) { Debug.LogError("GroundChecker did not find a CharacterController component."); } | |
} | |
void FixedUpdate() | |
{ | |
// Check ground, with an origin point defaulting to the bottom middle | |
// of the char controller's collider. Plus a little higher | |
if (controller && controller.isGrounded) { | |
CheckGround(new Vector3(transform.position.x, transform.position.y - (controller.height / 2) + startDistanceFromBottom, transform.position.z)); | |
} | |
} | |
/// <summary> | |
/// Checks for ground underneath, to determine some info about it, including the slope angle. | |
/// </summary> | |
/// <param name="origin">Point to start checking downwards from</param> | |
public void CheckGround(Vector3 origin) | |
{ | |
// Out hit point from our cast(s) | |
RaycastHit hit; | |
// SPHERECAST | |
// "Casts a sphere along a ray and returns detailed information on what was hit." | |
if (Physics.SphereCast(origin, sphereCastRadius, Vector3.down, out hit, sphereCastDistance, castingMask)) | |
{ | |
// Angle of our slope (between these two vectors). | |
// A hit normal is at a 90 degree angle from the surface that is collided with (at the point of collision). | |
// e.g. On a flat surface, both vectors are facing straight up, so the angle is 0. | |
groundSlopeAngle = Vector3.Angle(hit.normal, Vector3.up); | |
// Find the vector that represents our slope as well. | |
// temp: basically, finds vector moving across hit surface | |
Vector3 temp = Vector3.Cross(hit.normal, Vector3.down); | |
// Now use this vector and the hit normal, to find the other vector moving up and down the hit surface | |
groundSlopeDir = Vector3.Cross(temp, hit.normal); | |
} | |
// Now that's all fine and dandy, but on edges, corners, etc, we get angle values that we don't want. | |
// To correct for this, let's do some raycasts. You could do more raycasts, and check for more | |
// edge cases here. There are lots of situations that could pop up, so test and see what gives you trouble. | |
RaycastHit slopeHit1; | |
RaycastHit slopeHit2; | |
// FIRST RAYCAST | |
if (Physics.Raycast(origin + rayOriginOffset1, Vector3.down, out slopeHit1, raycastLength)) | |
{ | |
// Debug line to first hit point | |
if (showDebug) { Debug.DrawLine(origin + rayOriginOffset1, slopeHit1.point, Color.red); } | |
// Get angle of slope on hit normal | |
float angleOne = Vector3.Angle(slopeHit1.normal, Vector3.up); | |
// 2ND RAYCAST | |
if (Physics.Raycast(origin + rayOriginOffset2, Vector3.down, out slopeHit2, raycastLength)) | |
{ | |
// Debug line to second hit point | |
if (showDebug) { Debug.DrawLine(origin + rayOriginOffset2, slopeHit2.point, Color.red); } | |
// Get angle of slope of these two hit points. | |
float angleTwo = Vector3.Angle(slopeHit2.normal, Vector3.up); | |
// 3 collision points: Take the MEDIAN by sorting array and grabbing middle. | |
float[] tempArray = new float[] { groundSlopeAngle, angleOne, angleTwo }; | |
Array.Sort(tempArray); | |
groundSlopeAngle = tempArray[1]; | |
} | |
else | |
{ | |
// 2 collision points (sphere and first raycast): AVERAGE the two | |
float average = (groundSlopeAngle + angleOne) / 2; | |
groundSlopeAngle = average; | |
} | |
} | |
} | |
void OnDrawGizmosSelected() | |
{ | |
if (showDebug) | |
{ | |
// Visualize SphereCast with two spheres and a line | |
Vector3 startPoint = new Vector3(transform.position.x, transform.position.y - (controller.height / 2) + startDistanceFromBottom, transform.position.z); | |
Vector3 endPoint = new Vector3(transform.position.x, transform.position.y - (controller.height / 2) + startDistanceFromBottom - sphereCastDistance, transform.position.z); | |
Gizmos.color = Color.white; | |
Gizmos.DrawWireSphere(startPoint, sphereCastRadius); | |
Gizmos.color = Color.gray; | |
Gizmos.DrawWireSphere(endPoint, sphereCastRadius); | |
Gizmos.DrawLine(startPoint, endPoint); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment