Advertisement

#2 Follow Pointer Coroutine

In this article we will focus on creating the code for a coroutine that we can use to allow our gameobject to follow the users cursor around when it is being dragged. To facilitate this we will see how to determine where the pointer is relative to the rect transform of the object that will be dragging around the screen.

Getting or creating a component

  • Assets
    • Utilities
      • ComponentUtilities.cs

As I have mentioned previously my general attitude when creating code that others will use, including myself, I like to make it work with as little configuration as possible. To that end as we saw in the last video we checked to see if an event trigger was present on our gameobject and if it was not we created one. This pattern is a very common one and as such our first order of business is to make it easier to do. As you may have guess it is time for another extension method (a).

ComponentUtilities.cs

using UnityEngine;

namespace Utilities
{
    public static class ComponentUtilities
    {
        public static T GetOrCreateComponent<T>(this Transform transform)
            where T : Component
        {
            var component = transform.GetComponent<T>();
            if (component == null)
            {
                component = transform.gameObject.AddComponent<T>();
            }

            return component;
        }
    }
}
(a) Extension method that we can use to get a component if it exists and if it does not it will be automatically created for us.

Cleaning up

  • Assets
    • UserInterface
      • DraggableBehavior.cs

With our new extension method in our pocket we can update our draggable behavior to make use of it (b).

DraggableBehavior.cs

...
namespace UserInterface
{
    public sealed class DraggableBehavior : MonoBehaviour
    {
        ...
        private void ConfigureEventHandling()
        {
            Handle.GetOrCreateComponent<EventTrigger>()
                .AddTrigger(EventTriggerType.PointerDown, OnPointerDown)
                .AddTrigger(EventTriggerType.PointerUp, OnPointerUp);
        }
        ...
    }
}
(b) Using our new extension method makes it a lot cleaner and therefore more readable for us to get or create a component.

Follow that pointer

  • Assets
    • UserInterface
      • DraggableBehavior.cs

Unity offers us a couple of different ways to make sure that some action is performed on a repeated basis. There are a few different methods that we could take advantage of such as Update and FixedUpdate which would in fact work. But using these methods would be over kill for our particular use case since we are able to very easily identify when our code needs to run. What we need then is a way to start a repeating process when a user clicks and drags our object which is a perfect place to use a Coroutine (c).

DraggableBehavior.cs

using System.Collections;
...
namespace UserInterface
{
    public sealed class DraggableBehavior : MonoBehaviour
    {
        ...
        public float followPointerWait = 0.01f;
        ...
        private Coroutine _followPointerCoroutine;
        private WaitForSeconds _followPointerWaitForSeconds;
        ...
        public bool IsBeingDragged { get; private set; }
        ...
        private IEnumerator FollowPointer()
        {
            while (true)
            {
                if (IsBeingDragged == false)
                {
                    yield break;
                }

                Debug.Log("following");

                yield return _followPointerWaitForSeconds;
            }
        }
        ...
        private void Init()
        {
            _followPointerWaitForSeconds = new WaitForSeconds(followPointerWait);
            ...
        }
        ...
        private void OnPointerDown(BaseEventData data)
        {
            Debug.Log($"pointer down: {data}");

            IsBeingDragged = true;

            _followPointerCoroutine = StartCoroutine(FollowPointer());
        }

        private void OnPointerUp(BaseEventData data)
        {
            Debug.Log($"pointer up: {data}");

            IsBeingDragged = false;

            if (_followPointerCoroutine != null)
            {
                StopCoroutine(_followPointerCoroutine);
            }
        }
    }
}
(c) Adding a coroutine to our behavior will allow us to repeatedly execute our code when we need to and stop when we want to.
Advertisement

Now that we have our coroutine code added if we switch over to Unity and play our scene when we click and hold our mouse button down on the panel we should see output in the console very similar to what is shown in (d).

Unity console output showing our coroutine is actually running.
(d) Unity console output showing our coroutine is actually running.

Just where is the pointer?

  • Assets
    • Utilities
      • UiUtilities.cs

In order for us to move our object in response to a user dragging it we are going to need to know the location of the pointer within the objects transform. Although that is our current question the underlying question is if given a two dimensional vector what is its position relative to the transform. Luckily Unity already provides us a method to determine this which we will once again wrap in an extension method to make it easier to use (e). And while we are at it we will create a method that will assume the position we are talking about is the mouse pointer position.

UiUtilities.cs

using UnityEngine;
...
namespace Utilities
{
    public static class UiUtilities
    {
        ...
        public static Vector2 PointerPositionInTransform(this RectTransform transform,
            Camera camera)
        {
            return PositionInTransform(transform, camera, Input.mousePosition);
        }

        public static Vector2 PositionInTransform(this RectTransform transform,
            Camera camera, Vector2 position)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                transform, position, camera, out var pos);
            return pos;
        }
    }
}
(e) Adding a couple of extension methods to make it easier to determine positions relative to a rect transform.

Pass the pointer

  • Assets
    • UserInterface
      • DraggableBehavior.cs

For everything to work we of course need to pass the pointer position when the user presses the mouse button to the follow coroutine (f).

DraggableBehavior.cs

...
namespace UserInterface
{
    public sealed class DraggableBehavior : MonoBehaviour
    {
        ...
        private RectTransform RectTransform => (RectTransform)transform;
        ...
        private IEnumerator FollowPointer(Vector2 position)
        {
            while (true)
            {
                ...
                Debug.Log($"following: {position}");
                ...
            }
        }
        ...
        private void OnPointerDown(BaseEventData data)
        {
            ...
            var pos = RectTransform.PointerPositionInTransform(((PointerEventData) data).pressEventCamera);
            _followPointerCoroutine = StartCoroutine(FollowPointer(pos));
        }
        ...
    }
}
(f) Updating our follow coroutine and the call made to it to make use of the position of the mouse pointer relative to the rect transform when the user presses the button.
Exciton Interactive LLC
Advertisement