Moving from A to B at CONSTANT speed doesn't work

Hi everyone!
I got this problem that I am trying to solve for a few days now.

I have a game in which you have to click an object that is tagged “Movable”… but only click, not hold!
Since it should be about clicking only once and not holding, I am trying to solve my problem using Coroutine combined with Vector3.MoveTowards, however it doesn’t even move!
I have tried a few variations now, but I can’t make this GameObject to move no matter what.

On clicking the object it should store itself as a GameObject:

active = hit.transform.gameObject;

…and make target positions out of it depending on which key is pressed(w,a,s,d - we all know the directions of those):

Storing GameObject position:

x = active.transform.position.x;
y = active.transform.position.y;
z = active.transform.position.z;

… making target positions:

left = x + distanceStep;
right = x - distanceStep;
forward = z - distanceStep;
back = z + distanceStep;

So all in all:

  1. the object has to move on single click and at constant speed smoothly
  2. while moving - can’t do anything else!
  3. move exactly 4 units as it is defined in distance variable

Maybe I am still lacking some knowledge, but I am short on time and I have to solve this problem for my game to finally work.
It is becoming extremely frustrating.

Please help me with this because I have tried to read all of the official scripting documentation and yet here I am having this, what I believe to be a primitive problem.

And please keep the solution somewhat simple if it is possible since I am still something between Beginner and Intermediate level programmer, but I am slowly getting better!

Here is a full code:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour{

    private GameObject active;
    private bool moving;
    private float force, speed, distanceStep;
    private float x, y, z;
    private float left, right, forward, back;
    private Vector3 position;
    private Vector3 goLeft, goRight, goForward, goBack;
    private Vector3 cursor;

    void Start()
    {
        distanceStep = 4.0f;
        force = 1.0f;
        speed = force * Time.deltaTime;
    }

    void Update()
    {
        RaycastHit hit;
        cursor = Input.mousePosition;
        Ray ray = Camera.main.ScreenPointToRay(cursor);

        if (Physics.Raycast(ray, out hit))
        {
            Debug.Log("HITTING ALL");
            if (hit.collider.tag == "Movable")
            {
                Debug.Log("HITTING TAGGED");
                if (Input.GetButtonDown("Fire1"))
                {
                    Debug.Log("CLICKING TAGGED");

                    active = hit.transform.gameObject;
                    moving = true;

                    x = active.transform.position.x;
                    y = active.transform.position.y;
                    z = active.transform.position.z;

                    position = new Vector3(x, y, z);

                    left = x + distanceStep;
                    right = x - distanceStep;
                    forward = z - distanceStep;
                    back = z + distanceStep;

                    goLeft = new Vector3(left, y, z);
                    goRight = new Vector3(right, y, z);
                    goForward = new Vector3(x, y, forward);
                    goBack = new Vector3(x, y, back);
                }
            }
        }

        if (moving)
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                Debug.Log("going left");
                StartCoroutine(move(goLeft));
            }
            else if (Input.GetKeyDown(KeyCode.D))
            {
                Debug.Log("going right");
                StartCoroutine(move(goRight));
            }
            else if (Input.GetKeyDown(KeyCode.W))
            {
                Debug.Log("going forward");
                StartCoroutine(move(goForward));
            }
            else if (Input.GetKeyDown(KeyCode.S))
            {
                Debug.Log("going back");
                StartCoroutine(move(goBack));
            }
        }
    }

    IEnumerator move(Vector3 target)
    {
        float distance = Vector3.Distance(position, target);

        if(distance > 0)
        {
            position = Vector3.MoveTowards(position, target, speed);
            moving = false;
            yield return null;
        }
        moving = true;
    }
}

One quick note you should switch the order of your if statements:
if (Input.GetButtonDown())
{
// Raycast code in here
if (hit != null)
}

This way your only doing a raycast when the button is pressed instead of every single update.

Your explanation and code make what you want to do a little vague:
So I mouseClick on the object that is moveable. Does it start moving immediately and just waits for me to hit wasd to tell it what way to go until its moved X distance? Or does it stand still till i hit one direction (wasd) and moves only that direction for X distance?

The second option.

I think your main misconception is in the way MoveTowards works. MoveTowards is instantaneous: If you feed it (vector1, target, 4), it returns a vector that is 4 units closer to target, starting from vector1 - and that’s it.

With that in mind, your coroutine is really doing not much of anything at all - literally just moving instantly in 1 frame. Try this instead:

IEnumerator move(Vector3 target)
    {
while (true) {
        float distance = Vector3.Distance(position, target);

        if(distance > 0)
        {
            position = Vector3.MoveTowards(position, target, speed * Time.deltaTime);
            yield return null;
        }
        else {
break; //break out of the while loop when distance is 0
}
}
    }

By putting the yield inside a loop, you make this action take place over a number of frames; and by multiplying speed by Time.deltaTime, your guy will move by (speed) units per second - over the course of one frame at a time.

Doesn’t seem to fix the problem, furthemore now it seems it’s not registering key inputs… clicking on objects debugs it but pressing arrows not.

Probably need to add the “moving” variable changes back into that function - I did not copy those over.

But I added them already while reconstructing coroutine with your idea.
Also that unresponsive input was my bad pressing arrow keys instead of wasd…
But it still doesn’t work

Have the move function do this:

 Debug.Log ("Object moving from "+transform.position+" to "+target);

What does it output?

I think you mean position and not transform.position because we need to refer to clicked GameObject position not cameras - this script is attached to camera

Output when clicking only D for right side:

I can’t even print it here for it prints 201 times before breaking out of loop when arriving at target position

PROBLEM is here - the values are literally moving from (8, 2 ,-8) to (4, 2, -8) on x axis as it is supposed to be but object is not moving !!!

It is as if the object position is not updated with those values.

Here is an approach that Queues up each time they click an object. Then when they finally press wasd pops off the first clicked item and starts moving it. This code has one script in charge of both clicking objects and then moving them. If this is a 2D game you might have to change .forward/back to .up/down i’m not sure.
Basic ClickMove Script

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour
{
    private Queue<GameObject> clickedGO;
    private float force, speed, distanceStep;

    void Awake()
    {
        clickedGO = new Queue<GameObject>();
    }
    void Start()
    {
        distanceStep = 4.0f;
        speed = 1.0f;
    }

    void Update()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            RaycastHit hit;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.tag == "Movable")
                {
                    if (!clickedGO.Contains(hit.collider.gameObject)
                         clickedGO.Enqueue(hit.collider.gameObject);
                }
            }
        }

        if (clickedGO.Count > 0)
        {
 
            if (Input.GetKeyDown(KeyCode.A))
            {
                StartCoroutine(move(Vector3.left));
            }
            else if (Input.GetKeyDown(KeyCode.D))
            {
                StartCoroutine(move(Vector3.right));
            }
            else if (Input.GetKeyDown(KeyCode.W))
            {            
                StartCoroutine(move(Vector3.forward));
            }
            else if (Input.GetKeyDown(KeyCode.S))
            {
                StartCoroutine(move(Vector3.back));
            }
        }
    }

    IEnumerator move(Vector3 direction)
    {
        float distance = 0;
        GameObject moveable = clickedGO.Dequeue();
        while (distance < distanceStep)
        {
            distance += speed + Time.deltaTime;
            moveable.transform.position += direction * speed * Time.deltaTime;
            yield return null;
        }
    }
}

However, if you use interfaces and a component design system, you can achieve the same results with greater ability to extend your code easily later:

First Create a simple interface script. Just have it in your project folder.
Interface IClickMove

public interface IClickMove
{
     void SetActive()
}

Change the base script above to this:
Note: we no longer need tags on our objects we are making that can be clicked.
New ClickMove Script

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour
{
    void Update()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            RaycastHit hit;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out hit))
            {
                IClickMove clickedObject = hit.collider.gameObject.GetComponent<IClickMove>();
                // if its null we clicked on an object we can't move
                if (clickedObjecdt != null)
                    clickedObject.SetActive();
            }
        }
    }
}

Now add this script to your objects being created (the ones that use to have Moveable tag)
BasicClickable

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BasicClickable : MonoBehaviour, IClickMove
{
    // Set these in the inspector
    public float distanceStep;
    public float speed;

    private bool isActive = false;
    private bool waitingForInput = false;

 
    // if we get clicked on and already in the middle
    // of moving ignore it
    public void SetActive()
    {
        if (isActive == true)
            return;
        else
        {
            isActive = true;
            waitingForInput = true;
        }
    }

    private void Update()
    {
        if (!isActive)
           return;
        if (waitingForInput)
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                StartCoroutine(move(Vector3.left));
            }
            else if (Input.GetKeyDown(KeyCode.D))
            {
                StartCoroutine(move(Vector3.right));
            }
            else if (Input.GetKeyDown(KeyCode.W))
            {
                StartCoroutine(move(Vector3.forward));
            }
            else if (Input.GetKeyDown(KeyCode.S))
            {
                StartCoroutine(move(Vector3.back));
            }
        }
    }

    IEnumerator move(Vector3 direction)
    {
        waitingForInput = false;
        float distance = 0;
        while (distance < distanceStep)
        {
            distance += speed + Time.deltaTime;
            transform.position += direction * speed * Time.deltaTime;
            yield return null;
        }
        isActive = false;
    }
}

Now this second set of code is identical to the first one script version. However, we can now make all kinds of Clickable objects and our script that is clicking on them won’t care. It just cares that what we clicked on is an IClickMove. For example here is clickable object that moves forward with us until we press Z then it explodes
Exploding Clickable

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BuddyClickable : MonoBehaviour, IClickMove
{
    // Set these in the inspector
    public float speed;

    private bool isActive = false;

    // if we get clicked on and already in the middle
    // of moving ignore it
    public void SetActive()
    {
        if (isActive == true)
            return;
        else
        {
            isActive = true;
        }
    }

    private void Update()
    {
           if (!isActive)
                 return;
        if (Input.GetKeyDown(KeyCode.Z))
        {
            Explode();
        }
        else
        {
            transform.position += Vector3.forward * Time.deltaTime * speed;
        }

    }

    Explode()
    {
        // Do Some Explode code here to blow up things
        Destroy(gameObject);
    }
}

So you can start making all kinds of clickable objects that look different and the user uses them differrently after they click on them. You could even make objects that don’t ignore being clicked on in the middle of movement. Maybe it resets them?

And how does “position” get applied back to the transform’s position? If it doesn’t then any movement will never be seen.

Although this solution seems to be more complicated for me to understand, I understand that it is useful for variety of objects that have different move speed etc.

I need this move script only for 1 object, but thanks anyway I will look into it when I will need such variety

Btw:

position is a variable that stores clicked GameObject position
transform.position refers to position of camera - this whole script is attached to camera

And back to my reply before … it seems Vector3 values are moving smoothly in coroutine… it’s just that those values are not applied to GameObject it seems and that’s why it doesnt’s move

Yes, it stores the position, but you never do (for example) active.transform.position = position. You’re lerping your local variable nicely, but the GameObject you got it from has no way of knowing that.

So, I did something so it finally moves however… it moves way too badly:
Try it because I don’t know now how to fix this…

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour{

    private GameObject active;
    private bool moving;
    private float force, speed, distanceStep;
    private float x, y, z;
    private float left, right, forward, back;
    private Vector3 position;
    private Vector3 goLeft, goRight, goForward, goBack;
    private Vector3 cursor;

    void Start()
    {
        distanceStep = 4.0f;
        force = 1.0f;
        speed = force * Time.deltaTime;
    }

    void Update()
    {
        RaycastHit hit;
        cursor = Input.mousePosition;
        Ray ray = Camera.main.ScreenPointToRay(cursor);

        if (Physics.Raycast(ray, out hit))
        {
            //Debug.Log("HITTING ALL");
            if (hit.collider.tag == "Movable")
            {
                //Debug.Log("HITTING TAGGED");
                if (Input.GetButtonDown("Fire1"))
                {
                    Debug.Log("CLICKING TAGGED");

                    active = hit.transform.gameObject;
                    moving = true;

                    x = active.transform.position.x;
                    y = active.transform.position.y;
                    z = active.transform.position.z;

                    position = new Vector3(x, y, z);

                    left = x + distanceStep;
                    right = x - distanceStep;
                    forward = z - distanceStep;
                    back = z + distanceStep;

                    goLeft = new Vector3(left, y, z);
                    goRight = new Vector3(right, y, z);
                    goForward = new Vector3(x, y, forward);
                    goBack = new Vector3(x, y, back);
                }
            }
        }

        if (moving)
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                Debug.Log("going left");
                StartCoroutine(move(goLeft));
            }
            else if (Input.GetKeyDown(KeyCode.D))
            {
                Debug.Log("going right");
                StartCoroutine(move(goRight));
            }
            else if (Input.GetKeyDown(KeyCode.W))
            {
                Debug.Log("going forward");
                StartCoroutine(move(goForward));
            }
            else if (Input.GetKeyDown(KeyCode.S))
            {
                Debug.Log("going back");
                StartCoroutine(move(goBack));
            }
        }
    }

    IEnumerator move(Vector3 target)
    {
        while(true)
        {
            float distance = Vector3.Distance(position, target);

            if (distance > 0.0f)
            {
                Debug.Log("Object moving from " + position + " to " + target);
                position = Vector3.MoveTowards(active.transform.position, target, speed);
                active.transform.position = position;
                moving = false;
                yield return null;
            }
            else
            {
                moving = true;
                break;
            }
         
        }
    }
}

Try moving it in different directions

I think it has to do something with target positions defined in Update but this is so confusing that I don’t know solution

ALRIGHT, I FIXED IT AND IT WORKS !!!

I copied this:

x = active.transform.position.x;
                    y = active.transform.position.y;
                    z = active.transform.position.z;
                    position = new Vector3(x, y, z);
                    left = x + distanceStep;
                    right = x - distanceStep;
                    forward = z - distanceStep;
                    back = z + distanceStep;
                    goLeft = new Vector3(left, y, z);
                    goRight = new Vector3(right, y, z);
                    goForward = new Vector3(x, y, forward);
                    goBack = new Vector3(x, y, back);

to coroutine as well and it updates target postions as well!