Search code examples
c#unity-game-enginelerp

Jumping forward movement with Lerp causes infinite loop


I'm writing a simple controller for my player to move by jumping forwards/backwards/sideways.

I know I can easily move it by adding a Vector3 but I'm trying to have it move smoothly between the start point and the end point of the movement. I'm trying to use Lerp here but I'm not familiar with it, and I think I'm creating and infinite loop when testing it out as Unity freezes up when I try to move in play mode, but I can't figure out the problem. Help!

private Rigidbody playerRb;
private CapsuleCollider playerColl;

private Vector3 startPos;
    private Vector3 endPos;

    [SerializeField] private float jumpSpeed = 1f;

    private float startTime;
    private float jumpLength;

    private void Start()
    {
        playerRb = GetComponent<Rigidbody>();
        playerColl = GetComponent<CapsuleCollider>();
    }


    void Update()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            startTime = Time.time;
            startPos = playerRb.transform.position;
            endPos = startPos + Vector3.forward;//move (0,0,1)
            jumpLength = Vector3.Distance(startPos, endPos);

            while(transform.position != endPos)
            {
                float distCovered = (Time.time - startTime) * jumpSpeed;
                float fraction = distCovered / jumpLength;
                playerRb.transform.position = Vector3.Lerp(startPos, endPos, fraction);
            }
            
        }
    }

Solution

  • You are indeed creating an infinite loop with the code while (transform.position != endPos), due to the fact that the while loop probably isn't doing what you think it's doing.

    The while loop here will run within a single frame. Meaning that Time.time never progresses, meaning that (Time.time - startTime) will always be 0. This leads to the position never updating, which means the position is still at the start, which means the while loop runs again (in the same frame), meaning that (Time.time - startTime) is 0... infinite loop.

    Instead of lerping between the two positions, you should instead set a velocity (inside of FixedUpdate(), not Update()!) and allow the Rigidbody to move your player instead. Something like:

    bool wKeyPressed;
    float jumpDuration;
    Vector3 jumpVelocity;
    
    void Update() {
      if (Input.GetKeyDown(KeyCode.W)) {
        wKeyPressed = true;
      }
    }
    
    void FixedUpdate() {
      if (wKeyPressed) {
        if (jumpDuration <= 0) {
          Vector3 target = playerRb.position + Vector3.forward; // Or however you get the target...
          Vector3 distance = (target - playerRb.position);
          jumpVelocity = distance.normalized * jumpSpeed;
          jumpDuration = distance.magnitude / jumpSpeed;
        }
        wKeyPressed = false;
      }
    
      if (jumpDuration > 0) {
        // Currently inside a jump...
        playerRb.velocity = jumpVelocity;
        jumpDuration -= Time.fixedDeltaTime;
      }
    }
    

    Note that if you're using a "dynamic" Rigidbody (one that has gravity, air resistance, friction, etc...), you'll need to continuously reapply the velocity, since those other factors will mess with your velocity.

    The reason we use FixedUpdate() here is that Unity's physics system (and by extension, Rigidbodies), only update at a fixed interval; while Update() will be called every frame, which can be more/less than the fixed interval. If you modify or rely on the physics system, you need to do it within FixedUpdate()

    Input.GetKeyDown has to be done inside of Update() though, since inputs are processed every frame. That's why we use bool wKeyPressed to transfer the state between Update() and FixedUpdate(), instead of putting both in one.