I'm concerned over the difference between OnPointerDown
versus OnBeginDrag
in single-finger movement code.
(In the latest Unity paradigm of using a physics raycaster: so, finally, Unity will properly ignore touch on the UI layer.
So from 2015 onwards what you must do is this:
Forget about the crap traditional
Input
orTouches
system which are pointless crap and don't workAdd an empty game object with a usually BoxCollider2D, likely bigger than the screen. Make the layer called say "Draw". Physics settings, "Draw" interacts with nothing
Simply add to the camera, a 2D or 3D physics raycaster. Event mask the "Draw" layer.
Do a script like below and put it on.
(Tip - don't forget to simply add an EventSystem
to the scene. Bizarrely, Unity does not do this automatically for you in some situations but Unity does do it automatically for you in other situations, so it's annoying if you forget!)
But here's the problem.
There has got to be some subtle difference between using OnPointerDown
versus OnBeginDrag
(and the matching end calls). (You can just swap the action in the following code sample.)
Naturally Unity offers no guidance on this; the following code beautifully rejects stray grabs and also flawlessly ignores your UI layer (thanks Unity! at last!) but I am mystified about the difference between the two approaches (begin drag V. begin touch) and I cannot in anyway find the logical difference between the two in unit testing.
What's the answer?
/*
general movement of something by a finger.
*/
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class FingerMove:MonoBehaviour,
IPointerDownHandler,
IBeginDragHandler,
IDragHandler,
IPointerUpHandler,
IEndDragHandler
{
public Transform moveThis;
private Camera theCam;
private FourLimits thingLimits;
private Vector3 prevPointWorldSpace;
private Vector3 thisPointWorldSpace;
private Vector3 realWorldTravel;
public void Awake()
{
theCam = Camera.main or whatever;
}
public void OnMarkersReady() // (would be EVENT DRIVEN for liveness)
{
thingLimits = Grid.liveMarkers. your motion limits
}
private int drawFinger;
private bool drawFingerAlreadyDown;
public void OnPointerDown (PointerEventData data)
{
Debug.Log(" P DOWN " +data.pointerId.ToString() );
}
public void OnBeginDrag (PointerEventData data)
{
Debug.Log(" BEGIN DRAG " +data.pointerId.ToString() );
if (drawFingerAlreadyDown == true)
{
Debug.Log(" IGNORE THAT DOWN! " +data.pointerId.ToString() );
return;
}
drawFinger = data.pointerId;
drawFingerAlreadyDown=true;
prevPointWorldSpace = theCam.ScreenToWorldPoint( data.position );
}
public void OnDrag (PointerEventData data)
{
Debug.Log(" ON DRAG " +data.pointerId.ToString() );
if (drawFingerAlreadyDown == false)
{
Debug.Log(" IGNORE THAT PHANTOM! " +data.pointerId.ToString() );
}
if ( drawFinger != data.pointerId )
{
Debug.Log(" IGNORE THAT DRAG! " +data.pointerId.ToString() );
return;
}
thisPointWorldSpace = theCam.ScreenToWorldPoint( data.position );
realWorldTravel = thisPointWorldSpace - prevPointWorldSpace;
_processRealWorldtravel();
prevPointWorldSpace = thisPointWorldSpace;
}
public void OnEndDrag (PointerEventData data)
{
Debug.Log(" END DRAG " +data.pointerId.ToString() );
if ( drawFinger != data.pointerId )
{
Debug.Log(" IGNORE THAT UP! " +data.pointerId.ToString() );
return;
}
drawFingerAlreadyDown = false;
}
public void OnPointerUp (PointerEventData data)
{
Debug.Log(" P UP " +data.pointerId.ToString() );
}
private void _processRealWorldtravel()
{
if ( Grid. your pause concept .Paused ) return;
// potential new position...
Vector3 pot = moveThis.position + realWorldTravel;
// almost always, squeeze to a limits box...
// (whether the live screen size, or some other box)
if (pot.x < thingLimits.left) pot.x = thingLimits.left;
if (pot.y > thingLimits.top) pot.y = thingLimits.top;
if (pot.x > thingLimits.right) pot.x = thingLimits.right;
if (pot.y < thingLimits.bottom) pot.y = thingLimits.bottom;
// kinematic ... moveThis.position = pot;
// or
// if pushing around physics bodies ... rigidbody.MovePosition(pot);
}
}
And here's a handy thing. Save typing with the same thing for 3D scenes, using the little-known but exquisite
pointerCurrentRaycast
here's how... notice the excellent
data.pointerCurrentRaycast.worldPosition
call courtesy Unity.
public class FingerDrag .. for 3D scenes:MonoBehaviour,
IPointerDownHandler,
IDragHandler,
IPointerUpHandler
{
public Transform moveMe;
private Vector3 prevPointWorldSpace;
private Vector3 thisPointWorldSpace;
private Vector3 realWorldTravel;
private int drawFinger;
private bool drawFingerAlreadyDown;
public void OnPointerDown (PointerEventData data)
{
if (drawFingerAlreadyDown == true)
return;
drawFinger = data.pointerId;
drawFingerAlreadyDown=true;
prevPointWorldSpace = data.pointerCurrentRaycast.worldPosition;
// in this example we'll put it under finger control...
moveMe.GetComponent<Rigidbody>().isKinematic = false;
}
public void OnDrag (PointerEventData data)
{
if (drawFingerAlreadyDown == false)
return;
if ( drawFinger != data.pointerId )
return;
thisPointWorldSpace = data.pointerCurrentRaycast.worldPosition;
realWorldTravel = thisPointWorldSpace - prevPointWorldSpace;
_processRealWorldtravel();
prevPointWorldSpace = thisPointWorldSpace;
}
public void OnPointerUp (PointerEventData data)
{
if ( drawFinger != data.pointerId )
return;
drawFingerAlreadyDown = false;
moveMe.GetComponent<Rigidbody>().isKinematic = false;
moveMe = null;
}
private void _processRealWorldtravel()
{
Vector3 pot = moveMe.position;
pot.x += realWorldTravel.x;
pot.y += realWorldTravel.y;
moveMe.position = pot;
}
}
I want to start by saying that
Input
andTouches
are not crappy.They are still usefull and were the best way to check fortouch
on mobile devices beforeOnPointerDown
andOnBeginDrag
came along.OnMouseDown()
you can call crappy because it was not optimized for mobile. For a beginner who just started to learn Unity,Input
andTouches
are their options.As for your question,
OnPointerDown
andOnBeginDrag
are NOT the-same. Although they almost do the-same thing but they were implemented to perform in different ways. Below I will describe most of these:OnPointerDown
: Called when there is press/touch on the screen (when there is a click or finger is pressed down on touch screen)OnPointerUp
: Called when press/touch is released (when click is released or finger is removed from the touch screen)OnBeginDrag
: Called once before a drag is started(when the finger/mouse is moved for the first time while down)OnDrag
: Repeatedly called when user is dragging on the screen (when the finger/mouse is moving on the touch screen)OnEndDrag
: Called when drag stops (when the finger/mouse is no longer moving on the touch screen).OnPointerDown
versusOnBeginDrag
andOnEndDrag
OnPointerUp
will NOT be called ifOnPointerDown
has not been called.OnEndDrag
will NOT be called ifOnBeginDrag
has not been called. Its like the curly braces in C++,C#, you open it '{' and you close it '}'.THE DIFFERENCE: OnPointerDown will be called once and immediately when finger/mouse is on the touch screen. Nothing else will happen until there is a mouse movement or the finger moves on the screen then
OnBeginDrag
will be called once followed by OnDrag.These are made for doing advanced usage such such as custom UI with controls that is not included in Unity.
WHEN TO USE EACH ONE:
1. When you have to implement a simple click button, for example, Up,Down, Shoot Button on the screen, you only need
OnPointerDown
to detect the touch. This should work for Sprite Images.2. When you have to implement a custom toggle switch and you want it to be realistic so that the player can drag to left/right or up/down to toggle it then you need
OnPointerDown
,OnBeginDrag
,OnDrag
,OnEndDrag
,OnPointerUp
. You need to write your code in this order to have a smooth Sprite/Texture transition on the screen. Some toggle switches are made to be to clicked and it will toggle. Some people prefer to make it look realistic by making it so that you have to drag it in order to toggle it.3. Also when you want to implement a Generic re-usable pop-up window that is draggable, you also need to use those 5 functions (
OnPointerDown
,OnBeginDrag
,OnDrag
,OnEndDrag
,OnPointerUp
). First detect when there is a click(OnPointerDown
), check to make sure that the Sprite clicked is the right one you want to move. Wait for player to move(OnBeginDrag
) their finger/mouse. Once they start dragging, maybe you can call a coroutine function withwhile
loop that will start moving the Sprite and inside that coroutine, you can smooth the movement of the Sprite withTime.deltaTime
or any other preferred method.Since
OnBeginDrag
is called once, it is a good place to start the coroutine. As the player continue to drag the Sprite,OnDrag
will be called repeatedly. Use theOnDrag
function to get the current location of the finder and update that to aVector3
that the coroutine that is already running will use to update the position of the Sprite. When the player stops moving their finger/mouse on the screen,OnEndDrag
is called and you canboolean
variable and tell the coroutine to stop updating the position of the Sprite. Then, when the player releases their finger(OnPointerUp
) you can then stop the coroutine with the StopCoroutine function.Because of
OnBeginDrag
we we are able to start coroutine once drag started while waiting for drag to end. It wouldn't make sense to start that coroutine inOnPointerDown
because that means that each time player touches the screen, a coroutine would be started.Without
OnBeginDrag
, we have to useboolean
variable to make the coroutine start only once in theOnDrag
function which is called every time or else there would be coroutine running everywhere and unexpected movement of the Sprite will occur.4. When you want to determine how long player moved their finger. Example of this is that famous game called Fruit Ninja. Lets just say you want to determine far the player swiped on the screen.
First, wait until
OnPointerDown
is called, wait again untilOnBeginDrag
is called, then you can get the current position of the finger insideOnBeginDrag
function becauseOnBeginDrag
is called before the finger starts moving. After the finger is released,OnEndDrag
is called. Then you can get the current position of finger again. You can use these two positions to check how far the finger moved by subtracting them.If you instead decide to use
OnPointerDown
as the place to get the first position of the finger, you will get a wrong result because if the player swipes right, then waits and swipes left then waits again and swipe up without releasing their finger after each swipe, the only good result you have is the first swipe(right swipe). The left and the up swipe will have invalid values because that first value you got whenOnPointerDown
was called is the value you are still using. This is because the player never removed their finger from the screen so therefore,OnPointerDown
is never called again and the first old old value is still there.But when you use
OnBeginDrag
instead ofOnPointerDown
, this problem will be gone because when the finger stops moving,OnEndDrag
is called and when it starts moving againOnBeginDrag
is called once again causing the first position to be overwritten with the new one.The difference is that
OnBeginDrag
doesn't get called until the touch/mouse has moved a certain minimum distance, the drag threshold. You can set the drag threshold on the Event System component.This is necessary for when you have a hierarchy of objects with different ways of handling input, especially scrollviews. Imagine you have a scrollview with a vertical stack of cells, each with a button in it. When the touch first starts on one of the buttons, we don't know whether the user is tapping a button or dragging the scrollview. It isn't until the touch gets dragged for the drag threshold that we know it is a drag and not a tap.