Not work sliding

Hello. I have a question. I’m using AI to write code (I know it causes errors, but I don’t know where else to find what I need). The problem is that when a character is on a slope, regardless of the angle set in maxWalkableSlope and startSlideAngle, he slides only at 40 degrees. And when he tries to climb a slope when that angle is 40 degrees or higher, he bounces and shoots himself backward.



using UnityEngine;
using UnityEngine.InputSystem;

public enum SlopeDirection
{
    None,
    DownForward,
    DownBackward,
    DownLeft,
    DownRight
}

public enum MovementState
{
    Idle,       // Postać stoi w miejscu, brak ruchu
    Walking,    // Postać porusza się powoli, np. trzymając Ctrl
    Running,    // Postać porusza się normalnie, bez dodatkowych klawiszy
    Sprinting,  // Postać porusza się szybko, np. trzymając Shift
    Jumping     // Postać jest w powietrzu po wykonaniu skoku
}
public enum SlopeState
{
    Flat,           // 0-10°  - płasko
    SlightDown,     // 10-25° - lekki spadek
    SteepDown,      // 25-40° - stromy spadek
    SlideDown,      // 40-55° - ślizg w dół
    VerySteep,      // 55-65° - bardzo stroma ściana
    NearVertical,   // 65-80° - prawie pionowo
    VerticalWall    // 80-90° - ściana
}

public enum Condition
{
    Standing,   // Postać stoi na ziemi, gotowa do ruchu
    Airborne,   // Postać jest w powietrzu, np. po skoku lub podczas spadania
    Climbing    // (opcjonalnie) Postać wspina się po ścianie lub drabinie
}

public enum GroundedState
{
    Grounded,   // Postać jest na ziemi, bezpieczna
    SafeFall,   // Postać spada z niewielkiej wysokości, bez ryzyka obrażeń
    SoftFall,   // Postać spada z umiarkowanej wysokości, może odczuć lekki dyskomfort
    NormalFall, // Postać spada z dużej wysokości, ryzyko obrażeń
    HardFall    // Postać spada z bardzo dużej wysokości, ryzyko poważnych obrażeń lub śmierci
}

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(Gravity))]
public class CharacterMovement : MonoBehaviour
{
    // ------------------------
    // Komponenty
    // ------------------------
    private PlayerInput _playerInput;
    private CharacterController _characterController;
    private Animator _animator;
    private Gravity _gravity;

    // ------------------------
    // Input
    // ------------------------
    private Vector2 _moveInput; // aktualny wektor wejścia z klawiatury (X = strafe, Y = forward)
    private bool _sprintInput;
    private bool _walkInput;

    // ------------------------
    // Movement Settings
    // ------------------------
    [Header("Movement Settings")]
    [Tooltip("Podstawowa prędkość poruszania się postaci (w metrach na sekundę)")]
    public float baseSpeed = 2f;

    [Tooltip("Mnożnik prędkości podczas biegu, im wyższa wartość, tym szybszy bieg.")]
    public float sprintMultiplier = 1.5f;

    [Tooltip("Mnożnik prędkości podczas chodzenia, im wyższa wartość, tym szybsze chodzenie.")]
    public float walkMultiplier = 0.75f;

    [Tooltip("Mnożnik przyspieszenia, im wyższa wartość, tym szybsze przyspieszenie postaci")]
    public float acceleration = 10f;

    [Tooltip("Mnożnik hamowania, im wyższa wartość, tym szybsze zatrzymanie postaci")]
    public float deceleration = 15f;

    [Tooltip("Czy postać powinna poruszać się z normalizowaną prędkością podczas ruchu diagonalnego. Jeśli jest ustawione na true, prędkość podczas ruchu diagonalnego będzie taka sama jak podczas ruchu w jednym kierunku.")]
    public bool useNormalizedDiagonalMovement = true;

    [Header("Directional Speed Modifiers")]
    [Tooltip("Mnożnik prędkości podczas ruchu w bok (strafe)")]
    [Range(0.1f, 1f)] public float strafeSpeedMultiplier = 0.8f;

    [Tooltip("Mnożnik prędkości podczas cofania się")]
    [Range(0.1f, 1f)] public float backwardSpeedMultiplier = 0.6f;

    // ------------------------
    // Slope Settings
    // ------------------------

    [Header("Slope & Sliding")]
    [Tooltip("Maksymalny kąt, na którym postać jeszcze normalnie chodzi (0-90°)")]
    public float maxWalkableSlope = 40f;  // do SlideDown

    [Tooltip("Kąt, od którego postać zaczyna się ślizgać (0-90°)")]
    public float startSlideAngle = 40f;   // od tego kąta zaczyna się ślizg

    [Tooltip("Prędkość ślizgu na stromych stokach")]
    [Range(3f, 15f)] public float slideSpeed = 8f;

    [Tooltip("Maksymalna prędkość na bardzo stromych powierzchniach")]
    [Range(8f, 25f)] public float maxSlideSpeed = 15f;

    private SlopeState currentSlopeState;
    private bool isSliding;
    private float currentSlopeAngle;
    private Vector3 slideVelocity;

    // ------------------------
    // Jump Settings
    // ------------------------
    [Header("Jump Settings")]
    [Tooltip("Wysokość skoku, im wyższa wartość, tym wyższy skok.(w metrach)")]
    public float jumpHeight = 1.5f;

    [Range(0f, 1f)]
    [Tooltip("Mnożnik kontroli nad postacią podczas lotu, im niższa wartość, tym mniejsza kontrola.")]
    public float airboneControlMultiplier = 0.5f;

    [Tooltip("Czas, w którym gracz może jeszcze skoczyć po zejściu z krawędzi (w sekundach)")]
    public float coyoteTime = 0.2f;

    [Tooltip("Czas, w którym gracz nie może ponownie skoczyć po wykonaniu skoku i opadnięciu na ziemię (w sekundach)")]
    public float jumpCooldown = 0.5f;

    [Header("Fall Thresholds")]
    [Tooltip("Wysokość od jakiej następuje przejście od spadku bezpiecznego do spadku delikatnego (w metrach)")]
    public float softGroundedDistance = 1f;

    [Tooltip("Wysokość od jakiej następuje przejście od spadku delikatnego do spadku normalnego (w metrach)")]
    public float normalGroundedDistance = 3f;

    [Tooltip("Wysokość od jakiej następuje przejście od spadku normalnego do spadku twardego (w metrach)")]
    public float hardGroundedDistance = 6f;

    // ------------------------
    // Runtime State
    // ------------------------
    private MovementState currentMovementState = MovementState.Idle;
    private Condition currentCondition = Condition.Standing;
    private GroundedState currentGroundedState = GroundedState.Grounded;
    private SlopeDirection currentSlopeDirection = SlopeDirection.None;

    private float coyoteTimer;
    private float jumpCooldownTimer;

    private float fallStartHeight;
    private bool wasGrounded;

    private Vector3 velocity; // aktualny wektor ruchu
    private float currentSpeed; // prędkość w płaszczyźnie XZ
    private float fallStateTimer = 0f;  // Timer, który kontroluje jak długo stan spadku jest aktywny po lądowaniu
    [Tooltip("Jak długo stan spadku (HardFall, NormalFall itd.) ma być widoczny w Animatorze")]
    public float fallStateDisplayTime = 0.65f;   // 0.65s = wystarczająco na większość animacji lądowania
    private Vector3 groundNormal = Vector3.up;

    // ------------------------
    // Public Read-Only Access
    // ------------------------
    public MovementState MoveState => currentMovementState;
    public Condition CurrentCondition => currentCondition;
    public GroundedState GroundState => currentGroundedState;
    public SlopeDirection SlopeDirection => currentSlopeDirection;

    public bool IsGrounded => _characterController.isGrounded;
    public bool IsIdle => currentMovementState == MovementState.Idle;
    public bool IsWalking => currentMovementState == MovementState.Walking;
    public bool IsRunning => currentMovementState == MovementState.Running;

    public SlopeState CurrentSlopeState => currentSlopeState;
    public bool IsSliding => isSliding;
    public float SlopeAngle => currentSlopeAngle;

    // ------------------------
    // Unity
    // ------------------------
    private void Awake()
    {
        _playerInput = GetComponent<PlayerInput>();
        _characterController = GetComponent<CharacterController>();
        _gravity = GetComponent<Gravity>();
        // Szukamy Animatora tylko w dziecku "Body", żeby nie mieszać go z innymi Animatorami (np. na broni)
        Transform animatorChild = transform.Find("Body");
        if (animatorChild != null)
            _animator = animatorChild.GetComponent<Animator>();

        if (_animator == null)
            Debug.LogWarning("Animator not found on Body child!", this);
        _characterController.slopeLimit = 89.5f;

        _characterController.skinWidth = 0.1f;   // zwiększ trochę
        _characterController.stepOffset = 0.3f;
    }

    private void Update()
    {
        // 1. Dynamiczne parametry CC
        if (isSliding)
        {
            _characterController.stepOffset = 0f;
            _characterController.slopeLimit = 89f;
            // _characterController.skinWidth = Mathf.Lerp(_characterController.skinWidth, 0.12f, 10f * Time.deltaTime);
        }
        else
        {
            _characterController.stepOffset = 0.3f;
            _characterController.slopeLimit = 89.5f;
            // _characterController.skinWidth = Mathf.Lerp(_characterController.skinWidth, 0.08f, 10f * Time.deltaTime);
        }

        // 2. Input i timery
        ReadInput();
        UpdateTimers();

        // 3. Wykrywanie stoku – musi być przed ruchem
        DetectSlopeAndSliding();

        // 4. Ruch – tu dzieje się magia
        HandleMovement();

        // 5. Skok (po ruchu – bo velocity.y może być nadpisane)
        HandleJump();

        // 6. Śledzenie spadania i lądowania
        UpdateFallTracking();

        // 7. Aktualizacja stanów (Idle/Running/Sprinting/Airborne)
        UpdateStates();

        // 8. Animator na samym końcu – widzi już wszystko
        UpdateAnimator();

        // ────────────────────────────────────────────────
        // Opcjonalne debugowanie – włącz na czas testów
        // ────────────────────────────────────────────────
         if (isSliding)
         {
             Debug.DrawRay(transform.position, groundNormal * 2f, Color.magenta, 0.1f);
             Debug.DrawRay(transform.position, velocity.normalized * 2f, Color.yellow, 0.1f);
         }
    }

    // ------------------------
    // Input
    // ------------------------
    private void ReadInput()
    {
        _moveInput = _playerInput.actions["Move"].ReadValue<Vector2>();
        _sprintInput = _playerInput.actions["Sprint"].IsPressed();
        _walkInput = _playerInput.actions["Walk"].IsPressed();

    }

    private void UpdateTimers()
    {
        // Coyote time – bez zmian
        // Coyote timer odlicza się tylko wtedy, gdy postać jest w powietrzu. Gdy postać jest na ziemi, timer jest resetowany do wartości coyoteTime.
        if (_characterController.isGrounded)
        {
            // Reset coyote timer, gdy postać jest na ziemi
            coyoteTimer = coyoteTime;
        }
        else
        {
            // Odliczanie coyote timer, gdy postać jest w powietrzu
            coyoteTimer -= Time.deltaTime;
        }
        // Zapewnienie, że coyoteTimer nigdy nie spadnie poniżej 0
        coyoteTimer = Mathf.Max(coyoteTimer, 0f);

        // jumpCooldownTimer odlicza się ZAWSZE (w powietrzu i na ziemi)
        if (jumpCooldownTimer > 0f)
        {
            // Odliczanie jump cooldown timer niezależnie od stanu postaci
            jumpCooldownTimer -= Time.deltaTime;
        }
        // Zapewnienie, że jumpCooldownTimer nigdy nie spadnie poniżej 0
        jumpCooldownTimer = Mathf.Max(jumpCooldownTimer, 0f);
    }

    // ------------------------
    // Movement
    // ------------------------
    [Header("Camera")]
    [Tooltip("Kamera, względem której porusza się postać (zazwyczaj Main Camera)")]
    public Transform cameraTransform;

    private void HandleMovement()
    {
        DetectSlopeAndSliding();           // musi być na samym początku
        Vector3 inputDir = GetCameraRelativeInput();

        float targetSpeed = _moveInput.sqrMagnitude > 0.01f ? baseSpeed : 0f;

        // Twoje modyfikatory prędkości (backward, strafe, sprint, walk) – zostaw bez zmian
        if (_moveInput.sqrMagnitude > 0.01f)
        {
            float forwardAmount = _moveInput.y;
            float sideAmount = _moveInput.x;
            if (forwardAmount < -0.1f) targetSpeed *= backwardSpeedMultiplier;
            else if (Mathf.Abs(sideAmount) > 0.1f && Mathf.Abs(forwardAmount) < 0.1f) targetSpeed *= strafeSpeedMultiplier;
            else if (Mathf.Abs(sideAmount) > 0.1f && forwardAmount > 0.1f) targetSpeed *= 0.9f;

            if (_sprintInput) targetSpeed *= sprintMultiplier;
            else if (_walkInput) targetSpeed *= walkMultiplier;
        }

        float control = _characterController.isGrounded ? 1f : airboneControlMultiplier;
        float currentAccel = _moveInput.sqrMagnitude > 0.01f ? acceleration : deceleration;

        // ====================== ŚLIZG ======================
        if (isSliding)
        {
            _characterController.stepOffset = 0f;           // ważne!

            // PEŁNY wektor ślizgu + lekka kontrola gracza
            velocity = slideVelocity;

            if (_moveInput.sqrMagnitude > 0.01f)
            {
                Vector3 projectedInput = Vector3.ProjectOnPlane(inputDir, groundNormal);
                velocity += projectedInput * (targetSpeed * 0.4f);   // delikatny wpływ inputu
            }

            // Siła docisku do powierzchni (eliminuje odbicia przy wchodzeniu)
            velocity -= groundNormal * 5f * Time.deltaTime;

            // Zapobiegamy „wspinaniu się” w górę stoku
            velocity.y = Mathf.Min(velocity.y, 0f);
        }
        // ====================== NORMALNY RUCH ======================
        else
        {
            _characterController.stepOffset = 0.3f;   // przywróć normalną wartość

            Vector3 horizontalVel = new Vector3(velocity.x, 0f, velocity.z);
            Vector3 targetVelocity = inputDir * targetSpeed;
            horizontalVel = Vector3.MoveTowards(horizontalVel, targetVelocity, currentAccel * control * Time.deltaTime);

            velocity.x = horizontalVel.x;
            velocity.z = horizontalVel.z;
            velocity.y = _gravity.UpdateGravity(velocity.y, _characterController.isGrounded);
        }

        // Ruch
        _characterController.Move(velocity * Time.deltaTime);
    }

    // ------------------------
    // Jump
    // ------------------------
    private void HandleJump()
    {
        bool jumpPressed = _playerInput.actions["Jump"].triggered;

        if (jumpPressed && coyoteTimer > 0f && jumpCooldownTimer <= 0f)
        {
            // Oblicz prędkość skoku
            velocity.y = Mathf.Sqrt(jumpHeight * -2f * _gravity.GravityValue);
            currentCondition = Condition.Airborne;

            jumpCooldownTimer = jumpCooldown;
            coyoteTimer = 0f;
        }
    }
    // ------------------------
    // Camera and Rotation
    // ------------------------

    private void LateUpdate()
    {
        Vector3 horizontalVelocity = new Vector3(velocity.x, 0f, velocity.z);

        if (horizontalVelocity.sqrMagnitude > 0.01f)
        {
            RotateToCameraDirection();
        }
    }

    private void RotateToCameraDirection()
    {
        if (cameraTransform == null)
            return;

        Vector3 camForward = cameraTransform.forward;
        camForward.y = 0f;

        if (camForward.sqrMagnitude < 0.001f)
            return;

        Quaternion targetRotation = Quaternion.LookRotation(camForward);

        transform.rotation = Quaternion.Slerp(
            transform.rotation,
            targetRotation,
            15f * Time.deltaTime
        );
    }

    // ------------------------
    // Fall Tracking
    // ------------------------
    private void UpdateFallTracking()
    {
        bool isGroundedNow = _characterController.isGrounded;

        // Start spadania
        if (!isGroundedNow && wasGrounded)
        {
            fallStartHeight = transform.position.y;
        }

        // LĄDOWANIE ──────────────── TU RESETUJEMY COOLDOWN ────────────────
        if (isGroundedNow && !wasGrounded)
        {
            float fallDistance = Mathf.Max(0f, fallStartHeight - transform.position.y);
            currentGroundedState = EvaluateFallDistance(fallDistance);

            // Kluczowa linia – pełny reset cooldownu przy każdym dotknięciu ziemi
            jumpCooldownTimer = jumpCooldown;

            // reszta obsługi lądowania
            if (currentGroundedState >= GroundedState.SoftFall)
            {
                fallStateTimer = fallStateDisplayTime;
            }
            else
            {
                currentGroundedState = GroundedState.Grounded;
                fallStateTimer = 0f;
            }

            // ──────────────── opcjonalny debug, żeby zobaczyć co się dzieje
            // Debug.Log($"[LAND] Reset jump cooldown → {jumpCooldownTimer:F2}s");
        }

        // Dynamiczna zmiana w powietrzu
        if (!isGroundedNow)
        {
            float currentFallDistance = Mathf.Max(0f, fallStartHeight - transform.position.y);
            currentGroundedState = EvaluateFallDistance(currentFallDistance);
        }

        // Odliczanie timera stanu lądowania
        if (fallStateTimer > 0f)
        {
            fallStateTimer -= Time.deltaTime;
            if (fallStateTimer <= 0f && isGroundedNow)
            {
                currentGroundedState = GroundedState.Grounded;
            }
        }
        else if (isGroundedNow)
        {
            currentGroundedState = GroundedState.Grounded;
        }

        wasGrounded = isGroundedNow;
    }

    private GroundedState EvaluateFallDistance(float distance)
    {
        if (distance < 0.1f)
            return GroundedState.Grounded;

        if (distance < softGroundedDistance)
            return GroundedState.SafeFall;

        if (distance < normalGroundedDistance)
            return GroundedState.SoftFall;

        if (distance < hardGroundedDistance)
            return GroundedState.NormalFall;

        return GroundedState.HardFall;
    }

    // ------------------------
    // Update States
    // ------------------------
    private void UpdateStates()
    {
        if (!_characterController.isGrounded)
        {
            currentCondition = Condition.Airborne;
            currentMovementState = MovementState.Jumping;
            return;
        }

        currentCondition = Condition.Standing;

        // Brak ruchu → Idle (nawet jeśli trzymamy shift/ctrl)
        if (_moveInput.sqrMagnitude < 0.1f)
        {
            currentMovementState = MovementState.Idle;
            return;
        }

        // Priorytety modyfikatorów prędkości (kolejność ważna!)
        if (_sprintInput)           // najszybszy stan
        {
            currentMovementState = MovementState.Sprinting;
        }
        else if (_walkInput)        // najwolniejszy stan
        {
            currentMovementState = MovementState.Walking;
        }
        else
        {
            currentMovementState = MovementState.Running; // domyślny bieg
        }
    }
    private void DetectSlopeAndSliding()
    {
        isSliding = false;
        slideVelocity = Vector3.zero;
        currentSlopeAngle = 0f;
        currentSlopeState = SlopeState.Flat;

        float checkDistance = _characterController.height * 0.8f;
        Vector3 origin = transform.position;

        if (Physics.SphereCast(origin, _characterController.radius * 0.8f, Vector3.down,
            out RaycastHit hit, checkDistance, ~0, QueryTriggerInteraction.Ignore))
        {
            groundNormal = hit.normal;
            currentSlopeAngle = Vector3.Angle(hit.normal, Vector3.up);
            currentSlopeState = GetSlopeState(currentSlopeAngle);

            // kierunek zsuwania w dół stoku
            Vector3 slideDir = Vector3.ProjectOnPlane(Vector3.down, hit.normal).normalized;

            // tylko jeśli stromy stok
            if ((int)currentSlopeState >= (int)SlopeState.SlideDown)
            {
                isSliding = true;

                // Jeśli gracz podaje input, lekko modyfikujemy kierunek
                if (_moveInput.sqrMagnitude > 0.01f)
                {
                    Vector3 inputDir = GetCameraRelativeInput();
                    float inputInfluence = Mathf.InverseLerp(startSlideAngle, 85f, currentSlopeAngle);
                    // maksymalnie 25% wpływu inputu
                    slideDir = Vector3.Lerp(slideDir, inputDir, inputInfluence * 0.25f).normalized;
                }

                // Prędkość ślizgu rośnie wraz z kątem
                float angleFactor = Mathf.InverseLerp(startSlideAngle, 90f, currentSlopeAngle);
                float targetSlideSpeed = Mathf.Lerp(slideSpeed, maxSlideSpeed, angleFactor);

                // Ustawiamy velocity tylko w X/Z
                slideVelocity = slideDir * targetSlideSpeed;
            }

            // Kierunek stoku względem postaci (dla debuga i Animatora)
            Vector3 playerForward = transform.forward;
            Vector3 playerRight = transform.right;

            float forwardDot = Vector3.Dot(playerForward, slideDir);
            float rightDot = Vector3.Dot(playerRight, slideDir);

            currentSlopeDirection = SlopeDirection.None;

            if (Mathf.Abs(forwardDot) > Mathf.Abs(rightDot))
            {
                if (forwardDot > 0.3f) currentSlopeDirection = SlopeDirection.DownForward;
                else if (forwardDot < -0.3f) currentSlopeDirection = SlopeDirection.DownBackward;
            }
            else
            {
                if (rightDot > 0.3f) currentSlopeDirection = SlopeDirection.DownRight;
                else if (rightDot < -0.3f) currentSlopeDirection = SlopeDirection.DownLeft;
            }

            Debug.Log($"[SLOPE] Angle: {currentSlopeAngle:F1}° | Sliding: {isSliding} | Speed: {slideVelocity.magnitude:F1}");
        }
    }

    private SlopeState GetSlopeState(float angle)
    {
        if (angle < 10f) return SlopeState.Flat;
        if (angle < 25f) return SlopeState.SlightDown;
        if (angle < 40f) return SlopeState.SteepDown;
        if (angle < 55f) return SlopeState.SlideDown;
        if (angle < 65f) return SlopeState.VerySteep;
        if (angle < 80f) return SlopeState.NearVertical;
        return SlopeState.VerticalWall;
    }
    private Vector3 GetCameraRelativeInput()
    {
        Vector3 inputDir = new Vector3(_moveInput.x, 0f, _moveInput.y);
        if (useNormalizedDiagonalMovement && inputDir.sqrMagnitude > 0.01f)
            inputDir.Normalize();

        if (cameraTransform != null)
        {
            Vector3 camForward = cameraTransform.forward;
            Vector3 camRight = cameraTransform.right;
            camForward.y = camRight.y = 0f;
            camForward.Normalize();
            camRight.Normalize();
            inputDir = camRight * inputDir.x + camForward * inputDir.z;
        }
        return inputDir;
    }
    private void UpdateAnimator()
    {
        if (_animator == null)
            return;

        // Prędkość w płaszczyźnie XZ
        Vector3 horizontalVelocity = new Vector3(velocity.x, 0f, velocity.z);
        float speed = horizontalVelocity.magnitude;

        _animator.SetFloat("Speed", speed);
        float displayVertical = _characterController.isGrounded ? 0f : velocity.y;
        _animator.SetFloat("VerticalVelocity", displayVertical);

        _animator.SetFloat("DirectionX", _moveInput.x);
        _animator.SetFloat("DirectionY", _moveInput.y);

        // ========================
        // NOWE – IsJumping TYLKO podczas prawdziwego skoku!
        // ========================
        bool isJumping = !_characterController.isGrounded && velocity.y > 0.1f;   // > 0.1f żeby uniknąć szumów
        _animator.SetBool("IsJumping", isJumping);

        // Opcjonalnie: osobny parametr na spadanie (bardzo polecam!)
        _animator.SetBool("IsFalling", !_characterController.isGrounded && velocity.y < -0.1f);

        // Stany
        _animator.SetBool("IsGrounded", _characterController.isGrounded);

        _animator.SetInteger("MovementState", (int)currentMovementState);
        _animator.SetInteger("FallState", (int)currentGroundedState);
        _animator.SetInteger("SlopeState", (int)currentSlopeState);
        _animator.SetBool("IsSliding", isSliding);
        _animator.SetFloat("SlopeAngle", currentSlopeAngle);
        _animator.SetFloat("SlideSpeed", slideVelocity.magnitude);

        _animator.SetInteger("SlopeDirection", (int)currentSlopeDirection);
    }
} 

That sounds like you wrote a bug… and that means… time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

Remember with Unity the code is only a tiny fraction of the problem space. Everything asset- and scene- wise must also be set up correctly to match the associated code and its assumptions.

ALSO…

Do you really want to hassle with writing your own CC? It’s pretty difficult obtuse stuff, certainly NOT a beginner topic at all.

Here is a super-basic starter prototype FPS based on Character Controller (BasicFPCC):

That one has run, walk, jump, slide, crouch.

And here is a free Kinematic Character Controller that also comes with a huge playground:

To write a character controller? That’s a waste of AI. Just pick an existing character controller and go with it, there’s plenty that are free and capable.