MongolianShaman
MongolianShaman

Reputation: 11

Can't move a gemeObject smoothly with MoveTowards or Lerp

I'm an extreme amateur with a very simple goal. Inside an OnGui i have a button. I want this button when pressed to move the gameObject it's attached to to move a distance to the right in a smooth motion. I've been successful in setting the distance i want to move to, but i cannot make the movement smooth to save my life.

    private void OnGUI()
{
    if (GUI.Button(new Rect(165, 300, 150, 350), "right"))
    {            
        var pos = transform.position;
        float rightMovement = pos.x + 0.5f;

      Vector3 targetPosition = new Vector3(rightMovement, transform.position.y, transform.position.z);

        while (Vector3.Distance(transform.position, targetPosition) > 0.1f)
        {
            float step = speed * Time.deltaTime;
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
        }
        Debug.Log("Clicked rightMovement");
    }

(Speed = 5f) This is what i felt like got me the closest to my goal. Currently it moves the distance, but just in a single frame. I also tried to use an IEnumerator that basically has the same code inside and just call said IEnumarator from inside the OnGui, but to no avail.

Upvotes: 0

Views: 451

Answers (1)

derHugo
derHugo

Reputation: 90639

The main issue is this loop

while (Vector3.Distance(transform.position, targetPosition) > 0.1f)
{
    float step = speed * Time.deltaTime;
    transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
}

This moves your object in one single step because no frame is rendered meanwhile. The while rather blocks the entire thread until it is done (which I this case is not that long) and then renders the frame when done.

Additionally as also mentioned in the comments OnGUI is called multiple times per frame so it gets even worse.


As I also commented OnGUI is kind of legacy and you shouldn't use it anymore except you know exactly how and what you are doing.

Rather use a UI.Button in a Canvas.


If I see it right you want to move while the button stays pressed.

Unfortunately there is no built-in Button for handling something like "while button stays pressed" so you'ld have to use the IPointerDownHandler and IPointerUpHandler interfaces.

Put this class on the UI.Button object

public class MoveButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler
{
    public enum Alignment
    {
        Global,
        Local
    }

    // Configure here the movement step per second in all three axis
    public Vector3 moveStep;

    // Here reference the object that shall be move by this button
    public Transform targetObject;

    // Configure here wether you want the movement in
    // - Global = World-Space
    // - Local = Object's Local-Space
    public Alignment align;

    private Button button;

    private void Awake ()
    {
        button = GetComponent<Button>();
    }

    //Detect current clicks on the GameObject (the one with the script attached)
    public void OnPointerDown(PointerEventData pointerEventData)
    {
        // Ignore if button is disabled
        if(!button.interactible) return;

        StartCoroutine (Move());
    }

    public void OnPointerUp(PointerEventData pointerEventData)
    {
        StopAllCoroutines();
    }

    public void OnPointerExit(PointerEventData pointerEventData)
    {
        StopAllCoroutines();
    }

    // Actually not really needed but I'm not sure right now
    // if it is required for OnPointerExit to work
    // E.g. PointerDown doesn't work if PointerUp is not present as well
    public void OnPointerEnter(PointerEventData pointerEventData)
    {

    }

    // And finally to the movement
    private IEnumerator Move()
    {
        // Whut? Looks dangerous but is ok as long as you yield somewhere
        while(true)
        {
            // Depending on the alignment move one step in the given direction and speed/second
            if(align == Alignment.Global)
            {
                targetObject.position += moveStep * Time.deltaTime;
            }
            else
            {
                targetObject.localPosition += moveStep * Time.deltaTime;
            }

            // Very important! This tells the routine to "pause"
            // render this frame and continue from here
            // in the next frame.
            // without this Unity freezes so careful ;)
            yield return null;
        }
    }
}

Alternatively you could stick to your code but separate the button from the movement like

private bool isPressed;

private void OnGUI()
{
    isPressed = GUI.Button(new Rect(165, 300, 150, 350), "right");
}

private void Update()
{  
    if(!isPressed) return;

    var pos = transform.position;
    var targetPosition = pos + Vector3.right * 0.5f;

    if (Vector3.Distance(transform.position, targetPosition) > 0.1f)
    {
        float step = speed * Time.deltaTime;
        transform.position = Vector3.MoveTowards(transform.position, targetPosition, step);
    }
}

Upvotes: 1

Related Questions