1

I'm working on a movement script and it seems there is a big problem with equality/approximation functions. Unity never detects the point I'm looking for unless I involve an own approximation value which is about 0.1 (far too unprecise) and not even that is always detected.

So, as I said I'm having this movement script for a machine. Rightnow, all it shall do is move to coordinates, where it shall go along the x axis first, if it has reached the x destination value go along the z axis and so on. My first idea and attempt was to use the MoveTowards function. That didn't work and I didn't get the point about that problem. But since Google wasn't helpful with that and I ran out of ideas, I was going for Transform.Translate. The translation works fine with it but the machine didn't stop when it should. So I was trying a few things(I will show in the code section) and came up with a solution, in that I compare the current coordinates with these of the destination and check, if they are less than a tolerance value variable I created. The lowest value that would work then was 0.031 but now I'm using 0.1 because the first value only worked sometimes and also 0.1 isn't a sure thing. However, overall that works(most of the time) but it is far too unprecise according to my needs. So I was wondering, where the heck is the problem?

Technical Background and other tries: I'm working with Unity 2019.2.4f1. The models I'm using are probably many years old and first, they were in 3ds. I was also wondering if maybe it's the filetype's problem so I got myself an available stl file, loaded it into Blender and exported it to fbx. I even created a fresh new project to test that but still it's causing the same problem. I was also wondering if that might be a scale kind of problem but scaling the whole machine up and down didn't change anything.

My first attempt with MoveTowards looked something like this. I'm wondering if I should hang on to this because the behaviour was strange. I printed the objects position and the position changed as expected, but only in the debug log, I didn't see the machine moving(also scaled everything up to see if the difference was only too small for my senses)

    objY.position = Vector3.MoveTowards(objY.position, destination, speedY * Time.deltaTime);

Then I tried this and I think the problem here was that it was moving but across the destination and further

    while(objY.position == destination)
        objY.Translate(0, dir * Time.deltaTime, 0);
        break;

This is the code I'm using rightnow which kind of works. "unityDistanceTolerance" rightnow is 0.1. This is called when isRunning true is (as you might expect) and it becomes true, when I press Return

private IEnumerator MovementRoutine()
{
    if (destination == new Vector3(0, 0, 0))
        yield return null;

    else
    {
        while (Mathf.Abs((destination - objY.position).x) > unityDistanceTolerance)
        {
            MoveX();
            yield break;
        }
        if (Mathf.Abs((destination - objY.position).x) <= unityDistanceTolerance)
        {
            while (Mathf.Abs((destination - objY.position).z) > unityDistanceTolerance)
            {
                MoveZ();
                yield break;
            }
            if (Mathf.Abs((destination - objY.position).z) <= unityDistanceTolerance)
            {
                while (Mathf.Abs((destination - objY.position).y) > unityDistanceTolerance)
                {
                    MoveY();
                    yield break;
                }
            }
        }
    }

    isRunning = false;
}

And this is, what I actually want to use, but with this the machine just skips the point and never stops (just like the simple equality comparison)

private IEnumerator MovementRoutine()
{
    if (destination == new Vector3(0, 0, 0))
        yield return null;

    else
    {
        while (!Mathf.Approximately(destination.x, objY.position.x))
        {
            MoveX();
            yield break;
        }
        if (Mathf.Approximately(destination.x, objY.position.x))
        {
            while (!Mathf.Approximately(destination.z, objY.position.z))
            {
                MoveZ();
                yield break;
            }
            if (Mathf.Approximately(destination.z, objY.position.z))
            {
                while (Mathf.Approximately(destination.y, objY.position.y))
                {
                    MoveY();
                    yield break;
                }
            }
        }
    }

    isRunning = false;
}

I've also tried replacing the Approximately function with this, but same result

        while (Mathf.Abs((destination - objY.position).x) > Mathf.Epsilon)

This should be everything I've tried. I'm really wondering why this happens and even more how I can fix that. I hope this is enough input and somebody can help me.

  • you call yield break inside a while loop. This seems odd to me, since yield break exits your IEnumerator (not "just" the while loop). Is this what you intend to do? – Immorality Sep 12 '19 at 11:33
  • actually I didn't think about that, but this works as intended. – Fledermauserl Sep 12 '19 at 11:36
  • @Immorality I'm just wondering, is that so? because in that case the whole method wouldn't even execute to the end and never reach another axis, or am I wrong? – Fledermauserl Sep 12 '19 at 11:39
  • No that is exactly what should be happening. Whenever it reaches a `yield break;`, the IEnumerator "returns". So any code after the `yield break;` is not called. Not even in a next frame. I'm not sure if you can use a regular break for exiting a while loop inside a coroutine. If not, then set a boolean flag which you add as a condition to your while loop. – Immorality Sep 12 '19 at 11:42
  • now I'm wondering why it even works :D so a regular break simply exits the loop? – Fledermauserl Sep 12 '19 at 11:47
  • ok, I tried it out. using break causes the machine to only do one step when I press. So not the whole movement is executed, as I intend – Fledermauserl Sep 12 '19 at 11:49
  • so maybe this is exactly what I want... – Fledermauserl Sep 12 '19 at 11:50
  • [`MoveTowards`](https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html) already assures there is no overshooting in the movement. However ... why do you want to check this in a `while` loop **at all**? ... If you translate it anyway only exactly once why not a simple `if` block instead? – derHugo Sep 12 '19 at 11:58
  • @Immorality yes exactly: `yield break` terminates the entire IEnumerator (kind of like `return` in a method) - the usual `break;` would only terminate the according `while` block as usual – derHugo Sep 12 '19 at 12:02

3 Answers3

1

Alright, in fact it was a logical mistake of mine. I'm always checking for the position of the same object (which is indeed what I want) but I'm moving other objects to that position which means it is not the object I'm checking for, that is reaching the position. So now I'm setting the position and calculate it with the distance of the objects. All your answers helped me find that out, otherwise I probably could have been searching for years. So thank you guys!

0

Float is not precise. You can read about it in this answer. Check if your variable passed the target value and then at the end of movement set it to the exact value.

Example:

while (dist.x < targetValue)
   MoveX();

dist.x = targetValue;

But as other point out you should be able to use Mathf.MoveTowards or Matfh.Lerp

Dave
  • 2,684
  • 2
  • 21
  • 38
  • I'm not sure what you mean. Can you be more precise? – Fledermauserl Sep 12 '19 at 11:58
  • I have edited my answer. Move your value in the desired direction and when you passed the target value, stop moving and set the value to the exact value you want. – Dave Sep 12 '19 at 12:08
  • You should be able to use MoveTowards, If you are getting strange behavior is because you are doing something wrong. – Dave Sep 12 '19 at 12:09
  • When moving your gears with the Move method you may move more than the epsilon precision of the `Mathf.Approximately` and "overshoot" the stop position. – Dave Sep 12 '19 at 12:28
  • Also as derHugo wrote why are using while loops instead of if statements? While loops in your code will run only once. – Dave Sep 12 '19 at 12:30
0

To extend on the comments, using yield break; will exit the current IEnumerator. This is probably part of your problem. Mathf.Approximately is a decent way to go, but might still lead to inconsistencies.

An alternative to seperately comparing the x, y and z is to compare against Vector3.Distance.

This is a simple movement snippet I've used.

while (Vector3.Distance(sourceTransform.position, targetTransform.position) > 0.1f)
{
     float speed = 1f;
     var step = speed * Time.deltaTime;
     sourceTransform.position = Vector3.MoveTowards(sourceTransform.position, targetTransform.position, step);

     yield return new WaitForEndOfFrame();
}

// Explicitly correct the position to the target when the treshold is passed.
sourceTransform.position = targetTransform.position;
Immorality
  • 2,164
  • 2
  • 12
  • 24
  • I can't use the whole distance because I explicitely need the distance only on the axis. and as I said, MoveTowards is a problem too, and that was before I even wrote the IEnumerator and there was no yield break; – Fledermauserl Sep 12 '19 at 11:56
  • and also, to check if something is bigger or smaller 0.1 is exactly what I do but not precise enough – Fledermauserl Sep 12 '19 at 12:00