Slopes angles Raycast cast

using System.Collections.Generic;

using UnityEngine;

using VContainer;




public class PlayerDetection : MonoBehaviour

{   

    [Header("Links")]

    private CapsuleCollider controller;




    [Header("Settings, Raycast")]

    [SerializeField] LayerMask groundMask;

    [SerializeField] float skinWidth = 0.2f;

    [SerializeField] float roofDistance = 1f;

    [SerializeField] float minAirTime = 0.1f;

    [SerializeField] float minLandingVelocity = 2;




    [Header("Settings, Slope")]

    [SerializeField] LayerMask stairs;

    [SerializeField] float slopeAngle = 45;

    [SerializeField] float maxSlopeAngle = 89;




    public RaycastHit GroundHit {get; private set;}

    private float airTime = 0;

    private float peakFallVelocity = 0;




    public bool JustLanded {get; private set;}

    public bool IsGrounded {get; private set;}

    public bool IsSurfing {get; private set;}




    public bool TochedRoofInFrame {get; private set;}

    

    private Vector3 wallHit;




    private List<Vector3> HitNormalsInFrame = new List<Vector3>();




    [Inject]

    public void Construct(PlayerView view)

    {

        controller = view.Capsule;

    }




    public void PreTick(PlayerContext ctx)

    {

        GroundCheck(ctx);

    }

    public void PostTick(PlayerContext ctx)

    {

        foreach (var v in HitNormalsInFrame)

        {

            if (ctx.IsSurfing && Vector3.Dot(v, ctx.SlideNormal) > 0.8f) continue;




            if(Vector3.Dot(ctx.velocity, v) < 0)

            {

                ctx.velocity = QuakeMathf.ClipVelocity(ctx.velocity, v);

            }

        }

        if(TochedRoofInFrame&& ctx.velocity.y > 0)

        {

            ctx.velocity.y = 0; 

        }

        TochedRoofInFrame = false;

        HitNormalsInFrame.Clear();

    }

    public bool RoofDetection()

    {

        float capsuleBottom = controller.center.y - (controller.height*0.5f) + controller.radius;

        float capsuleTop = controller.center.y + (controller.height*0.5f) + controller.radius;




        Vector3 bottom = transform.position + new Vector3(controller.center.x,capsuleBottom, controller.center.z);

        Vector3 top = transform.position + new Vector3(controller.center.x, capsuleTop, controller.center.z);




        return Physics.CheckCapsule(bottom,top, controller.radius*0.9f, groundMask);

    }

    private void GroundCheck(PlayerContext ctx)

    {

        float bottomOffset = controller.center.y - (controller.height * 0.5f) + controller.radius;

        Vector3 origin = transform.position + new Vector3(controller.center.x, bottomOffset, controller.center.z);

        float radius = controller.radius*0.9f;




        float currentDist = IsGrounded&&!ctx.JumpRequest? skinWidth + 0.2f : skinWidth;     

        RaycastHit hit;

        bool grounded = Physics.SphereCast(

            origin,

            radius,

            Vector3.down,

            out hit,

            currentDist,

            groundMask

        );

        GroundHit = hit;




        bool wasIsGrounded = IsGrounded;

        if (!grounded)

        {

            airTime += Time.deltaTime;

            if (ctx.velocity.y < 0)

            peakFallVelocity = Mathf.Min(peakFallVelocity, ctx.velocity.y);

        }





        if (grounded)

        {

            float angle = Vector3.Angle(hit.normal, Vector3.up);

            IsSurfing = angle > slopeAngle && angle <= maxSlopeAngle;

            IsGrounded = !IsSurfing;

        }

        else

        {

            IsSurfing = false;

            IsGrounded = false;

        }

        

        JustLanded = !wasIsGrounded && IsGrounded

        && airTime >= minAirTime

        && Mathf.Abs(peakFallVelocity) >= minLandingVelocity;




        if (IsGrounded || IsSurfing)

        {

            airTime = 0f;

            peakFallVelocity = 0f;

        }

        ctx.IsGrounded = IsGrounded;

        ctx.IsSurfing = IsSurfing;

        ctx.WallNormal = wallHit;

        ctx.SlideNormal = grounded ? hit.normal : Vector3.up;

    }

    void OnCollisionStay(Collision collision)

    {

        for (int i = 0; i < collision.contactCount; i++)

        {

            var c = collision.GetContact(i);

            if(c.normal.y < -0.3f)

            {

                TochedRoofInFrame = true;

                continue;

            }

            if (Mathf.Abs(c.normal.y) > 0.05f) continue;

            

            bool exists = false;

            for (int j = 0; j < HitNormalsInFrame.Count; j++)

            {

                if (Vector3.Dot(HitNormalsInFrame[j], c.normal) > 0.85f)

                {  

                    exists = true;

                    break;

                }

            }

            if (!exists)

                HitNormalsInFrame.Add(c.normal);




           

        }

        if (collision.contactCount > 0)

            wallHit = collision.GetContact(0).normal;

    }

}


Hello everyone
I trying make best character controller MYSELF NOT USE A CONTROLLER FROM STORE

So well. I have a issue with slopes angle because i use simple sphere cast bottom my character
This raycast checks all edges as surf surfaces, for example a stairs just a simple probuilder stairs have a problems with them because their edges angles like 80 and surfing turn on

… then the second best alternatives are:

a) Find one on GitHub. There’s even Quake character controllers you can get.
b) Task an AI agent to do it for you! :winking_face_with_tongue:

The problem with your approach is that you got stuck on something that ought to still be rather straightforward and you fail to explain the failing behaviour. “Have problems” is not a problem description. If you want to DIY a character controller you ought to have the gizmos drawing all your raycasts, and if there is an issue you can post the screenshots along with respective values for analysis.

Right now you’re getting stuck on basic trigonometry and the choice of the wrong kind of cast to determine ground slope - you can only do so with a raycast, not a spherecast, because a sphere will also report hits at its sides. Most likely the cause is that your spherecast reports the vertical of the stair, not the ground, and it treats it as a “slope”. You will get the same problem when approaching a wall, which is nothing but a super-sized stair step.

Feel free to continue and learn but expect more of and far harder to solve problems. As long as you still enjoy it, it’s fine. :wink: