#4 Confining the Draggable Behavior to a View Box
Monday, June 3, 2019
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.
Parts
- Part 4: Confine Using View Box
- Part 3: UI Camera
- Part 2: Follow Pointer Coroutine
- Part 1: Draggable Pointer Events
Pointer position relative to the canvas
- Assets
- Utilities
- UiUtilities.cs
- Utilities
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);
}
...
}
}
It's alive
- Assets
- UserInterface
- DraggableBehavior.cs
- UserInterface
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);
}
}
}
}
Confining the panel to the visible screen
- Assets
- UserInterface
- ViewBox.cs
- UserInterface
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;
}
}
}
Calling the view box
- Assets
- UserInterface
- DraggableBehavior.cs
- UserInterface
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;
}
}
...
}
}
Time to actually confine our panel
- Assets
- UserInterface
- ViewBox.cs
- UserInterface
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;
}
}
}