Advertisement

#4 Confining the Draggable Behavior to a View Box

In this article we will finally be able to drag our panel around the screen. Once we are able to do that we will create the ability to confine the panel either to the entire screen or a specified portion of it using a view box.

Pointer position relative to the canvas

  • Assets
    • Utilities
      • UiUtilities.cs

In order to move our panel around we only need to determine one more position. That position is the position of the pointer relative to the canvas. Of course with the work we have already done previously it is a very simple matter to add an additional extension method to make finding this position very easy (a).

UiUtilities.cs

...

namespace Utilities
{
    public static class UiUtilities
    {
        ...
        public static Vector2 PointerPositionInCanvas(this Canvas canvas)
        {
            return ((RectTransform)canvas.transform).PointerPositionInTransform(canvas.worldCamera);
        }
        ...
    }
}
(a) Adding an additional extension method that makes it easy to find the pointer position relative to a given canvas.

It's alive

  • Assets
    • UserInterface
      • DraggableBehavior.cs

Once we have a reference to our canvas we just need to slightly modify our follow pointer coroutine and we can start dragging our panel around the screen (b).

DraggableBehavior.cs

...
namespace UserInterface
{
    public sealed class DraggableBehavior : MonoBehaviour
    {
        ...
        private Canvas _canvas;
        ...
        private Canvas Canvas
        {
            get
            {
                if (_canvas == null)
                {
                    _canvas = transform.FindFirstComponentInAncestor<Canvas>();
                }

                return _canvas;
            }
        }
        ...
        private IEnumerator FollowPointer(Vector2 position)
        {
            while (true)
            {
                if (IsBeingDragged == false)
                {
                    yield break;
                }

                RectTransform.anchoredPosition = Canvas.PointerPositionInCanvas() - position;

                yield return _followPointerWaitForSeconds;
            }
        }
        ...
        private void OnPointerDown(BaseEventData data)
        {
            Debug.Log($"pointer down: {data}"); // <-- Time to remove this

            IsBeingDragged = true;

            var pos = RectTransform.PointerPositionInTransform(((PointerEventData) data).pressEventCamera);
            _followPointerCoroutine = StartCoroutine(FollowPointer(pos));
        }

        private void OnPointerUp(BaseEventData data)
        {
            Debug.Log($"pointer up: {data}"); // <-- Time to remove this

            IsBeingDragged = false;

            if (_followPointerCoroutine != null)
            {
                StopCoroutine(_followPointerCoroutine);
            }
        }
    }
}
(b) Modifying our follow pointer coroutine to make use of the pointer position in the canvas allows us to change the anchored position of our panel.

Confining the panel to the visible screen

  • Assets
    • UserInterface
      • ViewBox.cs

As it stands now we are able to drag our panel around but we have not placed any constraints on this movement which means we can drag at least a good portion of it off of the visible screen. In order to be able to correct this we will create a new script (c). For the time being we will just apply the anchored position that we receive to our transform.

ViewBox.cs

using UnityEngine;

namespace UserInterface
{
    public sealed class ViewBox : MonoBehaviour
    {
        private RectTransform RectTransform => (RectTransform)transform;

        public void ConfineAnchoredPositionToViewBox(Vector2 anchoredPosition)
        {
            RectTransform.anchoredPosition = anchoredPosition;
        }
    }
}
(c) Our initial view box script just applies the provided anchored position to our transform.

Calling the view box

  • Assets
    • UserInterface
      • DraggableBehavior.cs

Once again we will use our extension methods in order to get the view box component and if one does not exist on our game object we will create it and in our follow pointer coroutine we will make use of it (d).

DraggableBehavior.cs

...
namespace UserInterface
{
    public sealed class DraggableBehavior : MonoBehaviour
    {
        ...
        private ViewBox _viewBox;
        ...
        private ViewBox ViewBox
        {
            get
            {
                if (_viewBox == null)
                {
                    _viewBox = Handle.GetOrCreateComponent<ViewBox>();
                }

                return _viewBox;
            }
        }
        ...
        private IEnumerator FollowPointer(Vector2 position)
        {
            while (true)
            {
                if (IsBeingDragged == false)
                {
                    yield break;
                }

                ViewBox.ConfineAnchoredPositionToViewBox(Canvas.PointerPositionInCanvas() - position);

                yield return _followPointerWaitForSeconds;
            }
        }
        ...
    }
}
(d) Calling the confine method of the view box in our follow pointer coroutine.
Advertisement

Time to actually confine our panel

  • Assets
    • UserInterface
      • ViewBox.cs

Confining our panel to view box starts with first defining the edges of the view box (e). We will set those edges based on the top left and bottom right corners that can be set in the inspector. If a component of the corners is greater than one we assume it to be a pixel value, if that is not the case if it is greater than zero we will assume it to be a percentage, and if that is not the case we will just assume it to be zero. Once we have the edges we can determine the maximum distance that the panel can be moved relative to the center of the screen and its height and width. If given anchored position would move the panel beyond the edges we will just set it to the maximum amount that would keep it within the view box.

ViewBox.cs

...
namespace UserInterface
{
    public sealed class ViewBox : MonoBehaviour
    {
        public Vector2 bottomRightCorner;
        public Vector2 topLeftCorner;

        public float BottomEdge
        {
            get
            {
                if (bottomRightCorner.y > 1)
                {
                    return bottomRightCorner.y;
                }

                if (bottomRightCorner.y >= 0)
                {
                    return Screen.height * bottomRightCorner.y;
                }

                return 0;
            }
        }

        public float LeftEdge
        {
            get
            {
                if (topLeftCorner.x > 1)
                {
                    return topLeftCorner.x;
                }

                if (topLeftCorner.x >= 0)
                {
                    return topLeftCorner.x * Screen.width;
                }

                return 0;
            }
        }

        ...

        public float RightEdge
        {
            get
            {
                if (bottomRightCorner.x > 1)
                {
                    return Screen.width - bottomRightCorner.x;
                }

                if (bottomRightCorner.x >= 0)
                {
                    return Screen.width * (1 - bottomRightCorner.x);
                }

                return 0;
            }
        }

        public float TopEdge
        {
            get
            {
                if (topLeftCorner.y > 1)
                {
                    return Screen.height - topLeftCorner.y;
                }

                if (topLeftCorner.y >= 0)
                {
                    return Screen.height * (1 - topLeftCorner.y);
                }

                return 0;
            }
        }

        public void ConfineAnchoredPositionToViewBox(Vector2 anchoredPosition)
        {
            var halfHeightScreen = 0.5f * Screen.height;
            var halfHeightTransform = 0.5f * RectTransform.rect.height;
            var halfWidthScreen = 0.5f * Screen.width;
            var halfWidthTransform = 0.5f * RectTransform.rect.width;

            var bottomFromCenter = BottomEdge - halfHeightScreen;
            var leftFromCenter = LeftEdge - halfWidthScreen;
            var rightFromCenter = RightEdge - halfWidthScreen;
            var topFromCenter = TopEdge - halfHeightScreen;

            var maxWindowCenterPositiveY = anchoredPosition.y + halfHeightTransform;
            if (maxWindowCenterPositiveY > topFromCenter)
            {
                anchoredPosition.y = topFromCenter - halfHeightTransform;
            }

            var minWindowCenterNegativeY = anchoredPosition.y - halfHeightTransform;
            if (minWindowCenterNegativeY < bottomFromCenter)
            {
                anchoredPosition.y = bottomFromCenter + halfHeightTransform;
            }

            var maxWindowCenterPositiveX = anchoredPosition.x + halfWidthTransform;
            if (maxWindowCenterPositiveX > rightFromCenter)
            {
                anchoredPosition.x = rightFromCenter - halfWidthTransform;
            }

            var minWindowCenterNegativeX = anchoredPosition.x - halfWidthTransform;
            if (minWindowCenterNegativeX < leftFromCenter)
            {
                anchoredPosition.x = leftFromCenter + halfWidthTransform;
            }

            RectTransform.anchoredPosition = anchoredPosition;
        }
    }
}
(e) Creating the code that will keep our panel within the prescribed view box dimensions.
Exciton Interactive LLC
Advertisement