Unity3D 中 OnPointerDown 与 OnBeginDrag 的对比

Horrors of OnPointerDown versus OnBeginDrag in Unity3D

我担心 OnPointerDownOnBeginDrag 在单指移动代码中的区别。

(在使用物理光线投射器的最新 Unity 范例中:因此,最后,Unity 将正确地忽略 UI 层上的触摸。

所以从 2015 年开始,你必须做的是:

  1. 忘掉传统的InputTouches 系统,它们毫无意义且不起作用

  2. 添加一个通常为 BoxCollider2D 的空游戏对象,可能比屏幕大。制作名为“Draw”的图层。物理设置,“Draw”没有交互

  3. 只需添加到相机,一个 2D 或 3D 物理光线投射器。事件屏蔽“绘图”层。

做一个像下面这样的脚本并把它放上去。

(提示 - 不要忘记简单地向场景添加一个 EventSystem。奇怪的是,Unity 在某些情况下不会自动为您执行此操作,但 Unity 在其他情况下会自动为您执行此操作,所以如果你忘记了会很烦人!)

但是问题来了

使用 OnPointerDownOnBeginDrag(以及匹配的结束调用)之间肯定存在一些细微差别。 (您可以只交换以下代码示例中的操作。)

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;
    }
  }

首先我想说 InputTouches 不是 crappy.They 仍然有用,并且是之前在移动设备上检查 touch 的最佳方式OnPointerDownOnBeginDrag 出现了。 OnMouseDown() 你可以称之为糟糕,因为它没有针对移动设备进行优化。对于刚刚开始学习Unity的初学者InputTouches是他们的选择。

至于您的问题,OnPointerDownOnBeginDrag 相同。尽管它们几乎做同样的事情,但它们被实现为以不同的方式执行。下面我将描述其中的大部分:

OnPointerDown: 当屏幕上有 press/touch 时调用(当有点击或手指在触摸屏上按下时)

OnPointerUp: 当 press/touch 被释放时调用(当点击被释放或手指从触摸屏上移开时)

OnBeginDrag: 在开始拖动之前调用 一次 (当 finger/mouse 移动 第一次 时间时)

OnDrag : 当用户在屏幕上拖动时重复调用(当 finger/mouse 在触摸屏上移动时)

OnEndDrag: 拖动停止时调用(当finger/mouse不再不再在触摸屏上移动时调用。

OnPointerDown 对比 OnBeginDragOnEndDrag

如果 OnPointerDown 没有被 调用,

OnPointerUpNOT 被调用。如果 OnBeginDrag 没有被 调用, 将不会 被调用。就像C++、C#中的大括号,你打开它'{'你关闭它'}'.

区别: 当 finger/mouse 在触摸屏上时,OnPointerDown 将被调用 一次 并且 立即。在鼠标移动或手指在屏幕上移动之前不会发生任何其他事情,然后 OnBeginDrag 将被调用 一次 然后是 OnDrag。

这些是为了进行高级用法,例如 custom UI Unity 中不包含的控件。

何时使用每个:

1. 当你必须实现一个简单的点击按钮,例如屏幕上的向上、向下、射击按钮时,你需要OnPointerDown来检测触摸。这应该适用于 Sprite 图像。

2. 当您必须实现自定义 切换开关 并且您希望它 逼真 以便玩家可以拖动到 left/right 或 up/down 以 切换 然后你需要 OnPointerDown , OnBeginDrag , OnDragOnEndDragOnPointerUp。您需要按此顺序编写代码才能在屏幕上实现平滑 Sprite/Texture 过渡。一些拨动开关被设为 clicked,它将 toggle。有些人喜欢让它看起来逼真,因此您必须拖动它才能切换它。

3. 当你想实现一个通用的可重用弹出window 是可拖动的,您还需要使用这 5 个函数(OnPointerDownOnBeginDragOnDragOnEndDragOnPointerUp)。 首先检测是否有点击(OnPointerDown),检查确保点击的精灵是您要移动的正确精灵。等待玩家移动(OnBeginDrag)他们的 finger/mouse。一旦他们开始拖动,也许你可以用 while loop 调用协程函数,它将开始移动 Sprite,在协程中,你可以 smooth 使用 Time.deltaTime 或任何其他首选方法移动精灵。

因为OnBeginDrag被调用了一次,所以它是启动协程的好地方。 随着玩家继续拖动精灵,OnDrag将被重复调用。使用 OnDrag 函数获取查找器的当前位置并将其更新为 Vector3already 运行 的协程将使用该 Vector3 更新精灵的位置。当玩家 停止 在屏幕上移动他们的 finger/mouse 时,调用 OnEndDrag 并且您可以 boolean 变量并告诉协程停止 更新精灵的位置。然后,当玩家 释放 他们的手指时 (OnPointerUp) 您可以使用 StopCoroutine 函数停止协程。

由于 OnBeginDrag 我们可以在拖动开始时启动协程,同时等待拖动结束。它 不会 sensestart OnPointerDown 中的协程,因为那意味着每次玩家 触摸 屏幕时,协程将 启动

如果没有OnBeginDrag,我们必须使用boolean变量来让协程在每次调用的OnDrag函数中只启动一次,否则就会出现协程运行 到处都会发生精灵的意外移动。

4. 当你想要确定玩家移动手指的时间时。这方面的例子就是著名的游戏 Fruit Ninja。假设您想确定玩家在屏幕上滑动的距离。

首先,等待直到调用OnPointerDown,再次等待直到调用OnBeginDrag,然后你可以在 OnBeginDrag 函数中获取手指的 当前 位置,因为 OnBeginDrag 在手指开始移动之前被调用。手指松开后,调用OnEndDrag。然后你可以再次获得手指的当前位置。您可以使用这些 two positions 通过 subtracting 它们来检查手指移动了多远。

如果您决定使用 OnPointerDown 作为获取手指 first position 的位置,您将得到一个错误的结果,因为如果玩家向右滑动,然后等待滑动左然后再次等待并且向上滑动在[=118之后不松开他们的手指 =]每次滑动,唯一好的结果是第一次滑动(右滑动). leftup 滑动将具有 invalid 值,因为您在 [=13] 时获得的第一个值=] was called 是您 still 使用的值。这是因为玩家从未将手指从屏幕上移开,因此 因此OnPointerDown 从未 再次调用 并且第一个旧 旧值 仍然存在。

但是当你使用 OnBeginDrag 而不是 OnPointerDown 时,这个问题就会 消失 因为当手指 停止移动时 , OnEndDrag 被调用,当它再次开始移动时 OnBeginDrag 被再次调用导致第一个 positionoverwritten.

不同之处在于 OnBeginDrag 直到 touch/mouse 移动了某个最小距离(拖动阈值)后才会被调用。您可以在事件系统组件上设置拖动阈值。

当您的对象层次结构具有不同的输入处理方式(尤其是滚动视图)时,这是必需的。想象一下,你有一个带有垂直堆栈单元格的滚动视图,每个单元格中都有一个按钮。当触摸首先在其中一个按钮上开始时,我们不知道用户是在点击按钮还是在拖动滚动视图。直到触摸被拖动达到拖动阈值,我们才知道这是拖动而不是点击。