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 шт):
// Управление не отностится к функционалу прыжка, это другая зона ответственности.
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;
}
}
Непонятно, зачем здесь 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;
}
Ну а дальше регулируйте изначальную силу прыжка и коэффициенты деления силы, чтобы добиться требуемого эффекта. Это самая простая реализация.