Why do we need to calculate distance in 2D roguelike tutorial?

    protected IEnumerator SmoothMovement (Vector3 end)
    {
        float sqrRemainingDistance = (transform.position - end).sqrMagnitude;

        while (sqrRemainingDistance > float.Epsilon)
        {
            Vector3 newPosition = Vector3.MoveTowards (rb2D.position, end, inverseMoveTime * Time.deltaTime);
            rb2D.MovePosition (newPosition);
            sqrRemainingDistance = (transform.position - end).sqrMagnitude;
            yeild return null;
        }
    }

I am not exactly sure what this code does in the 2D Roguelike Game in the Unity Tutorial. Can someone clarify this for me?

I think it has something to do with calculating distance between the tiles but I was wondering why you would need to calculate the distance if the objects are only moving one tile at a time.

Another case of co-routine abuse… Is this in an official Unity tutorial?!

It’s checking if the object has reached the center of a tile assuming the object is moving from the center of a tile to the center of another tile.

how is that “abuse”?

@steadmuffin yield is mispelt :slight_smile: also, the tutorial contains extensive comments for each section of that code that pretty much explains what each section does…

//Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
protected IEnumerator SmoothMovement (Vector3 end)
{
//Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter.
//Square magnitude is used instead of magnitude because it's computationally cheaper. float sqrRemainingDistance = (transform.position - end).sqrMagnitude;

//While that distance is greater than a very small amount (Epsilon, almost zero):
while(sqrRemainingDistance > float.Epsilon)
{
//Find a new position proportionally closer to the end, based on the moveTime
Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);

//Call MovePosition on attached Rigidbody2D and move it to the calculated position.
rb2D.MovePosition (newPostion);

//Recalculate the remaining distance after moving.
sqrRemainingDistance = (transform.position - end).sqrMagnitude;

//Return and loop until sqrRemainingDistance is close enough to zero to end the function
yield return null;
}
}

(the tutorial’s original)

How is that abuse of Coroutines? Seems like pretty much an ideal use case to me.

1 Like

Its not, this whole thing should be done in an Update, not a coroutine.

What that code does… this does better.

    public Vector3 end;
    public float speed = 2;
    void Update ()
    {
        if((transform.position - end).sqrMagnitude < float.Epsilon) return;

        rb2D.MovePosition (Vector3.MoveTowards (transform.position, end, speed * Time.deltaTime));
    }

Completely subjective.

If by “better” you mean “checking distance every frame regardless of whether or not the object is actually moving”. :slight_smile:

1 Like

sigh…

This code does exactly the same thing… EVERY FRAME

Until that while is no longer true at which point the coroutine ends.

1 Like

Sorry, what I meant to say was “distance” abuse. Considering the start time, start position, and end position are known. You would be better off with a slerp from start to end and just check if the move is complete. This cuts down on the overhead of creating and returning a difference vector and the cost of getting the square magnitude. I still stand by my original statement though. Update and FixedUpdate should always be used for physics unless you’re writing a massive simulation with thousands of objects that aren’t continuously moving. Even then you would be better off just disabling the movement script on the GameObject until it is required to move.

Slerp Coroutine Example:

public float MoveSpeed = 0.2f;
public Vector3 Start = new Vector3(0.0f, 0.0f, 0.0f);
public Vector3 End = new Vector3(5.0f, 0.0f, 0.0f);
public float StartTime = 0.0f;
public float Complete = 0.0f;

protected IEnumerator SmoothMovement()
{
    while (Complete < 1.0f)
    {
        Complete = (Time.time - StartTime) * MoveSpeed;
        RigidBody.MovePosition(Vector3.Slerp(Start, End, Complete));
        yield return null;
    }
}

With bigmisterb here. Coroutines are basically are very situational. Here, it is abused. It’s forced into a game that never needs it, can never need it and never will need it.

Also it is happily creating garbage for those all-important stutters one surely needs on mobile.

protected IEnumerator SmoothMovementUntilGCCollect (Vector3 end)
1 Like

For what KelsoMRK said, it is true, the coroutine ends. (eventually) Which means it is GC’d just as normal.

I disagree with jimroberts about distance checking and percentage. because you can change the target in the middle of operation, where if you had the percentage of completion, then the end could not change.

Lastly, there is a problem with the code jimroberts suggests. NEVER use Lerp/Slerp when doing a percentage. It will never reach it’s target. As a matter of fact, the result of Lerp/Slerp when using it like this is always a fast in/slow out curve. This is pretty to look at, but not a very good measure of exact movement.
The OP had the correct idea when he used MoveTowards, since this provides a consistent movement from one place to another.

consider that you have to move 100 units, and you use 10% each frame. The numbers appear like so:
100
90
81
72.9
65.61
59.049

As you can see, the numbers do not consistently drop.

You will find that my slerp function does reach its destination every single time due to the way the loop is written.

Well they’re never actually needed. I pretty much only use them for the occasional fire and forget functionality and generally prefer to make my own state machines rather than let the compiler do it (probably why I’ve never thought about the garbage issue).

What sort of situations would not be abuse?

Is the GC what you’re meaning by abuse? Are you saying a Coroutine should only be used when it is going to run for long periods of time so GC is a non-issue? (I’m more than willing to be corrected here :slight_smile: ).

That is the ideal scenario. However, the alternatives are still much better.

My apologies, You are correct, however, this still doesnt allow you to change the end target at all.

the OP’s original comment used sqrMagnitude, not magnitude, so it is not a “distance” abuse, thus allowing the change of the end target.

// sqrMagnitude
(x1 + x2) * (y1 + y2) + (z1 + z2);

// magnitude
Mathf.Sqrt((x1 + x2) * (y1 + y2) + (z1 + z2));

I still don’t get why he can’t change the end target? He would simply have to update the Start position and StartTime to reflect the new end target.

public void NewTarget(Vector3 pTarget)
{
    StartTime = Time.time;
    Complete = 0.0f;
    Start = transform.position;
    End = pTarget;
}

Where a simple condition in a loop ends up being messier and harder to read usually. In this case it seems like that the simple condition in loop is simpler to read and maintain.

I used to use coroutines a lot until I realised that it was becoming too easy to create bugs, race conditions and generally not really solving much (programs become harder to follow) so I’ve developed an aversion to them. In theory they’re really great. In practise, in larger games I don’t use them much for the above reasons.

you change the end target, but movement then becomes erratic. If the end is suddenly changed, then the object moves drasticly from one place to another, no smooth movment at all, using MoveTowards, you simply take the current position and start moving it towards the new end target.

If you were going from 0 to 10, and were 50% (at 5) then change the end to 20, you would move from 5 to 10 immediately, since 10 is half way between the two.

Where this has an advantage of being numerically correct, most games and players wont want things to just jerk suddenly. It is not very upwardly mobile.