Unity 2d проблема с реализацией прыжка

Я хочу реализовать прыжок игрока высота которого должна регулироваться в зависимости от времени нажатия на клавишу, я слышал что взаимодействия с физикой желательно делать в Fixed Update но по итогу сила прыжка либо невероятно большая что игрок улетает за границы экрана либо невероятно маленькая и впринципе я не могу регулировать высоту прыжка. Предполагаю что проблема в том что я что-то делаю неправильно, мой код ниже

private float maxTimeJump = 0.5f;
private float jumpForce = 700f;

private void Update ()
{
    if (Input.GetKeyDown(KeyCode.W))
    {
        if (isGround)
        {
            JumpClic = true;
            controlTimeJump = Time.time;
        }
        else
        {
            JumpClic = false;
        }
    }
}
private void FixedUpdate ()
{
    if (jumpClic)
    {
        float time = Time.time - controlTimeJump;
        if (time < maxTimeJump)
        {
            rb.AddForce(Vector2.up * jumpForce * (time * 10));
        }
    }
    else
    {
        controlTimeJump = 0;
        IsGround = Phisics2D.OverlapCircle(trigerTransform.position, trigerRadius, layer);
    }
}

Ответы (2 шт):

Автор решения: Yaroslav
// Управление не отностится к функционалу прыжка, это другая зона ответственности.
public class PlayerInputs : MonoBehaviour
{
    [SerializeField] private JumpFoo _jump;
    private KeyCode _jumpInput = KeyCode.Space;

    private void Update ()
    {
        if (Input.GetKeyDown(_jumpInput))
            _jump.TryStartJump();
        if (Input.GetKeyUp(_jumpInput))
            _jump.FinishJump();
    }
}
// Поля используемые, как атрибуты на проверку земли никак не пересикаются 
// с полями для прыжка, что прямо указывает на то, что они несут другой смысл
// и ответственность.
// К тому-же удобнее повесить на другой дочерний gameObject 
// и контролировать центр сферы проверки.
public class GroundCheck : MonoBehaviour
{
    [SerializeField] private float _radius = 0.5f;
    [SerializeField] private LayerMask _mask = 1 << 10;

    // IsGround - это земля? объект явно землей быть не должен, никогда,
    // поэтому OnGround - на земле? или IsGrounded - заземлен?
    // Хоть это и физика, нет нужды проверять OverlapCircle в FixedUpdate,
    // поскольку такт обновления физики и его дельты не играют никакой роли.
    public bool IsGrounded =>
        Physics2D.OverlapCircle(transform.position, _radius, _mask);

    private void OnDrawGizmosSelected ()
    {
        Gizmos.color = new Color(1, 0.4f, 0);
        Gizmos.DrawWireSphere(transform.position, _radius);
    }
}
[RequireComponent(typeof(Rigidbody2D))]
public class JumpFoo : MonoBehaviour
{
    // Если весь класс посвещён прыжку, как и должно быть,
    // нет смысла к названию абсолютно всех переменных и методов добавлять Jump, 
    // они и так в контексте.
    // Базовая сила прыжка и сила увеличивающая высоту с течением 
    // времени это две разные силы.
    [SerializeField] private float _force = 1f;
    [SerializeField] private float _boostForce = 0.1f;
    [SerializeField] private float _maxBoostTime = 1;
    [Space]
    [SerializeField] private GroundCheck _groundCheck;
    private Rigidbody2D _body;
    private IEnumerator _boost;
    
    private void OnEnable ()
    {
        _body = GetComponent<Rigidbody2D>();
    }

    // Настройка полей сил и времени довольно тонкая, за время действия буста
    // гравитация не должна особо пересиливать силу подъема.
    // Для настройки полей лучше визуализировать время действия буста.
    private void OnDrawGizmos ()
    {
        if (_boost != null)
        {
            Gizmos.color = new Color(0, 0.4f, 1, 0.25f);
            Gizmos.DrawCube(transform.position + Vector3.one * 0.5f, Vector3.one);
        }
        if (_body.velocity.y < 0)
        {
            Gizmos.color = new Color(0, 1f, 0.4f, 0.25f);
            Gizmos.DrawCube(transform.position - Vector3.one * 0.1f, -Vector3.one * 0.2f);
        }
    }

    public void TryStartJump ()
    {
        // Проверять IsGrounded каждый такт, нет никакого смысла, только по факту прыжка,
        // или во время падения (_body.velocity.y < 0), что бы понять, когда он преземлится.
        if (_groundCheck.IsGrounded)
        {
            _body.AddForce(Vector2.up * _force);
            StartBoost();
        }
    }

    public void FinishJump () => StopBoost();

    // Поскольку это не непрерывное действие, а ограниченное рамками времени событие,
    // корректнее использовать не одну из функций обновления, а корутину.
    private void StartBoost ()
    {
        StopBoost();
        _boost = Boost();
        StartCoroutine(_boost);
    }

    private void StopBoost ()
    {
        if (_boost != null)
        {
            StopCoroutine(_boost);
            _boost = null;
        }
    }

    // Для взаимодействия с физикой нужен FixedUpdate, потому что ForceMode2D.Force 
    // зависит от длины шага (fixedDeltaTime), о чём написано в документации по AddForce
    // и постоянное применение чаще или реже такта физики даст кривые результаты,
    // но разовое можно сделать и в Update.
    // Но ForceMode2D.Impulse от дельты не зависит, поэтому мы будем умнажать силу
    // на дельту и можем делать это как в Update с Time.deltaTime, 
    // так и в FixedUpdate c fixedDeltaTime.
    // Кстате ForceMode2D.Force и ForceMode2D.Impulse ещё зависят от массы, 
    // не зависящих от массы типов, как ForceMode.VelocityChange, для 2D к сожалению нет.
    // Эта корутина прерывается по инструкции WaitForFixedUpdate и по сути как метод FixedUpdate, 
    // живущий ограниченный _maxBoostTime отрезок времени. 
    private IEnumerator Boost ()
    {
        YieldInstruction delayInstruction = new WaitForFixedUpdate();
        float elapsed = 0;
        while (elapsed < _maxBoostTime)
        {
            yield return delayInstruction;
            Vector3 force = Vector2.up * _boostForce * Time.fixedDeltaTime;
            _body.AddForce(force, ForceMode2D.Impulse);
            elapsed += Time.fixedDeltaTime;
        }
        _boost = null;
    }
}
→ Ссылка
Автор решения: aepot

Непонятно, зачем здесь time. Также если вы хотите обрабатывать удержание клавиши, а не нажатие, то нужен GetKey, а не GetKeyDown. В этом случае Update вообще не нужен.

Если персонаж на земле, то можно давать полный импульс. При удержании клавиши можно немного добавлять, например 1/10 от полного импульса и силу добавления снижать на каждый вызов метода, например в 2 раза.

Код получится простой.

private float jumpForce = 700f;
private float currentJumpForce;

private void FixedUpdate()
{
    if (Input.GetKey(KeyCode.W))
    {
        bool isGround = Phisics2D.OverlapCircle(trigerTransform.position, trigerRadius, layer);
        if (isGround)
        {
            currentJumpForce = jumpForce;
        }
        else
        {
            currentJumpForce /= (currentJumpForce == jumpForce) ? 10 : 2;
        }
        rb.AddForce(Vector2.up * currentJumpForce);
    }
    else
        currentJumpForce = 0;
}

Ну а дальше регулируйте изначальную силу прыжка и коэффициенты деления силы, чтобы добиться требуемого эффекта. Это самая простая реализация.

→ Ссылка