7

我担心单指移动代码OnPointerDownOnBeginDrag单指移动代码之间的差异。

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

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

  1. 忘记那些毫无意义的废话并且不起作用的废话传统Input或系统Touches

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

  3. 只需将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场景省去打字,用鲜为人知但精致的

指针当前射线投射

这是如何...注意优秀

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

2 回答 2

27

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

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

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

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

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

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

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

OnPointerDownOnBeginDragOnEndDrag

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

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

这些是为进行高级使用而设计的,例如带有 Unity 中未包含的控件的自定义UI。

何时使用每个:

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

2.当你必须实现一个自定义切换开关并且你希望它是真实的,以便玩家可以拖动到左/右或上/下来切换它,那么你需要OnPointerDown, OnBeginDrag, OnDrag, OnEndDrag, OnPointerUp。您需要按此顺序编写代码才能在屏幕上实现平滑的 Sprite/Texture 过渡。一些拨动开关被制作成可以点击,它会切换。有些人更喜欢让它看起来更逼真,这样你就必须拖动它才能切换它。

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

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

因为OnBeginDrag我们,一旦拖动开始,我们就可以在等待拖动结束时启动协程。启动那个协程是没有意义的,因为意味着每次玩家触摸屏幕时,都会启动一个协程。OnPointerDown

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

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

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

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

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

于 2016-03-17T15:21:09.350 回答
2

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

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

于 2017-04-09T23:28:21.890 回答