Cant jump buffer

so i tried implementing jump buffering using a tut on youtube (that uses old input and rigidbody) and got only coyote time working. tho the jump buffering doesnt work no matter whatever came on my mind to do.
previous threads that i found either are rigidbody or are older input plus i started coding like 2 weeks ago and still am getting things figured out so sorry for my possibly bad code

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerMovement : MonoBehaviour
{
    public bool canMove = true;
    public CharacterController controller;
    private Vector2 moveInput;
    public float speed = 5f;
    public Vector3 velocity;
    public float gravity = -9.81f;
    public float jumpHeight = 3f;
    public Transform playerCamera;
    public float mouseSensitivity = 100f;
    private float xRotation = 0f;
    private Vector2 lookInput;
    public bool isSprinting = false;
    private Camera playerCameraalt;
    private float targetfov;
    public float fovmin = 60f;
    public float fovmax = 90f;
    private float fovSpeed = 10f;
    [SerializeField] private float coyoteTime = 0.2f;
    private float coyoteTimer;
    [SerializeField] private float JumpBufferTime = 0.2f;
    private float jumpBufferTimer;

    // Start is called before the first frame update
    void Start()
    {
        controller = GetComponent<CharacterController>();
        Cursor.lockState = CursorLockMode.Locked;
        playerCameraalt = playerCamera.GetComponent<Camera>();
        targetfov = fovmin;
    }
    
    void OnMove(InputValue value)
    {
        moveInput = value.Get<Vector2>();
    }

    void OnJump(InputValue value)
    {
        if (value.isPressed)
        {
            jumpBufferTimer = JumpBufferTime;
        }
    }

    void OnLook(InputValue value)
    {
        lookInput = value.Get<Vector2>();
    }
    void OnSprint(InputValue value)
    {
        isSprinting = value.isPressed;
        if (isSprinting)
        {
            speed = 10f;
            targetfov = fovmax;
        }
        else
        {
            speed = 5f;
            targetfov = fovmin;
        }
    }

void Update()
{
    if (!canMove)
    {
        return;
    }


    if (controller.isGrounded)
    {
        coyoteTimer = coyoteTime;

        if (velocity.y < 0f)
        {
            velocity.y = -2f;
        }
    }

    if (jumpBufferTimer > 0f && coyoteTimer > 0f)
    {
        velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        jumpBufferTimer = 0f;
        coyoteTimer = 0f;
    }

    Vector3 move = transform.right * moveInput.x + transform.forward * moveInput.y;
    controller.Move(move * speed * Time.deltaTime);

    velocity.y += gravity * Time.deltaTime;
    controller.Move(velocity * Time.deltaTime);

    if (controller.isGrounded && jumpBufferTimer > 0f)
    {
        velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        jumpBufferTimer = 0f;
        coyoteTimer = 0f;
    }

    if (jumpBufferTimer > 0f)
    {
        jumpBufferTimer -= Time.deltaTime;
    }

    if (coyoteTimer > 0f)
    {
        coyoteTimer -= Time.deltaTime;
    }

    float mouseX = lookInput.x * mouseSensitivity * Time.deltaTime;
    float mouseY = lookInput.y * mouseSensitivity * Time.deltaTime;
    xRotation -= mouseY;
    xRotation = Mathf.Clamp(xRotation, -90f, 90f);
    playerCamera.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
    transform.Rotate(Vector3.up * mouseX);

    float currentfov = playerCameraalt.fieldOfView;
    playerCameraalt.fieldOfView = Mathf.Lerp(currentfov, targetfov, fovSpeed * Time.deltaTime);
}
}

The description “doesn’t work” is not a useful way to describe a problem. When asking about an issue, it is better to explain what you expected to happen and what is happening instead, along with any solutions you have already tried. This helps others understand the problem and the intended behavior, and it saves time by preventing them from suggesting things you have already attempted.

Looking at your code, there are some unusual aspects, particularly regarding the order in which operations occur.

You declare a velocity variable of type Vector3 and modify it in multiple places inside the Update method. In particular, there are two if statements that execute the exact same code. The first appears on line 87: if (jumpBufferTimer > 0f && coyoteTimer > 0f), and the second on line 100: if (controller.isGrounded && jumpBufferTimer > 0f).

What is unusual is that between these two blocks you modify the velocity using velocity.y += gravity * Time.deltaTime; and then pass that value to the controller. This raises a question about the purpose of the second if block. It updates the velocity in the same way as the first one, but the result is not used anywhere afterward by the controller. It will affect the velocity value in the next Update call, but is that intentional? Are you trying to reset the velocity for the following frame?

If not, consider what the purpose of that if block is and whether it should appear earlier in the code, specifically before the call to the controller, and possibly before modifying the velocity on the y axis.

Throw out the above code as it is based on a defective piece of example code from Unity that was picked up by all kinds of people and tutorial-makers.

CharacterController CharMover broken:

I wrote about this before: the Unity example code used to show defective example code.

If you call .Move() twice in one single frame, the grounded check may fail.

I reported it to Unity via their docs feedback in October 2020 and it appears to be fixed now in early 2025.

This is their new example working code which I have not personally tested since they fixed it:

Here is my personal work-around:

You can also start from one of these much-more-fully-featured 3D character controllers:

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:

FINALLY… if you want to implement jump buffering and multi-jump, first check to see if the above controllers support it, and if they don’t, here is the fundamental timer-based logic to handle it in 2D. You can apply this same logic in 3D, but I have never done so.

Here’s my 2D coyote time and jump buffering demo, with fully functioning project.

Coyote Time, Multi-Jumping, Jump-Buffering, and disconnecting input gathering from input processing: