Unity3D 中 OnPointerDown 与 OnBeginDrag 的对比
Horrors of OnPointerDown versus OnBeginDrag in Unity3D
我担心 OnPointerDown
与 OnBeginDrag
在单指移动代码中的区别。
(在使用物理光线投射器的最新 Unity 范例中:因此,最后,Unity 将正确地忽略 UI 层上的触摸。
所以从 2015 年开始,你必须做的是:
忘掉传统的Input
或Touches
系统,它们毫无意义且不起作用
添加一个通常为 BoxCollider2D 的空游戏对象,可能比屏幕大。制作名为“Draw”的图层。物理设置,“Draw”没有交互
只需添加到相机,一个 2D 或 3D 物理光线投射器。事件屏蔽“绘图”层。
做一个像下面这样的脚本并把它放上去。
(提示 - 不要忘记简单地向场景添加一个 EventSystem
。奇怪的是,Unity 在某些情况下不会自动为您执行此操作,但 Unity 在其他情况下会自动为您执行此操作,所以如果你忘记了会很烦人!)
但是问题来了。
使用 OnPointerDown
与 OnBeginDrag
(以及匹配的结束调用)之间肯定存在一些细微差别。 (您可以只交换以下代码示例中的操作。)
Unity 自然不会对此提供指导;下面的代码漂亮地拒绝了杂散的抓取,也完美地忽略了你的 UI 层(感谢 Unity!终于!)但我对这两种方法之间的区别感到困惑(开始拖动 V. 开始触摸),我无论如何不能找出两者在单元测试中的逻辑差异
答案是什么?
/*
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);
}
}
这是一个方便的东西。 3D场景省打字,使用鲜为人知但精致
pointerCurrentRaycast
这里是如何......注意优秀
data.pointerCurrentRaycast.worldPosition
致电 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;
}
}
首先我想说 Input
和 Touches
不是 crappy.They 仍然有用,并且是之前在移动设备上检查 touch
的最佳方式OnPointerDown
和 OnBeginDrag
出现了。 OnMouseDown()
你可以称之为糟糕,因为它没有针对移动设备进行优化。对于刚刚开始学习Unity的初学者,Input
和Touches
是他们的选择。
至于您的问题,OnPointerDown
和 OnBeginDrag
不 相同。尽管它们几乎做同样的事情,但它们被实现为以不同的方式执行。下面我将描述其中的大部分:
OnPointerDown
:
当屏幕上有 press/touch 时调用(当有点击或手指在触摸屏上按下时)
OnPointerUp
:
当 press/touch 被释放时调用(当点击被释放或手指从触摸屏上移开时)
OnBeginDrag
:
在开始拖动之前调用 一次 (当 finger/mouse 移动 第一次 时间时)
OnDrag
:
当用户在屏幕上拖动时重复调用(当 finger/mouse 在触摸屏上移动时)
OnEndDrag
:
拖动停止时调用(当finger/mouse不再不再在触摸屏上移动时调用。
OnPointerDown
对比 OnBeginDrag
和 OnEndDrag
如果 OnPointerDown
没有被 调用,OnPointerUp
将 NOT 被调用。如果 OnBeginDrag
没有被 调用, 将不会 被调用。就像C++、C#中的大括号,你打开它'{'你关闭它'}'.
区别:
当 finger/mouse 在触摸屏上时,OnPointerDown 将被调用 一次 并且 立即。在鼠标移动或手指在屏幕上移动之前不会发生任何其他事情,然后 OnBeginDrag
将被调用 一次 然后是 OnDrag。
这些是为了进行高级用法,例如 custom UI Unity 中不包含的控件。
何时使用每个:
1. 当你必须实现一个简单的点击按钮,例如屏幕上的向上、向下、射击按钮时,你只需要OnPointerDown
来检测触摸。这应该适用于 Sprite 图像。
2. 当您必须实现自定义 切换开关 并且您希望它 逼真 以便玩家可以拖动到 left/right 或 up/down 以 切换 然后你需要 OnPointerDown
, OnBeginDrag
, OnDrag
、OnEndDrag
、OnPointerUp
。您需要按此顺序编写代码才能在屏幕上实现平滑 Sprite/Texture 过渡。一些拨动开关被设为 clicked,它将 toggle。有些人喜欢让它看起来逼真,因此您必须拖动它才能切换它。
3. 当你想实现一个通用的可重用弹出window 是可拖动的,您还需要使用这 5 个函数(OnPointerDown
、OnBeginDrag
、OnDrag
、OnEndDrag
、OnPointerUp
)。
首先检测是否有点击(OnPointerDown
),检查确保点击的精灵是您要移动的正确精灵。等待玩家移动(OnBeginDrag
)他们的 finger/mouse。一旦他们开始拖动,也许你可以用 while
loop 调用协程函数,它将开始移动 Sprite,在协程中,你可以 smooth 使用 Time.deltaTime
或任何其他首选方法移动精灵。
因为OnBeginDrag
被调用了一次,所以它是启动协程的好地方。
随着玩家继续拖动精灵,OnDrag
将被重复调用。使用 OnDrag
函数获取查找器的当前位置并将其更新为 Vector3
,already 运行 的协程将使用该 Vector3
更新精灵的位置。当玩家 停止 在屏幕上移动他们的 finger/mouse 时,调用 OnEndDrag
并且您可以 boolean
变量并告诉协程停止 更新精灵的位置。然后,当玩家 释放 他们的手指时 (OnPointerUp
) 您可以使用 StopCoroutine 函数停止协程。
由于 OnBeginDrag
我们可以在拖动开始时启动协程,同时等待拖动结束。它 不会 让 sense 到 start OnPointerDown
中的协程,因为那意味着每次玩家 触摸 屏幕时,协程将 启动 。
如果没有OnBeginDrag
,我们必须使用boolean
变量来让协程在每次调用的OnDrag
函数中只启动一次,否则就会出现协程运行 到处都会发生精灵的意外移动。
4. 当你想要确定玩家移动手指的时间时。这方面的例子就是著名的游戏 Fruit Ninja。假设您想确定玩家在屏幕上滑动的距离。
首先,等待直到调用OnPointerDown
,再次等待直到调用OnBeginDrag
,然后你可以在 OnBeginDrag
函数中获取手指的 当前 位置,因为 OnBeginDrag
在手指开始移动之前被调用。手指松开后,调用OnEndDrag
。然后你可以再次获得手指的当前位置。您可以使用这些 two positions 通过 subtracting 它们来检查手指移动了多远。
如果您决定使用 OnPointerDown
作为获取手指 first position 的位置,您将得到一个错误的结果,因为如果玩家向右滑动,然后等待并滑动左然后再次等待并且向上滑动在[=118之后不松开他们的手指 =]每次滑动,唯一好的结果是第一次滑动(右滑动). left 和 up 滑动将具有 invalid 值,因为您在 [=13] 时获得的第一个值=] was called 是您 still 使用的值。这是因为玩家从未将手指从屏幕上移开,因此 因此,OnPointerDown
从未 再次调用 并且第一个旧 旧值 仍然存在。
但是当你使用 OnBeginDrag
而不是 OnPointerDown
时,这个问题就会 消失 因为当手指 停止移动时 , OnEndDrag
被调用,当它再次开始移动时 OnBeginDrag
被再次调用导致第一个 position 被 overwritten 与 新 .
不同之处在于 OnBeginDrag
直到 touch/mouse 移动了某个最小距离(拖动阈值)后才会被调用。您可以在事件系统组件上设置拖动阈值。
当您的对象层次结构具有不同的输入处理方式(尤其是滚动视图)时,这是必需的。想象一下,你有一个带有垂直堆栈单元格的滚动视图,每个单元格中都有一个按钮。当触摸首先在其中一个按钮上开始时,我们不知道用户是在点击按钮还是在拖动滚动视图。直到触摸被拖动达到拖动阈值,我们才知道这是拖动而不是点击。
我担心 OnPointerDown
与 OnBeginDrag
在单指移动代码中的区别。
(在使用物理光线投射器的最新 Unity 范例中:因此,最后,Unity 将正确地忽略 UI 层上的触摸。
所以从 2015 年开始,你必须做的是:
忘掉传统的
Input
或Touches
系统,它们毫无意义且不起作用添加一个通常为 BoxCollider2D 的空游戏对象,可能比屏幕大。制作名为“Draw”的图层。物理设置,“Draw”没有交互
只需添加到相机,一个 2D 或 3D 物理光线投射器。事件屏蔽“绘图”层。
做一个像下面这样的脚本并把它放上去。
(提示 - 不要忘记简单地向场景添加一个 EventSystem
。奇怪的是,Unity 在某些情况下不会自动为您执行此操作,但 Unity 在其他情况下会自动为您执行此操作,所以如果你忘记了会很烦人!)
但是问题来了。
使用 OnPointerDown
与 OnBeginDrag
(以及匹配的结束调用)之间肯定存在一些细微差别。 (您可以只交换以下代码示例中的操作。)
Unity 自然不会对此提供指导;下面的代码漂亮地拒绝了杂散的抓取,也完美地忽略了你的 UI 层(感谢 Unity!终于!)但我对这两种方法之间的区别感到困惑(开始拖动 V. 开始触摸),我无论如何不能找出两者在单元测试中的逻辑差异
答案是什么?
/*
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);
}
}
这是一个方便的东西。 3D场景省打字,使用鲜为人知但精致
pointerCurrentRaycast
这里是如何......注意优秀
data.pointerCurrentRaycast.worldPosition
致电 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;
}
}
首先我想说 Input
和 Touches
不是 crappy.They 仍然有用,并且是之前在移动设备上检查 touch
的最佳方式OnPointerDown
和 OnBeginDrag
出现了。 OnMouseDown()
你可以称之为糟糕,因为它没有针对移动设备进行优化。对于刚刚开始学习Unity的初学者,Input
和Touches
是他们的选择。
至于您的问题,OnPointerDown
和 OnBeginDrag
不 相同。尽管它们几乎做同样的事情,但它们被实现为以不同的方式执行。下面我将描述其中的大部分:
OnPointerDown
:
当屏幕上有 press/touch 时调用(当有点击或手指在触摸屏上按下时)
OnPointerUp
:
当 press/touch 被释放时调用(当点击被释放或手指从触摸屏上移开时)
OnBeginDrag
:
在开始拖动之前调用 一次 (当 finger/mouse 移动 第一次 时间时)
OnDrag
:
当用户在屏幕上拖动时重复调用(当 finger/mouse 在触摸屏上移动时)
OnEndDrag
:
拖动停止时调用(当finger/mouse不再不再在触摸屏上移动时调用。
OnPointerDown
对比 OnBeginDrag
和 OnEndDrag
OnPointerDown
没有被 调用,OnPointerUp
将 NOT 被调用。如果 OnBeginDrag
没有被 调用, 将不会 被调用。就像C++、C#中的大括号,你打开它'{'你关闭它'}'.
区别:
当 finger/mouse 在触摸屏上时,OnPointerDown 将被调用 一次 并且 立即。在鼠标移动或手指在屏幕上移动之前不会发生任何其他事情,然后 OnBeginDrag
将被调用 一次 然后是 OnDrag。
这些是为了进行高级用法,例如 custom UI Unity 中不包含的控件。
何时使用每个:
1. 当你必须实现一个简单的点击按钮,例如屏幕上的向上、向下、射击按钮时,你只需要OnPointerDown
来检测触摸。这应该适用于 Sprite 图像。
2. 当您必须实现自定义 切换开关 并且您希望它 逼真 以便玩家可以拖动到 left/right 或 up/down 以 切换 然后你需要 OnPointerDown
, OnBeginDrag
, OnDrag
、OnEndDrag
、OnPointerUp
。您需要按此顺序编写代码才能在屏幕上实现平滑 Sprite/Texture 过渡。一些拨动开关被设为 clicked,它将 toggle。有些人喜欢让它看起来逼真,因此您必须拖动它才能切换它。
3. 当你想实现一个通用的可重用弹出window 是可拖动的,您还需要使用这 5 个函数(OnPointerDown
、OnBeginDrag
、OnDrag
、OnEndDrag
、OnPointerUp
)。
首先检测是否有点击(OnPointerDown
),检查确保点击的精灵是您要移动的正确精灵。等待玩家移动(OnBeginDrag
)他们的 finger/mouse。一旦他们开始拖动,也许你可以用 while
loop 调用协程函数,它将开始移动 Sprite,在协程中,你可以 smooth 使用 Time.deltaTime
或任何其他首选方法移动精灵。
因为OnBeginDrag
被调用了一次,所以它是启动协程的好地方。
随着玩家继续拖动精灵,OnDrag
将被重复调用。使用 OnDrag
函数获取查找器的当前位置并将其更新为 Vector3
,already 运行 的协程将使用该 Vector3
更新精灵的位置。当玩家 停止 在屏幕上移动他们的 finger/mouse 时,调用 OnEndDrag
并且您可以 boolean
变量并告诉协程停止 更新精灵的位置。然后,当玩家 释放 他们的手指时 (OnPointerUp
) 您可以使用 StopCoroutine 函数停止协程。
由于 OnBeginDrag
我们可以在拖动开始时启动协程,同时等待拖动结束。它 不会 让 sense 到 start OnPointerDown
中的协程,因为那意味着每次玩家 触摸 屏幕时,协程将 启动 。
如果没有OnBeginDrag
,我们必须使用boolean
变量来让协程在每次调用的OnDrag
函数中只启动一次,否则就会出现协程运行 到处都会发生精灵的意外移动。
4. 当你想要确定玩家移动手指的时间时。这方面的例子就是著名的游戏 Fruit Ninja。假设您想确定玩家在屏幕上滑动的距离。
首先,等待直到调用OnPointerDown
,再次等待直到调用OnBeginDrag
,然后你可以在 OnBeginDrag
函数中获取手指的 当前 位置,因为 OnBeginDrag
在手指开始移动之前被调用。手指松开后,调用OnEndDrag
。然后你可以再次获得手指的当前位置。您可以使用这些 two positions 通过 subtracting 它们来检查手指移动了多远。
如果您决定使用 OnPointerDown
作为获取手指 first position 的位置,您将得到一个错误的结果,因为如果玩家向右滑动,然后等待并滑动左然后再次等待并且向上滑动在[=118之后不松开他们的手指 =]每次滑动,唯一好的结果是第一次滑动(右滑动). left 和 up 滑动将具有 invalid 值,因为您在 [=13] 时获得的第一个值=] was called 是您 still 使用的值。这是因为玩家从未将手指从屏幕上移开,因此 因此,OnPointerDown
从未 再次调用 并且第一个旧 旧值 仍然存在。
但是当你使用 OnBeginDrag
而不是 OnPointerDown
时,这个问题就会 消失 因为当手指 停止移动时 , OnEndDrag
被调用,当它再次开始移动时 OnBeginDrag
被再次调用导致第一个 position 被 overwritten 与 新 .
不同之处在于 OnBeginDrag
直到 touch/mouse 移动了某个最小距离(拖动阈值)后才会被调用。您可以在事件系统组件上设置拖动阈值。
当您的对象层次结构具有不同的输入处理方式(尤其是滚动视图)时,这是必需的。想象一下,你有一个带有垂直堆栈单元格的滚动视图,每个单元格中都有一个按钮。当触摸首先在其中一个按钮上开始时,我们不知道用户是在点击按钮还是在拖动滚动视图。直到触摸被拖动达到拖动阈值,我们才知道这是拖动而不是点击。