OnPointerEnter/exit and lerp.

I am a bit rust with Unity - haven’t used it in a while.

I was wonder how can I get OnPointerEnter/Exit to work with lerp because lerp needs a loop but the pointer events are only called once.
Here is my current code:

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

public class HoverEffect : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {

    Vector3 trans;
    public int ShiftAmount;
    private RectTransform rctTrans;

    void Start()
    {
        rctTrans = this.GetComponent<RectTransform>();
    }

    public void OnPointerEnter(PointerEventData eventData)
     {
         trans = rctTrans.anchoredPosition;
         //(trans.x < 0) ? trans.x - ShiftAmount : trans.x + ShiftAmount
         rctTrans.anchoredPosition = Vector3.Lerp(new Vector3(trans.x, trans.y, 0), new Vector3(trans.x - ShiftAmount, trans.y, 0), 1f);
     }
     public void OnPointerExit(PointerEventData eventData)
     {
         trans = rctTrans.anchoredPosition;
         //(trans.x < 0) ? trans.x + ShiftAmount : trans.x - ShiftAmount
         rctTrans.anchoredPosition = Vector3.Lerp(new Vector3(trans.x, trans.y, 0), new Vector3(trans.x + ShiftAmount, trans.y, 0), 1f);
     }
}

I have tried using coroutines and also the simple update(), but I couldn’t get either to work.
How can I do this?

Thanks,
Dream

What effect exactly is it you are going for? I see what your code does, but it should result in a one-time aprupt motion for both events, so i’m not entirely sure what it is you want to do.
You are right however, that both Enter and Exit only get called once, so to trigger something like an animation, you need to start something that runs over multiple frames. You could either do this in Update, making Enter and Exit just trigger some bools and having Update handle the rest, or you should also be able to use Coroutines.

What did you try in both cases; Update and Coroutines, and what problems did you have with them?

create a bool and set it to true in pointerEnter and set it to false in pointerExit, In update method Lerp based on the loop

bool onPointerEnter = false;

update(){
if(onPointerEnter){
//Do your Lerp
}else{
//Do what ever you want to
}
}

public void OnPointerEnter(PointerEvenetData eventData){
onPointerEnter = true;
}

public void OnPointerExit(PointerEvenetData eventData){
onPointerEnter = false;
}
1 Like

It does result in that motion which is correct, however, I wanted it to be smooth. The only reason I have this is to add a bit more of “complete” main menu, so when you hover over the buttons, they shift slightly to the right.

I did try with coroutines, however, I need a while loop of some kind to repeat the lerps, but I don’t have anything I can use as the expression. It would work if I could just go ‘while(!.isFinished())’ or a variation of that which would allow me to run a while loop until the lerp is over.

As for using update, this is almost exactly what I tried, however, the else in the if statement will be called all the time (until it’s true, obviously) which when used with my lerp statements, means the button moves backwards off the screen because it is constantly calling the lerp and moving then button.
I then tried but setting another variable to equal ShiftAmount only when the button is hovered over (and setting it to 0 when the cursor exits) to stop it from moving the button, but this didn’t work either.

Thanks for the replies,
Dream

Using the Update() / Coroutine approach you need to work with the passed time to get a statement similar to “lerp.isFinished”. However, now that you explained what you want… there is a way simpler approach.

Did you try using OnMouseOver or something similar? It should be called each frame the mouse is over the button. So you should be able to animate it whenever the mouse hovers over the button, which seems to be what you want.

That’s what I was going for originally but from some googling it doesn’t seem like OnMouseOver works on UI elements.

Ok you are right, my bad. Then again, you could fake it by introducing a bool “mouseOver” which you set true on Enter, and false on Exit. Then simply work with “mouseOver” in Update, which is only true when the mouse is hovering over that button. In there, for example move the button position to the right until it hits some cap. When not mouseOver, move it into the other direction until it’s back to where it should be.

cmyd had the correct approach. you use pointerenter/exit to set a flag and then Update to move to 1 of 2 locations based on that flag. The reason it doesn’t appear to work for you is cause you were specifying a target location based on its current location. instead store a starting location (normal position), and then a target location(hover position) that is offset from the starting location, not current location.

if pointerIsInside == true, lerp currentPosition to hoverPosition, else lerp currentPosition to startingPosition.

I tried instead using coroutines and have this:

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

public class HoverEffect : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {

    Vector3 trans;
    public int ShiftAmount;
    private RectTransform rctTrans;
    private bool hover = false;

    void Start()
    {
        rctTrans = this.GetComponent<RectTransform>();
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        //(trans.x < 0) ? trans.x - ShiftAmount : trans.x + ShiftAmount
        hover = true;
        StartCoroutine("move");
    }
     public void OnPointerExit(PointerEventData eventData)
    {
        //(trans.x < 0) ? trans.x + ShiftAmount : trans.x - ShiftAmount
        hover = false;
        StartCoroutine("move");
    }

    IEnumerator move()
    {
        trans = rctTrans.anchoredPosition;
        float t = 0.0f;
        if(hover)
        {   
            while(t < 1.0)
            {
                t += Time.deltaTime;
                 rctTrans.anchoredPosition = Vector3.Lerp(new Vector3(trans.x, trans.y, 0), new Vector3(trans.x - ShiftAmount, trans.y, 0), t);
            }
        }
        else if (!hover)
        {
            while(t < 1.0)
            {
                t += Time.deltaTime;
                rctTrans.anchoredPosition = Vector3.Lerp(new Vector3(trans.x, trans.y, 0), new Vector3(trans.x + ShiftAmount, trans.y, 0), t);
            }
        }
        yield return null;
    }
}

However, it is still an instant jump. By the looks of things, (if I increase the cap to a hight value), the while loop freees the program until it has finished, which is why it looks like an instant jump.

void Start()
    {
        rctTrans = this.GetComponent<RectTransform>();

        start_pos = rctTrans.anchoredPosition;
        end_pos = new Vector3(rctTrans.anchoredPosition.x + shift_amount, rctTrans.anchoredPosition.y, 0);
    }

    void Update()
    {
        if(hover)
        {
            print("forward");
            rctTrans.anchoredPosition = Vector3.Lerp(start_pos, end_pos, 0.3f);
        }
        else if(!hover)
        {
            print("backward");
            rctTrans.anchoredPosition = Vector3.Lerp(end_pos, start_pos, 0.3f);
        }
    }

Quickly added what was suggested. It doesn’t move the button though.

Let me understand you, you want to move a button when your mouse is over it and when it’s not you want it to move back? is that right?

I honestly never used Coroutines and maybe never will, but to my understanding, since they are executed on the Mainthread, unless you make the Coroutine wait/sleep some time inbetween, all of it is executed sequentially. This explains both; the freezing, as well as the lack of fluid motion, since it’s all executed between 2 frames.
If you dont feel comfortable with Coroutines, i suggest following the Update() approach, which is probably a bit easier to think about.

Yes that’s what he wants.

The code I posted should do what he wants, There are two more problems I see in his code, First he isn’t using Time.deltaTime or Time.fixedDeltaTime which will smoother out the motion, Second he is calling the coroutines via reflection which is a very very bad practice, he should’ve used StartCoroutine(move()) instead

I said to lerp from the buttons CURRENT position, as Current->Target (where target is either start or end depending on hover state), not start->end or end->start.
this is the correct way to implement it:

public class HoverEffect : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{

    Vector3 trans;
    public int shift_amount;
    private RectTransform rctTrans;
    private bool hover = false;
    private Vector2 start_pos, end_pos;


    void Start()
    {
        rctTrans = this.GetComponent<RectTransform>();

        start_pos = rctTrans.anchoredPosition;
        end_pos = new Vector3(rctTrans.anchoredPosition.x + shift_amount,rctTrans.anchoredPosition.y,0);
    }

    void Update()
    {
        var currentPosition = rctTrans.anchoredPosition;
        var targetPosition = hover
            ? end_pos
            : start_pos;

        rctTrans.anchoredPosition = Vector3.Lerp(currentPosition,targetPosition,0.3f* Time.smoothDeltaTime);
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        hover = true;
    }
    public void OnPointerExit(PointerEventData eventData)
    {
        hover = false;
    }
}

I’m not great with coroutines but whether I am or not, the update approach is much easier.

That’s my bad. I looked up the Unity docs on coroutines just to refresh myself, and that’s how they have it done. My bad.

Thanks for the code. I understand the changes you have made, and it works very well!

Thanks all,
Dream

It makes more sense to use Update instead of Coroutines here. Coroutines are best used in the cases where the behavior is a sequence of actions over several frames that change and/or is a finite sequence. Update is cleaner when you are always doing the same generic action (like lerping) on every frame.

Examples in Unity docs typically take the simplest route to show an example, not always the best practice way of doing them. Documentation is usually highly focused on just the content itself. While best practices typically require more generic knowledge, understanding, and workflow that would be unrelated to a documentation’s page, and so are usually written in things like Unity Blogs or tutorials.