Unity movement coroutine, delay between steps

Hi, I am using a simple coroutine for lerp movement. My issue is that between each step there is a tiny delay, how would I go about transforming it into continouous movement?

float timer = 0;
    protected IEnumerator MoveCharacter(Transform actor, Vector3 targetPosition, float movementTime)
    {
        PreDelayTrigger();
        timer = 0;
        while (timer < movementTime)
        {
            timer += Time.deltaTime;
            actor.localPosition = Vector2.Lerp(actor.position, targetPosition, timer / movementTime);
            if (timer >= movementTime)
                break;
            yield return new WaitForSeconds(Time.deltaTime);
        }
        PostDelayTrigger();
        yield return null;
    }

And this is the code that I’ve been using to trigger the coroutine, I’m using the new input system to detect if movement keys are down, then use them as movement directions.

if (runningCoroutine == null)
        {
            Vector2 movement = controls.Player.Move.ReadValue<Vector2>();
            if (movement != Vector2.zero)
            {
                runningCoroutine = StartCoroutine(MoveCharacter(transform, transform.position + (Vector3)movement, stepTime));

                Debug.Log("Moving");
            }
            else
            {
                return;
            }
        }
        else
        {
            Debug.Log("Coroutine already running - moving");
            return;
        }

You’re telling it to delay

yield return new WaitForSeconds(Time.deltaTime);

if you want it to delay only until next frame it should be yield return null

But for a key press movement you won’t want to use a coroutine, unless you have very good reasons because it’s much more complex than just calling a regular function from update.

For example, your target is always changing while you are pressing a movement key, but your coroutine doesn’t see that. The Vector3 targetPosition that you pass into the coroutine will not update to reflect the changes to transform.position, it is a copy of that data from when you passed it in, and it will not get updated. You can fix that, but it’s more complicated.

You also have to make sure that only one coroutine is running at a time, and you have to make sure they run and stop running depending on whether you are moving.

Update is way easier.

The code is a simplified version of what I’m running, the movement is grid based steps so it’s either interpreted as 1 or 0 in the proper code, having it as a singular coroutine is exactly what I want, where another cannot be run until one is finished.

Wouldn’t the coroutine in your example run its course within a single update, as it instantly returns a null value and has to be called again to progress? Or did I just misunderstand coroutines and they don’t end at yield return unless it’s a wait?

Edit: Yes, having it “over a period of time” is exactly what I’m trying to do, have the character be able to move a single step (or however many) at controllable speed.

In the scenario of pressing movement key. I want the player to move a step. If I change the speed of the player, it will move a bigger step. If I speed up the step, it’s just a faster step.
I guess step is a bad term to explain it, a single tile movement?

No. Yield return null will pause the coroutine this frame and then continue where it left off next frame
A coroutine will stop when it has no more lines of code to run because it’s reached the end of the function.

They don’t need to end with yield return null, that will just cause them to take a frame longer to finish.

Ok.
//Ignore the performed actions advice, the movement vector you have is actually better

So to track whether the coroutine is running or not, you can use a flag that lives outside of the coroutine. Set it to true as the top line of your coroutine, and set it to false as the last line of your coroutine. Passing the flag in as an argument won’t allow you to set it from the coroutine though. You’d have to access it as a local class variable if you can, so use it without passing it in.

Right after your while loop, you should set the position to the targetPosition, because lerp isn’t entirely accurate. That’s a nuance of lerp.

Something closer to this:

private bool _isMoving = false;

//...

if (!_isMoving) {
  Vector2 movement = controls.Player.Move.ReadValue < Vector2 > ();
  if (movement != Vector2.zero {
      StartCoroutine(MoveCharacter(transform, transform.position + (Vector3) movement, stepTime));
    }
  }

  //...

  protected IEnumerator MoveCharacter(Transform actor, Vector3 targetPosition, float movementTime) {
    _isMoving = true;
    PreDelayTrigger();

    timer = 0;
    while (timer < movementTime) {
      timer += Time.deltaTime;
      actor.localPosition = Vector2.Lerp(actor.position, targetPosition, timer / movementTime);

      if (timer >= movementTime)
        break;

      yield return null;
    }
    actor.localPosition = targetPosition;

    PostDelayTrigger();
    _isMoving = false;
  }

I’ve attempted to use a MoveTowards instead of Lerp and it seems to be a bit smoother, but there still seems to be tiny delay, specifically between first step and continuous movement, I guess it’s something to do with the states in the complete code and how they interact with the coroutine. Oddly enough, moving and adjusting the code from a coroutine to update seems to remove the delay, so I guess I’ll just do it this way hah. I appreciate the help.