【问题标题】:Horrors of OnPointerDown versus OnBeginDrag in Unity3DUnity3D 中 OnPointerDown 与 OnBeginDrag 的恐怖
【发布时间】:2016-03-16 22:23:50
【问题描述】:

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

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

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

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

  2. 添加一个通常为 BoxCollider2D 的空游戏对象,可能比屏幕大。使图层称为“绘制”。物理设置,“画图”什么都没有交互

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

【问题讨论】:

  • 公平地说,我之前没有使用过 OnPointerDown 和 OnBeginDrag,因为它在一两年前推出时出现了问题。我在移动设备上使用 raycast 来检测物体上的触摸。您想知道 OnPointerDown 和 OnBeginDrag 之间的区别以及何时应该使用它们吗?
  • 我将您的脚本复制到了一个测试项目 Unity 5.2.1 中。当我单击 Box2D 时,它记录了 OnPointerDown 的东西,当我开始拖动它时,它记录了 OnBeginDrag 的东西。因此,如果您单击并按住但不移动,则调用 OnPointerDown,则不会调用 OnBeginDrag。
  • 嗨,Prog & Nikster。是的,我理解其中的区别,正如 Nika 解释的那样……我想我想知道使用哪一个……事实上,在现实世界中,哪一个才是正确的选择?
  • @Programmer 我放了一个用于 3D 场景的超级方便的 pointerCurrentRaycast 示例。保存输入

标签: unity3d touch unity3d-ui


【解决方案1】:

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

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

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

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

OnBeginDrag: 在开始拖动之前调用一次(当手指/鼠标在向下时第一次移动时)

OnDrag : 当用户在屏幕上拖动时(手指/鼠标在触摸屏上移动时)重复调用

OnEndDrag: 当拖动停止时调用(当手指/鼠标不再不再在触摸屏上移动时)。

OnPointerDownOnBeginDragOnEndDrag

如果OnPointerDown 没有被调用

OnPointerUp不会被调用。如果OnBeginDrag没有被调用OnEndDrag不会被调用。它就像 C++、C# 中的花括号,你打开它'{'然后你关闭它'} '。

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

这些是为进行高级使用而设计的,例如 custom UI 以及 Unity 中未包含的控件。

何时使用每个:

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

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

3. 此外,当您想要实现一个可拖动的通用可重复使用弹出窗口时,您还需要使用这 5 个函数(OnPointerDownOnBeginDragOnDragOnEndDragOnPointerUp)。 首先检测何时有点击(OnPointerDown),检查以确保点击的 Sprite 是您要移动的正确的。等待玩家移动(OnBeginDrag)他们的手指/鼠标。一旦它们开始拖动,也许您可​​以使用 while loop 调用协程函数,该函数将开始移动 Sprite,并且在该协程内,您可以平滑 Sprite 的移动使用Time.deltaTime 或任何其他首选方法。

由于OnBeginDrag 被调用一次,它是启动 协程 的好地方。 随着玩家继续拖动 Sprite,OnDrag 将被重复调用。使用OnDrag 函数获取查找器的当前位置并将其更新为Vector3已经 运行的协程将使用该Vector3更新 >Sprite 的位置。当玩家停止在屏幕上移动他们的手指/鼠标时,OnEndDrag 被调用,您可以通过boolean 变量告诉协程停止更新雪碧。然后,当玩家松开他们的手指(OnPointerUp)时,您可以使用 StopCoroutine 函数停止协程。

由于OnBeginDrag,我们可以在拖动开始时启动协程,同时等待拖动结束。在OnPointerDown开始该协程没有意义,因为这意味着每次玩家触摸屏幕上,一个协程将被启动

如果没有OnBeginDrag,我们必须在每次调用的OnDrag函数中使用boolean变量使协程只启动一次,否则协程会到处跑,Sprite会发生意外的移动.

4. 当您想确定玩家手指移动了多长时间时。这方面的例子是名为水果忍者的著名游戏。假设您想确定玩家在屏幕上滑动的距离。

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

如果您改为使用OnPointerDown 作为获取手指第一个 位置的位置,您将得到错误结果是因为如果玩家向右滑动,然后等待向左滑动,然后再次等待向上滑动 每次滑动后不松开手指,您获得的唯一良好结果就是第一次滑动向右滑动)。 向左向上滑动将具有无效值,因为您在OnPointerDown调用时获得的第一个值是您仍在使用的值。这是因为玩家从未将手指从屏幕上移开,因此因此OnPointerDown从未再次被调用,并且是第一个旧的旧值仍然存在。

但是当你使用OnBeginDrag而不是OnPointerDown时,这个问题将消失,因为当手指停止移动时,OnEndDrag被调用并且当它再次开始移动 OnBeginDrag 再次被调用,导致第一个 位置新位置覆盖

【讨论】:

  • "不会被称为..."配对:惊人的点,好一个!
  • 我意识到我的问题有点不切实际,所以我把它关闭了! @Programmer 在这里提出的观点...... “如果没有调用 OnBeginDrag,则不会调用 OnEndDrag。它就像花括号......” 等等,似乎成为此 QA 的关键洞察力。谢谢大家!
  • 没问题乔。很高兴我能提供帮助。
  • 老兄@programmer 你好吗。我一直在想,最近,如果您只是想检测屏幕上任意位置的触摸(例如滑动),那么最好的方法是什么?令人惊奇的是,在 Unity 中如此基本的东西却如此模糊。你有什么看法?
  • 我的意思是,我通常必须在相机下方(即随相机移动)添加一个空对撞机(只是一个扁平的盒子)来检测设备屏幕上的触摸。我的意思是在普通的 3D 游戏中,摄像机在移动。看起来很不雅。
【解决方案2】:

不同之处在于OnBeginDrag 直到触摸/鼠标移动了某个最小距离(拖动阈值)才会被调用。您可以在 Event System 组件上设置拖动阈值。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-10
    • 1970-01-01
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多