Making rigidbody always perpendicular to the ground and running on loopings like sonic ?

Hello, i’m currently making a sonic fan game, i’ve just made the base at the moment
i use a capsule collider with a rigidbody with locked X and Z rotation at the moment

and i’d like to allow sonic to always be perpendicular to the ground and allow him to run on loopings like in the original games if he has enough speed

i know that i would probably have to use raycast but i don’t know a lot about it, could you help me on the way i should use to allow this ?

my current character code is this one, it currently detects the floor with a tiny trigger placed under his main collider

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestCubeScript : MonoBehaviour
{
    public bool Dead;
    Vector3 forward;
    AudioClip Jump;
    public FloorDetection floorcollider;
    Vector3 right;
    public float forcejump;
    public Animation anims;
    public string idle;
    public string walk;
    bool normalstate;
    public GameObject Ball;
    public SkinnedMeshRenderer SonicRenderer;
    public Rigidbody rb;
    AudioClip GetRing;
 
    public float speed;
    float currentspeed;
    public bool isRunning;
    public float maxspeed;
    public RingsCounterScript ringcount;
    public float timejump = 1.5f;
    public string run;
    bool isJumping;
    public bool canmove;
    float derapage = 0f;
    Camera maincam;
   public float moveSpeed= 6; // move speed
float turnSpeed = 90; // turning speed (degrees/second)
float lerpSpeed = 10; // smoothing speed
float gravity = 70; // gravity acceleration
bool isGrounded;
float deltaGround= 0.2f; // character is grounded up to this distance
float jumpSpeed = 10; // vertical jump initial speed
float jumpRange = 10; // range to detect target wall
    CapsuleCollider col;
private Vector3 surfaceNormal; // current surface normal
private Vector3 myNormal; // character normal
private float distGround; // distance from character position to ground
private bool jumping = false; // flag "I'm jumping to wall"
    private float vertSpeed = 0; // vertical jump current speed
    // Use this for initialization
    public void SetNormalState()
    {
      
        normalstate = true;
        canmove = true;
    }
    void Start()
    {
        col = GetComponent<CapsuleCollider>();
        myNormal = transform.up; // normal starts as character up direction
        rb.freezeRotation = true; // disable physics rotation
                                      
        distGround = col.bounds.extents.y - col.center.y;  // distance from transform.position to ground
        Ball.SetActive(false);
      GetRing =  (AudioClip)Resources.Load("Sound Effects/Sonic Ring Sound");

        Jump = (AudioClip)Resources.Load("Sound Effects/Jump");
        currentspeed = speed;
        anims["JumpEnd"].speed = 4;
        anims[run].speed = 4;
        anims.Play(idle);
        Cursor.lockState = CursorLockMode.Locked;
        maincam = Camera.main;
        SetNormalState();
    }
    private void Update()
    {
      
      
            if (Input.GetButton("Submit") && !isJumping)
        {
            isJumping = true;
                StartCoroutine(jump(timejump));
                AudioSource aS = gameObject.GetComponent<AudioSource>();
                if (aS == null)
                {
                    aS = gameObject.AddComponent<AudioSource>();
                }
                AudioClip leclip = Jump;
                aS.clip = Jump;
                aS.Play();
            }
        if (floorcollider.floor)
        {

            isJumping = false;

        }
if (isJumping)
        {

            if (!anims.IsPlaying("BallLoop"))
            {
                normalstate = false;
                anims.CrossFade("BallLoop", 0.2f);
           
                SonicRenderer.enabled = false;
                Ball.SetActive(true);
            }
        }
        else
        {
            if (!normalstate)
            {
                canmove = false;
                anims.CrossFade("JumpEnd", 0.2f);
              
            }
            SonicRenderer.enabled = true;
            Ball.SetActive(false);

            isJumping = false;
        }

    }
    // Update is called once per frame
    void FixedUpdate()
    {
        rb.AddForce(0,-gravity * rb.mass,0 );
        forward = maincam.transform.TransformDirection(Vector3.forward);
        forward.y = 0;
        forward = forward.normalized;
        right = new Vector3(forward.z, 0, -forward.x);
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float mag = Mathf.Clamp01(new Vector2(h, v).magnitude);
        if (mag > 0.1)
        {
derapage = mag;
            if (currentspeed < maxspeed && mag == 1)
            {
                currentspeed += 0.1f;
                anims[run].speed += 0.02f;
            }
        }
        else
        {
            derapage -= 0.03f;
            if (currentspeed > speed)
            {
                currentspeed = speed;
            }
            if (anims[run].speed > 1)
            {
anims[run].speed = 1;
            }
           
        }
        Vector3 moveDirection = Vector3.zero;
        if (mag != 0)
        {
            moveDirection = (h * right + v * forward);
        }
        else
        {
            moveDirection = transform.forward;
        }
        // moveDirection *= speed;
        anims[walk].speed = derapage * 8;
        if (derapage > 0.1 && derapage < 1 && !anims.IsPlaying(walk) && !isJumping && normalstate)
        {
            anims.CrossFade(walk, 0.2f);
        }
        else if (derapage == 1 && !anims.IsPlaying(run) && !isJumping && normalstate)
        {
            anims.CrossFade(run, 0.2f);
            isRunning = true;
        }
        else if (derapage < 0.1 && !anims.IsPlaying(idle) && !isJumping && normalstate)
        {
            anims.CrossFade(idle, 0.2f);
        }
        if (derapage > 0 && canmove)
        {
         
            if (mag != 0)
            {
                transform.rotation = Quaternion.LookRotation(moveDirection);
            }
            if (derapage > 0.1 )
            {
                rb.position += transform.forward * currentspeed * derapage * Time.deltaTime;


            }
       






        }
       
    
      
    }
 
    public void AddRing()
    {
        ringcount.ringcount += 1;
        AudioSource aS = gameObject.GetComponent<AudioSource>();
        if (aS == null)
            {
             aS = gameObject.AddComponent<AudioSource>();
        }
        AudioClip leclip = GetRing;
        aS.PlayOneShot(leclip, 1f);
    }
    IEnumerator jump(float time)
    {

        StartCoroutine(JumpAnims());

        while (Input.GetButton("Submit") && time > 0)
        {
        
            time -= Time.deltaTime;
            rb.AddForce(new Vector3(0, forcejump, 0), ForceMode.Impulse);
            yield return 0;
        }



    }
    IEnumerator JumpAnims()
    {
       
            anims.CrossFade("JumpStart00", 0.2f);
       
      
        yield return 0;
      
       
      


    }
}

i still have a lot to optimize but before i’d like to fix my problem of floor detection with raycast to make loopings and things like this