Ужасы OnPointerDown против OnBeginDrag в Unity3D

Я обеспокоен разницей между OnPointerDown против OnBeginDrag в коде движения одним пальцем.

(В последней парадигме Unity об использовании физики raycaster: так, наконец, Unity будет должным образом игнорировать прикосновение к слою пользовательского интерфейса.

Итак, с 2015 года вы должны сделать следующее:

  1. Забудь про хрень традиционную Input или же Touches системы, которые являются дерьмом и даже Unity признают, являются совершенно бессмысленными дерьмом - так как они не работают

  2. Добавьте пустой игровой объект с обычно BoxCollider2D, вероятно, больше, чем экран. Сделайте слой "Draw" (настройки физики, он ни с чем не взаимодействует)

  3. Просто добавьте в камеру 2D или 3D физику raycaster. Событие маскирует ваш слой "Draw".

Сделайте скрипт, как показано ниже, и поместите его в "draw".

(Совет - не забудьте просто добавить EventSystem на сцену. Как ни странно, Unity не делает это автоматически для вас в некоторых ситуациях, но Unity делает это автоматически для вас в других ситуациях, поэтому это раздражает, если вы забудете!)

Но вот проблема.

Там должно быть какая-то тонкая разница между использованием OnPointerDown против OnBeginDrag (и соответствующие конечные вызовы). (Вы можете просто поменять действие в следующем примере кода.)

Естественно, Unity не дает никаких указаний на этот счет; следующий код прекрасно отвергает случайные захваты, а также безупречно игнорирует ваш уровень пользовательского интерфейса (наконец-то, спасибо 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;
    }
  }

2 ответа

Решение

Я хочу начать с того, что 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#, вы открываете его " { " и закрываете его " } ".

РАЗНИЦА: OnPointerDown будет вызываться один раз и сразу, когда палец / мышь находится на сенсорном экране. Ничего другого не произойдет, пока не появится движение мыши или палец на экране. OnBeginDrag будет вызван один раз, а затем OnDrag.

Они предназначены для расширенного использования, такого как пользовательский интерфейс с элементами управления, которые не включены в Unity.

КОГДА ИСПОЛЬЗОВАТЬ КАЖДОГО ОДНОГО:

1. Когда вам нужно реализовать простую кнопку, например, "Вверх", "Вниз", "Стрелять" на экране, вам нужно только OnPointerDown обнаружить прикосновение. Это должно работать для Sprite Images.

2. Когда вам нужно реализовать собственный переключатель, и вы хотите, чтобы он был реалистичным, чтобы игрок мог перетаскивать его влево / вправо или вверх / вниз, чтобы переключить его, тогда вам нужно OnPointerDown, OnBeginDrag, OnDrag, OnEndDrag, OnPointerUp, Вам нужно написать свой код в этом порядке, чтобы на экране был плавный переход Sprite/Texture. Некоторые тумблеры предназначены для нажатия, и он переключается. Некоторые люди предпочитают, чтобы это выглядело реалистично, сделав так, чтобы вам приходилось перетаскивать его, чтобы переключать.

3. Также, если вы хотите реализовать универсальное всплывающее окно многократного использования, которое можно перетаскивать, вам также необходимо использовать эти 5 функций (OnPointerDown, OnBeginDrag, OnDrag, OnEndDrag, OnPointerUp). Сначала определите, когда есть щелчок (OnPointerDown), убедитесь, что выбранный спрайт является тем, который вы хотите переместить. Подождите, пока игрок переместится (OnBeginDrag) их палец / мышь. Как только они начнут перетаскивать, возможно, вы можете вызвать функцию сопрограммы с while цикл, который начнет движение спрайта и внутри этой сопрограммы, вы можете сгладить движение спрайта с Time.deltaTime или любой другой предпочтительный метод.

поскольку OnBeginDrag называется один раз, это хорошее место, чтобы начать сопрограмму. Как игрок продолжает тянуть спрайт, OnDrag будет вызываться повторно. Использовать OnDrag функция, чтобы получить текущее местоположение искателя и обновить его до Vector3 что сопрограмма, которая уже работает, будет использовать для обновления позиции спрайта. Когда игрок перестает двигать пальцем / мышью по экрану, OnEndDrag называется и вы можете boolean переменная и скажите сопрограмме прекратить обновление позиции Sprite. Затем, когда игрок отпускает палец (OnPointerUp) затем вы можете остановить сопрограмму с помощью функции StopCoroutine.

Потому что OnBeginDrag мы можем запустить сопрограмму после начала перетаскивания в ожидании завершения перетаскивания. Не было бы смысла начинать эту сопрограмму в OnPointerDown потому что это означает, что каждый раз, когда игрок касается экрана, запускается сопрограмма.

Без OnBeginDrag мы должны использовать boolean переменная, чтобы запуск сопрограммы только один раз в OnDrag функция, которая вызывается каждый раз, иначе везде будет работать сопрограмма, и произойдет неожиданное движение Спрайта.

4. Когда вы хотите определить, как долго игрок двигал пальцем. Примером тому является та известная игра под названием Fruit Ninja. Скажем так, вы хотите определить, насколько далеко игрок ударил по экрану.

Сначала подожди OnPointerDown называется, подождите еще раз, пока OnBeginDrag называется, то вы можете получить текущее положение пальца внутри OnBeginDrag функция, потому что OnBeginDrag вызывается до того, как палец начнет двигаться. После того, как палец отпущен, OnEndDrag называется. Затем вы можете получить текущее положение пальца снова. Вы можете использовать эти две позиции, чтобы проверить, насколько далеко сдвинулся палец, вычитая их.

Если вы вместо этого решили использовать OnPointerDown в качестве места, где можно получить первую позицию пальца, вы получите неверный результат, потому что если игрок проведет пальцем вправо, затем ждет и проведет пальцем влево, затем снова ждет и проводит пальцем вверх, не отпуская палец после каждого удара, - единственный хороший результат, который у вас есть это первый удар (правый удар). Влево и вверх будет иметь недопустимые значения, потому что это первое значение, которое вы получили, когда OnPointerDown было названо значение, которое вы все еще используете. Это потому, что игрок никогда не убирал свой палец с экрана, поэтому OnPointerDown больше никогда не вызывается, и первое старое старое значение все еще там.

Но когда вы используете OnBeginDrag вместо OnPointerDown эта проблема исчезнет, потому что, когда палец перестает двигаться, OnEndDrag называется, и когда он начинает двигаться снова OnBeginDrag вызывается еще раз, в результате чего первая позиция перезаписывается новой.

Разница в том, что OnBeginDrag не вызывается до тех пор, пока касание / мышь не переместятся на определенное минимальное расстояние, порог перетаскивания. Вы можете установить порог перетаскивания в компоненте Event System.

Это необходимо, когда у вас есть иерархия объектов с различными способами обработки ввода, особенно прокрутки. Представьте, что у вас есть вид прокрутки с вертикальным стеком ячеек, в каждой из которых есть кнопка. Когда касание начинается с одной из кнопок, мы не знаем, нажал ли пользователь кнопку или перетащил просмотр прокрутки. До тех пор, пока касание не достигнет порога сопротивления, мы знаем, что это перетаскивание, а не касание.

Другие вопросы по тегам