Information/Help with IDragHandler

I implemented a custom ViewPager, basically to scroll through different UI panels.
It works great but, it has absolutely horrific performance on mobile devices.
I use the three interfaces

  • IBeginDragHandler
  • IDragHandler
  • IEndDragHandler

The main calculation happens in IDragHandler, but it’s not as much calculation as you would expect it to get jiggerish. I only do some positioning stuff in there.
That’s why I was wondering how often the IDragHandler-method gets called, every time the pointer is moved, once every Update?

I hope you have some suggestions to that.
Thanks in advance!

its likely not the drag handler calls but the implementation you’ve put in the drag handler, or how you’ve set up the UI in the scene.

can you show the code you’re using in the drag call (preferbly the entire class). even simple postitioning can cause frame loss if not done properly.

is any of the objects in the Canvas Heirarchy not coplanar?
is the render mode on the canvas set to “screen space - camera”?
Do you have one monolithic canvas with a lot of child UI elements or several smaller, nested canvases?
does the canvas have a ton of child UI that move, change color, or change text?

all of these can force the UI system to clear the render buffer and start completely from scratch on the new frame. especially multiple moving UI elements on one canvas.

if the UI is not coplanar then that means the UI has depth, thus it has a perspective. At which point the UI system can’t assume that the perspective won’t change so it redraws that canvas

Ui rendering is mostly handled on a separate thread, but Camera Mode can throttle that as now the UI has to wait on the camera. world space isn’t much better (even if the ui hasn’t changed, new perspectives require a redraw, so the UI just assumes it always has to redraw in that mode)

splitting the UI into one canvas full of the static UI and multiple other canvases with dynamic UI can greatly improve the performance on the system as a whole (as now the UI will only redraw the tiny canvases that changed instead of redrawing everything).

finally when a Ui element changes, that element and everything drawn behind it (in that canvas) will need to be redrawn. which is why elements that constantly change appearance are best on their own canvases

So as you can see you can probably understand why simple positioning in code can cause your performance issues.

Thank you for your quick reply!

Everything is on the same Z-Axis, if this is what you mean!

I will try if this makes a performance improvement.

One parent Canvas, and only UI Panels as children.

That is a great suggestion, I’ll give it a try, too!

Finally this is my code:
Code

public class PanelPager: MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler {

    [Header("Threshold")]
    public float panelSwitchDeltaThreshold = 0.5f;
    public float panelSwitchSpeedThreshold = 5f;

    [Header("Appearance")]
    public float panelSwitchSpeed = 0.2f;
    public float lastPanelDamping = 0.1f;

    private List<RectTransform> panels;

    private int activePanel = 0;
    private Vector2 panelLeftPos, panelCenterPos, panelRightPos;
    private Vector2 dragStartPosition;
    private float transitionDelta = 0;

    private void Awake() {
        panels = new List<RectTransform>();

        panelLeftPos = new Vector2(-Screen.width, 0);
        panelCenterPos = new Vector2(0, 0);
        panelRightPos = new Vector2(Screen.width, 0);
    }

    public void AddPanel(RectTransform panel) {
        panels.Add(panel);
        panel.SetParent(transform);
    }

    public void InitializePanels() {
        for(int i = 0; i < panels.Count; i++) {
            panels[i].anchorMin = new Vector2(0, 0);
            panels[i].anchorMax = new Vector2(0, 0);
            panels[i].pivot = new Vector2(0, 0);
            panels[i].sizeDelta = new Vector2(Screen.width, Screen.height);
        }
    }

    public void OnBeginDrag(PointerEventData ped) {
        dragStartPosition = ped.position;
    }

    public void OnDrag(PointerEventData ped) {
        // Drag from RIGHT to LEFT
        if(dragStartPosition.x > ped.position.x) {

            transitionDelta = (dragStartPosition.x - ped.position.x) / Screen.width;
            transitionDelta = Mathf.Clamp01(transitionDelta);

            if(activePanel + 1 < panels.Count) {
                panels[activePanel].position = Vector2.Lerp(panelCenterPos, panelLeftPos, transitionDelta);
                panels[activePanel + 1].position = Vector2.Lerp(panelRightPos, panelCenterPos, transitionDelta);
            } else {
                transitionDelta = Mathf.Sin(transitionDelta * Mathf.PI * 0.5f);
                panels[activePanel].position = Vector2.Lerp(panelCenterPos, panelLeftPos * lastPanelDamping, transitionDelta);
            }
        }

        // Drag from LEFT to RIGHT
        if(dragStartPosition.x < ped.position.x) {

            transitionDelta = 1 - (1 + (dragStartPosition.x - ped.position.x) / Screen.width);
            transitionDelta = Mathf.Clamp01(transitionDelta);

            if(activePanel > 0) {
                panels[activePanel].position = Vector2.Lerp(panelCenterPos, panelRightPos, transitionDelta);
                panels[activePanel - 1].position = Vector2.Lerp(panelLeftPos, panelCenterPos, transitionDelta);
            } else {
                transitionDelta = Mathf.Sin(transitionDelta * Mathf.PI * 0.5f);
                panels[activePanel].position = Vector2.Lerp(panelCenterPos, panelRightPos * lastPanelDamping, transitionDelta);
            }
        }
    }

    public void OnEndDrag(PointerEventData ped) {
        // Drag from RIGHT to LEFT
        if(dragStartPosition.x > ped.position.x) {
            if(activePanel + 1 < panels.Count) {
                if(ped.delta.magnitude > panelSwitchSpeedThreshold) {
                    transitionDelta = 1;
                }

                if(transitionDelta > panelSwitchDeltaThreshold) {
                    activePanel++;

                    StartCoroutine(PanelTransition(activePanel, panelCenterPos));
                    StartCoroutine(PanelTransition(activePanel - 1, panelLeftPos));

                } else {
                    StartCoroutine(PanelTransition(activePanel, panelCenterPos));
                    StartCoroutine(PanelTransition(activePanel + 1, panelRightPos));
                }
            } else {
                StartCoroutine(PanelTransition(activePanel, panelCenterPos));
            }
        }

        // Drag from LEFT to RIGHT
        if(dragStartPosition.x < ped.position.x) {
            if(activePanel > 0) {

                if(ped.delta.magnitude > panelSwitchSpeedThreshold) {
                    transitionDelta = 1;
                }

                if(transitionDelta > panelSwitchDeltaThreshold) {
                    activePanel--;
                    StartCoroutine(PanelTransition(activePanel, panelCenterPos));
                    StartCoroutine(PanelTransition(activePanel + 1, panelRightPos));
                } else {
                    StartCoroutine(PanelTransition(activePanel, panelCenterPos));
                    StartCoroutine(PanelTransition(activePanel - 1, panelLeftPos));
                }
            } else {
                StartCoroutine(PanelTransition(activePanel, panelCenterPos));
            }
        }
    }

    public void ShowPage(int panelIndex, bool instant = false) {
        for(int i = 0; i < panels.Count; i++) {
            panels[i].position = panelRightPos;
        }

        if(instant) {
            panels[panelIndex].position = panelCenterPos;
            if(pagerIndicator) {
                pagerIndicator.SetActiveIndicator(panelIndex);
            }
        } else {
            if(activePanel < panelIndex) {
                StartCoroutine(PanelTransition(activePanel, panelLeftPos));
                StartCoroutine(PanelTransition(panelIndex, panelCenterPos));
            } else {
                panels[panelIndex].position = panelLeftPos;
                StartCoroutine(PanelTransition(activePanel, panelRightPos));
                StartCoroutine(PanelTransition(panelIndex, panelCenterPos));
            }
        }

        activePanel = panelIndex;
    }

    private IEnumerator PanelTransition(int panelIndex, Vector2 panelPos) {
        float t = 0;
        float cTime = 0;
        float tTime = panelSwitchSpeed;

        Vector2 panelStartPositionOne = panels[panelIndex].position;

        while(t < 1) {
            cTime += Time.deltaTime;

            if(cTime >= tTime) {
                cTime = tTime;
            }

            t = cTime / tTime;
            t = Mathf.Sin(t * Mathf.PI * 0.5f);

            panels[panelIndex].position = Vector2.Lerp(panelStartPositionOne, panelPos, t);

            yield return null;
        }
    }
}

Thanks again, and in the meanwhile I give it a try!

Great. And about the Render mode, “Screenspace - Overlay” is the fastest one I was just asking if you were using screenspace camera as that can slow it down.

and yes with how you have your UI set up this code can cause a performance hit