我担心单指移动代码中OnPointerDown
与OnBeginDrag
之间的差异。
(在使用物理raycaster的最新Unity范例中:最后,Unity将正确地忽略UI层上的触摸。
所以从2015年开始你必须做的是:
忘掉传统的Input
或Touches
系统废话,甚至Unity承认完全没有意义的废话 - 因为它们不起作用
添加一个空的游戏对象,通常是BoxCollider2D,可能比屏幕大。使图层“绘制”(物理设置,它与任何内容交互)
只需添加到相机, 2D或3D物理raycaster 。事件掩盖了你的“绘图”层。
执行如下脚本并将其置于“绘图”。
(提示 - 不要忘记简单地在场景中添加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场景保存相同的输入这是怎样的......注意优秀的
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;
}
}
答案 0 :(得分:15)
我首先要说Input
和Touches
并不蹩脚。它们仍然很有用,是touch
之前在移动设备上检查OnPointerDown
的最佳方法和OnBeginDrag
出现了。 OnMouseDown()
你可以称之为糟糕,因为它没有针对移动设备进行优化。对于初学者,只需开始来学习Unity,Input
和Touches
是他们的选择。
至于您的问题,OnPointerDown
和OnBeginDrag
不相同。虽然他们几乎做同样的事情,但他们被实施以不同的方式执行。下面我将介绍其中的大部分内容:
OnPointerDown
:
当屏幕上有按下/触摸时(当触摸屏上有按下或手指按下时)调用
OnPointerUp
:
释放按下/触摸时(释放单击或从触摸屏移除手指)时调用
OnBeginDrag
:
在拖动开始之前调用一次(当手指/鼠标在第一次时移动时向下移动时)
OnDrag
:
当用户在屏幕上拖动时(手指/鼠标在触摸屏上移动时)反复调用
OnEndDrag
:
拖动停止时调用(当手指/鼠标在触摸屏上没有更长时移动)。
OnPointerDown
与 OnBeginDrag
和 OnEndDrag
OnPointerUp
未被调用, OnPointerDown
将不。如果OnEndDrag
未被调用,OnBeginDrag
将不。它就像C ++中的花括号,C#,你打开它&#39; {&#39;并且你关闭它&#39; } &#39;。
差异:
当手指/鼠标在触摸屏上时,OnPointerDown将被称为一次,立即。在鼠标移动或手指在屏幕上移动之后不会发生任何其他事情,然后OnBeginDrag
将被称为一次,然后是OnDrag。
这些用于执行高级用法,例如自定义用户界面中未包含的控件。
何时使用每个人:
1。当您必须在屏幕上实施简单点击按钮时,例如,向上,向下,拍摄按钮,仅 >需要OnPointerDown
来检测触摸。这适用于Sprite Images。
2。当您必须实施自定义切换开关并希望它逼真时,玩家可以向左拖动/向右/向上/向下到切换,然后您需要OnPointerDown
,OnBeginDrag
,OnDrag
,OnEndDrag
,OnPointerUp
。您需要按此顺序编写代码,以在屏幕上显示平滑 Sprite / Texture转换。一些切换开关用于点击,它将切换。有些人喜欢通过制作它看起来很逼真,所以你必须拖动它才能切换它。
3。此外,如果您想实施可拖动的通用可重复使用弹出窗口,您还需要使用这5个函数(OnPointerDown
,OnBeginDrag
,OnDrag
,OnEndDrag
,OnPointerUp
)。
首先检测何时有单击(OnPointerDown
),检查以确保单击的Sprite是您要移动的正确的Sprite。等待玩家移动(OnBeginDrag
)他们的手指/鼠标。一旦他们开始拖动,也许你可以调用带有while
循环的协同程序功能,它将开始移动精灵并在该协程内,你可以平滑的运动使用Time.deltaTime
或任何其他首选方法的精灵。
由于OnBeginDrag
被调用一次,因此它是开始 协程的好地方。
当玩家继续拖动精灵时,OnDrag
将被重复称为 。使用OnDrag
功能获取搜索程序的当前位置,并将其更新为Vector3
已经正在运行的协程将用于更新 Sprite的位置。当播放器停止在屏幕上移动他们的手指/鼠标时,OnEndDrag
会被调用,您可以boolean
变量并告诉协程停止更新雪碧的位置。然后,当玩家释放他们的手指(OnPointerUp
)时,您可以使用StopCoroutine函数停止协程。
由于OnBeginDrag
我们能够在等待拖动结束时拖动开始时启动协同程序。 感觉 开始 {/ 1}}中的协同程序,因为这意味着每次玩家触摸屏幕,协程将启动。
如果没有OnPointerDown
,我们必须使用OnBeginDrag
变量来使协程在每次调用的boolean
函数中只启动一次,否则将会有coroutine在任何地方运行且意外将发生精灵的移动。
4. 当你想确定玩家移动手指的时间。这方面的例子是名为 Fruit Ninja 的着名游戏。让我们说你想确定播放器在屏幕上滑动的距离。
首先,等待直到调用OnDrag
,等待再次调用OnPointerDown
,然后您就可以获得当前< / strong>手指在OnBeginDrag
函数内的位置,因为在手指开始移动之前调用了OnBeginDrag
。释放手指后,将调用OnBeginDrag
。然后你可以再次获得手指的当前位置。您可以使用这些两个 位置来检查手指移动了多远减去它们。
如果您决定使用OnEndDrag
作为获取手指的第一个 位置的地方,您将获得错误结果,因为如果播放器向右滑动,则等待并向左滑动,然后再次等待并在每次滑动后,向上滑动 而不释放手指,您唯一的良好结果是首次刷卡(右键滑动)。 左和向上滑动将包含无效值,因为OnPointerDown
调用时获得的第一个值是您仍然使用的值。这是因为玩家从未将手指从屏幕上移开,所以因此,OnPointerDown
从不再次称为 和第一个旧<强>旧价值仍然存在。
但是当您使用OnPointerDown
代替OnBeginDrag
时,此问题将消失,因为当手指停止移动时,{{1} }当被再次开始移动时,OnPointerDown
再次被调用,导致第一个位置被覆盖 新
答案 1 :(得分:1)
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.