I’m trying to have my character slide down slopes that are above the slope limit so he can’t get stuck on the side out mountains, this is what I have right now;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[RequireComponent(typeof(CharacterController))]
//[RequireComponent(typeof(Animator))]
public class MovementController : MonoBehaviour
{
private float InputX, InputZ, Speed;
private Camera cam;
private CharacterController characterController;
private Animator characterAnimator;
private Vector3 desiredMoveDirection;
[SerializeField] float rotationSpeed = 0.3f;
[SerializeField] float allowRotation = 0.1f;
[SerializeField] float movementSpeed = 3f;
[SerializeField] float gravity;
[SerializeField] float gravityMultiplier;
[SerializeField] float slopeLimit = 45f;
[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);
// Start is called before the first frame update
void Awake()
{
characterController = GetComponent<CharacterController>();
cam = Camera.main;
characterAnimator = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
InputX = Input.GetAxis("Horizontal");
InputZ = Input.GetAxis("Vertical");
InputDecider();
MovementManager();
}
void FixedUpdate()
{
// Check ground, with an origin point defaulting to the bottom middle
// of the char controller's collider. Plus a little higher
if (characterController && characterController.isGrounded)
{
CheckGround(new Vector3(transform.position.x, transform.position.y - (characterController.height / 2) + startDistanceFromBottom, transform.position.z));
}
}
void InputDecider()
{
Speed = new Vector2(InputX, InputZ).sqrMagnitude;
if (Speed > allowRotation)
{
RotationManager();
} else
{
desiredMoveDirection = Vector3.zero;
}
}
void RotationManager()
{
var camforward = cam.transform.forward;
var camright = cam.transform.right;
camforward.y = 0;
camright.y = 0;
camforward.Normalize();
camright.Normalize();
desiredMoveDirection = camforward * InputZ + camright * InputX;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(desiredMoveDirection), rotationSpeed);
}
void MovementManager()
{
gravity -= 9.8f * Time.deltaTime;
gravity = gravity * gravityMultiplier;
Vector3 moveDirection = desiredMoveDirection * (movementSpeed * Time.deltaTime);
moveDirection = new Vector3 (moveDirection.x, gravity, moveDirection.z);
characterController.Move(moveDirection);
characterAnimator.SetFloat("Speed", (Mathf.Abs(Input.GetAxis("Horizontal")) + Mathf.Abs(Input.GetAxis("Vertical"))));
if (characterController.isGrounded)
{ gravity = 0; }
if (groundSlopeAngle > slopeLimit)
{
characterController.Move(groundSlopeDir + Vector3.down * gravity * Time.deltaTime);
}
}
/// Checks for ground underneath, to determine some info about it, including the slope angle.
public void CheckGround(Vector3 origin)
{
// Out hit point from our cast(s)
RaycastHit hit;
{
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 - (characterController.height / 2) + startDistanceFromBottom, transform.position.z);
Vector3 endPoint = new Vector3(transform.position.x, transform.position.y - (characterController.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);
}
}
}
There’s a couple issues, namely if I hit a slope that’s steeper than 45 degree angle, my slope limit, I move at that direction until I hit the ground so if I hit a 46 degree mountain side I move at that 46 degree angle through the air until the ground not going down the actual slope if that makes sense
It’s also pretty jumpy if I’m running into a slope on the ground at that angle, it just instantly kicks you back a couple of feet…
Thoughts? Help?