Пользовательский интерфейс Unity3D, расчет положения, перетаскивающего элемент?

В наши дни перетаскивать элементы пользовательского интерфейса в Unity невероятно просто: создайте несколько элементов пользовательского интерфейса. Добавить компонент -> Событие -> Триггер события. Скиньте скрипт ниже. Нажмите, чтобы добавить четыре очевидных триггера. Готово.

Однако.

Я совершенно не понимаю отношения между координатами указателя и координатами пользовательского интерфейса (как видно в RectTransform и т. д.).

В DragIt ниже: как, черт возьми, правильно перемещать панель пользовательского интерфейса под пальцем?

Скажем, у вас есть одна большая панель с десятью UIButton, расположенными на панели с Dragster на кнопках. Какая связь между координатами RectTransform и указателем мыши...

Короче говоря, как вы перемещаете одну из кнопок в DragIt () ниже?

/* modern Unity drag of UI element */
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class Dragster:MonoBehaviour
    {
    public int index; // number each of your UI items
    static bool beingDragged = false;
    static int dragFrom;
    public void DragStart()
        {
        beingDragged = true; dragFrom = index;
        }
    public void DragIt()
        {
        ? ? W T F ? ?
        }
    public void DragEnd()
        {
        beingDragged = false;
        }
    public void DroppedBra()
        {
        Debig.Log("Drag: from/to " +dragFrom +" --> " +index);
        }
    }

person Fattie    schedule 27.05.2016    source источник
comment
Для преобразования данных указателя в координаты пользовательского интерфейса вы можете заглянуть в RectTransformUtilty.   -  person Vinod Vijayan    schedule 27.05.2016
comment
Итак, набрав все это, я снова прочитал это и понял, что вы прикрепили Dragster к каждому пользовательскому интерфейсу, что упростило кодирование. Мое решение было совершенно другим. Он использует только один скрипт для обнаружения любой панели/изображения и кнопки, а затем перетаскивает их. Вы не прикрепляете его к каждому пользовательскому интерфейсу каждого пользовательского интерфейса. Он должен быть прикреплен к родительскому холсту. Это все отходы. Простите меня за потраченное вами время. Я прочитаю вопрос прямо в следующий раз....   -  person Programmer    schedule 27.05.2016
comment
Это полезно только в том случае, если вы хотите использовать только один скрипт. В противном случае это пустая трата вашего времени.   -  person Programmer    schedule 27.05.2016
comment
Спасибо. Я не знаю, что вы имеете в виду под И ВЫ РАЗУМАЛИ, КАК КОНВЕРТИРОВАТЬ.   -  person Programmer    schedule 28.05.2016
comment
Разве компонент ScrollRect не делает то, что вы хотите?   -  person Everts    schedule 28.05.2016
comment
Ну тогда да, совсем не то. Неверно понял, что просто кнопка должна двигаться.   -  person Everts    schedule 28.05.2016
comment
Если вам когда-нибудь понадобится перетаскивание, код ниже в ответе Колтона будет удивительно полезен. Ваше здоровье   -  person Fattie    schedule 28.05.2016


Ответы (4)


Для перетаскивания я просто делаю это:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class Draggable : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler {
    
    
    
    public void OnBeginDrag(PointerEventData eventData) {
        
    }
    
    public void OnDrag(PointerEventData eventData) {
        //Debug.Log ("OnDrag");
        
        this.transform.position = eventData.position;

        }

    public void OnEndDrag(PointerEventData eventData) {
        Debug.Log ("OnEndDrag");
    
    }
}

Вот такой же удивительный КОД УРИПОПОВА с двумя простыми функциями, которые вам всегда нужны при перетаскивании:

// allow dragging with two basic problem fixes:
// - limit drag to the parent box
// - don't "jump" based on where you hold down

100% протестировано:

using UnityEngine;
using UnityEngine.EventSystems;

public class AmazingUPDrag : MonoBehaviour,
               IBeginDragHandler, IDragHandler, IEndDragHandler
{
    Vector2 dragOffset = Vector2.zero;
    Vector2 limits = Vector2.zero;

    public void OnBeginDrag(PointerEventData eventData)
    {
        dragOffset = eventData.position - (Vector2)transform.position;
        limits = transform.parent.GetComponent<RectTransform>().rect.max;
    }

    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position - dragOffset;
        var p = transform.localPosition;
        if (p.x < -limits.x) { p.x = -limits.x; }
        if (p.x > limits.x) { p.x = limits.x; }
        if (p.y < -limits.y) { p.y = -limits.y; }
        if (p.y > limits.y) { p.y = limits.y; }
        transform.localPosition = p;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        dragOffset = Vector2.zero;
    }
}
person Uri Popov    schedule 27.05.2016
comment
@JoeBlow подождите, вы хотите перетаскивать элементы, не относящиеся к пользовательскому интерфейсу, или элементы пользовательского интерфейса? - person Uri Popov; 27.05.2016
comment
@JoeBlow Я создал совершенно пустую сцену. Не снимать. Просто холст, панель на холсте, кнопка на панели и объект EventSystem. Мой код отлично перетаскивает кнопку. Некоторые странные графические глюки из-за отсутствия камеры, но перетаскивание в порядке. - person Uri Popov; 27.05.2016
comment
@JoeBlow правильно, я отказался от этого и просто использую то, что работает. - person Uri Popov; 27.05.2016
comment
Я просто хочу подтвердить всем читателям, что это идеально работает — три ура. - person Fattie; 27.05.2016
comment
это действительно работает для элементов пользовательского интерфейса, только прикрепите его к объекту, и он будет двигаться - person Geomorillo; 18.03.2017

Я бы сделал ваш скрипт реализующим интерфейсы перетаскивания

public class Dragster:MonoBehaviour,IBeginDragHandler, IEndDragHandler, IDragHandler

Что заставит вашу функцию DragIt стать

public void OnDrag(PointerEventData eventData)
{
    transform.position += (Vector3)eventData.delta;
}

предоставляя вам доступ к дельте этого события (насколько сдвинулась мышь), чтобы иметь возможность перемещать ваш объект.

Если вы все же предпочитаете использовать компонент EventTrigger (менее предпочтительный способ), вам просто нужно изменить функцию DragIt на DragIt(PointerEventData eventData) и использовать параметр Dynamic EvenData в раскрывающемся списке, чтобы триггер получал PointerEventData для доступа к дельта-информации.


Вот на самом деле полное, полное решение для перетаскивания элементов UnityEngine.UI, основанное на коде Ури и Колтона. Просто скопируйте и вставьте.

Поразительное копирование и вставка без особых усилий, идеальное перетаскивание для пользовательского интерфейса Unity, wtt Colton & Uri:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UNCDraggable:MonoBehaviour,
IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler
    {
    public Image ghost;
    // note DON'T try to drag the actual item: it's not worth the hassle.
    // a problem arises where you can't have it on top (as you would want
    // visually), and still easily get the drops. always use a ghost.
    // even if you want the "original invisible" while dragging,
    // simply hide it and use a ghost. everything is tremendously
    // easier if you do not move the originals.
    
    void Awake()
        {
        ghost.raycastTarget = false;
        // (just in case you forgot to do that in the Editor)
        ghost.enabled = false;
        }
    
    public void OnBeginDrag(PointerEventData eventData)
        {
        ghost.transform.position = transform.position;
        ghost.enabled = true;
        }

    public void OnDrag(PointerEventData eventData)
        {
        ghost.transform.position += (Vector3)eventData.delta;
        }

    public void OnEndDrag(PointerEventData eventData)
        {
        ghost.enabled = false;
        }
    
    public void OnDrop(PointerEventData data)
        {
        GameObject fromItem = data.pointerDrag;
        if (data.pointerDrag == null) return; // (will never happen)
        
        UNCDraggable d = fromItem.GetComponent<UNCDraggable>();
        if (d == null)
          {
          // means something unrelated to our system was dragged from.
          // for example, just an unrelated scrolling area, etc.
          // simply completely ignore these.
          return;
          // note, if very unusually you have more than one "system"
          // of UNCDraggable items on the same screen, be careful to
          // distinguish them! Example solution, check parents are same.
          }
        
        Debug.Log ("dropped  " + fromItem.name +" onto " +gameObject.name);
        
        // your code would look probably like this:
        YourThings fromThing = fromItem.GetComponent<YourButtons>().info;
        YourThings untoThing = gameObject.GetComponent<YourButtons>().info;
        
        yourBossyObject.dragHappenedFromTo(fromThing, untoThing);
        }
    }
person Colton White    schedule 27.05.2016
comment
Также @JoeBlow, да, система событий единства привязана к системе пользовательского интерфейса. используя дельту, а не просто устанавливая позицию преобразования, вы сохраняете смещение до того места, где пользователь на самом деле щелкнул, поэтому, если они щелкнут один угол, а ваша точка поворота находится в другом углу, она перейдет к этой новой позиции. - person Colton White; 27.05.2016
comment
@JoeBlow Попробовал ответ Ури, и он работает. Это сработало. Тебе все еще нужен мой ответ? Я тоже решил некоторые проблемы, лол - person Programmer; 27.05.2016
comment
@ColtonWhite Ваш код не скомпилировался. transform.position += eventData.delta; не сработало. Я изменил его и удалил знак +, и пользовательский интерфейс даже не перемещался в правильное положение. Он прыгнул в самый низ экрана. Однако ответ Ури сработал. Может я что не так делал... - person Programmer; 27.05.2016
comment
@Programmer да, извините, я не осознавал, что это невозможно будет добавить напрямую без актерского состава, я написал свой ответ, не открывая перед собой редактор. JoeBlow исправил мою ошибку компилятора, что должно исправить проблему, и она должна работать правильно. - person Colton White; 27.05.2016
comment
@Programmer, который, кажется, работает для меня: ему действительно нужен приведение, иначе вы получите предупреждение о Vector3 / Vector2 ... ghost.transform.position += (Vector3)eventData.delta; - person Fattie; 27.05.2016
comment
Это должно быть одно из самых полезных QA во всем Интернете. - person Fattie; 27.05.2016
comment
@JoeBlow Лол, хорошо. Все еще печатаю - person Programmer; 27.05.2016
comment
если вы, ребята, следите за этим, я добавил небольшое исправление в код в OnDrop. Потрясающий. - person Fattie; 03.06.2016

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

Моей официальной целью было предоставить способ сделать это без использования bool beingDragged = false;. Вы просто не будете знать, какой Button или Image перетаскивается, если вы сделаете это так.

Перетаскивание пользовательского интерфейса:

Преобразуйте точку экрана в локальную точку в RectTransform с помощью RectTransformUtility, затем используйте Canvas.transform.TransformPoint, чтобы узнать, где именно находится дочерний пользовательский интерфейс.

public Canvas parentCanvasOfImageToMove;
Vector2 pos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvasOfImageToMove.transform as RectTransform, eventData.position, parentCanvasOfImageToMove.worldCamera, out pos);
UIToMove.transform.position = parentCanvasOfImageToMove.transform.TransformPoint(pos);

Код перетаскивания выглядит более сложным, чем другой код перетаскивания в других ответах, но, похоже, он работает в каждом режиме камеры Canvas.

Определение того, какой объект будет перетаскиваться:

Самый простой способ сделать это — создать глобальную переменную, которую вы можете использовать для сохранения объекта, который пользователь хочет перетащить в функцию OnBeginDrag, после чего вы можете перетащить этот объект в файл OnDrag. Присвойте этому объекту значение null при вызове OnEndDrag.

objectToBeDragged = eventData.pointerCurrentRaycast.gameObject;

Это нужно сделать один раз в функции OnBeginDrag, а затем сохранить в глобальную переменную.

Вы не можете сделать следующее в функции OnDrag

if (eventData.pointerCurrentRaycast.gameObject == someOtherUI)
{
   someOtherUI....drag
}

Несмотря на то, что он должен работать, иногда это не так. Иногда он даже возвращает null во время OnDrag. Поэтому это нужно делать в функции OnBeginDrag.

Обнаружение и перетаскивание кнопки по сравнению с изображением:

Определить, является ли пользовательский интерфейс просто Image и перетаскиванием Image, очень просто.

objectToBeDragged  = eventData.pointerCurrentRaycast.gameObject;
Button tempButton = objectToBeDragged.GetComponent<Button>();
Image tempImage = objectToBeDragged.GetComponent<Image>();

Если tempImage не null, а tempButton равно null, то это Изображение.

Определить, является ли пользовательский интерфейс просто Button и перетаскиванием Button, НЕ легко. Когда кнопка нажимается на стороне/крае, возвращается имя Button, что нормально. Но в большинстве случаев щелчок по Button происходит в середине Button, что не возвращает экземпляр или имя кнопки, а вместо этого возвращает Text(дочерний элемент). Объект). Вы НЕ МОЖЕТЕ перемещать текст как кнопку. Это не сработает.

objectToBeDragged  = eventData.pointerCurrentRaycast.gameObject;
Button tempButton = objectToBeDragged.GetComponent<Button>();
Image tempImage = objectToBeDragged.GetComponent<Image>();
Text tempText = objectToBeDragged.GetComponent<Text>();

если tempText не равно null, получите GetComponentInParent компонента изображения и кнопки текста. Если Image не равно нулю, а Button не равно нулю, то это Button.

if (tempText != null)
{
    tempButton = tempText.GetComponentInParent<Button>();
    tempImage = tempText.GetComponentInParent<Image>();
    if (tempButton != null && tempImage != null)
    {
        //This is a Button
    }
}

Ниже приведен полный скрипт перетаскивания изображения/панели и кнопки пользовательского интерфейса. Любая кнопка, которую нужно перетащить, должна быть помещена в массив UIButtons, а любая панель/изображение, которые нужно перетащить, должна быть помещена в массив UIPanels. Он будет игнорировать другой пользовательский интерфейс, которого нет в массиве.

public class UIDRAGGER : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public Canvas parentCanvasOfImageToMove;

    //10 UI Buttons (Assign in Editor)
    public Button[] UIButtons;

    //2 UI Panels/Images (Assign in Editor)
    public Image[] UIPanels;

    //Hold which Button or Image is selected
    private Button selectedButton;
    private Image selectedUIPanels;

    //Used to make sure that the UI is position exactly where mouse was clicked intead of the default center of the UI
    Vector3 moveOffset;

    //Used to decide which mode we are in. Button Drag or Image/Panel Mode
    private DragType dragType = DragType.NONE;


    void Start()
    {
        parentCanvasOfImageToMove = gameObject.GetComponent<Canvas>();
    }

    //Checks if the Button passed in is in the array
    bool buttonIsAvailableInArray(Button button)
    {
        bool _isAValidButton = false;
        for (int i = 0; i < UIButtons.Length; i++)
        {
            if (UIButtons[i] == button)
            {
                _isAValidButton = true;
                break;
            }
        }
        return _isAValidButton;
    }

    //Checks if the Panel/Image passed in is in the array
    bool imageIsAvailableInArray(Image image)
    {
        bool _isAValidImage = false;
        for (int i = 0; i < UIPanels.Length; i++)
        {
            if (UIPanels[i] == image)
            {
                _isAValidImage = true;
                break;
            }
        }
        return _isAValidImage;
    }

    void selectButton(Button button, Vector3 currentPos)
    {
        //check if it is in the image array that is allowed to be moved
        if (buttonIsAvailableInArray(button))
        {
            //Make the image the current selected image
            selectedButton = button;
            dragType = DragType.BUTTONS;
            moveOffset = selectedButton.transform.position - currentPos;
        }
        else
        {
            //Clear the selected Button
            selectedButton = null;
            dragType = DragType.NONE;
        }
    }

    void selectImage(Image image, Vector3 currentPos)
    {
        //check if it is in the image array that is allowed to be moved
        if (imageIsAvailableInArray(image))
        {
            //Make the image the current selected image
            selectedUIPanels = image;
            dragType = DragType.IMAGES;
            moveOffset = selectedUIPanels.transform.position - currentPos;
        }
        else
        {
            //Clear the selected Button
            selectedUIPanels = null;
            dragType = DragType.NONE;
        }
    }


    public void OnBeginDrag(PointerEventData eventData)
    {
        GameObject tempObj = eventData.pointerCurrentRaycast.gameObject;

        if (tempObj == null)
        {
            return;
        }

        Button tempButton = tempObj.GetComponent<Button>();
        Image tempImage = tempObj.GetComponent<Image>();
        Text tempText = tempObj.GetComponent<Text>();

        //For Offeset Position
        Vector2 pos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvasOfImageToMove.transform as RectTransform, eventData.position, parentCanvasOfImageToMove.worldCamera, out pos);


        //Button must contain Text then Image and Button as parant
        //Check if this is an image
        if (tempButton == null || tempImage == null)
        {
            //Button not detected. Check if Button's text was detected
            if (tempText != null)
            {
                //Text detected. Now Look for Button and Image in the text's parent Object
                tempButton = tempText.GetComponentInParent<Button>();
                tempImage = tempText.GetComponentInParent<Image>();

                //Since child is text, check if parents are Button and Image
                if (tempButton != null && tempImage != null)
                {
                    //This is a Button
                    selectButton(tempButton, parentCanvasOfImageToMove.transform.TransformPoint(pos));
                }
                //Check if there is just an image
                else if (tempImage != null)
                {
                    //This is an Image
                    selectImage(tempImage, parentCanvasOfImageToMove.transform.TransformPoint(pos));
                }
            }
            else
            {
                //This is an Image
                selectImage(tempImage, parentCanvasOfImageToMove.transform.TransformPoint(pos));
            }
        }
        //Check if there is just an image
        else if (tempImage != null)
        {
            selectImage(tempImage, parentCanvasOfImageToMove.transform.TransformPoint(pos));
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        Vector2 pos;
        if (dragType == DragType.BUTTONS)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvasOfImageToMove.transform as RectTransform, eventData.position, parentCanvasOfImageToMove.worldCamera, out pos);
            selectedButton.transform.position = parentCanvasOfImageToMove.transform.TransformPoint(pos) + moveOffset;
        }
        else if (dragType == DragType.IMAGES)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvasOfImageToMove.transform as RectTransform, eventData.position, parentCanvasOfImageToMove.worldCamera, out pos);
            selectedUIPanels.transform.position = parentCanvasOfImageToMove.transform.TransformPoint(pos) + moveOffset;
        }
    }


    public void OnEndDrag(PointerEventData eventData)
    {
        //Buttons
        if (dragType == DragType.BUTTONS || dragType == DragType.IMAGES)
        {
            selectedButton = null;
            selectedUIPanels = null;
            dragType = DragType.NONE;
        }
    }

    DragType getCurrentDragType()
    {
        return dragType;
    }

    private enum DragType { NONE, BUTTONS, IMAGES };
}
person Programmer    schedule 27.05.2016
comment
похоже, это не работает - это значение всегда равно 0, несмотря на нетривиальное перетаскивание parentCanvasOfImageToMove.transform.TransformPoint(pos) - person ina; 17.06.2018
comment
@ina это значение всегда равно 0. Какое значение? Было бы хорошо объяснить, что вы делаете и как это не работает - person Programmer; 18.06.2018
comment
@programmer parentCanvasOfImageToMove.transform.TransformPoint(pos) и pos всегда равны 0 век? - person ina; 18.06.2018
comment
неважно, я только что понял, что все зависит от назначения worldCamera. добавлен if(worldCamera==null) worldCamera=Camera.main - person ina; 18.06.2018

Итак, первое решение (решения) работает для элементов пользовательского интерфейса (Overlay Canvas). Решение от @programmer также работает для других полотен, но имеет некоторые накладные расходы для моего использования. Итак, здесь я извлекаю то, что, по моему мнению, является минимумом, необходимым для перетаскивания вашего текущего объекта.

using UnityEngine.EventSystems;

public class SomeObject : MonoBehaviour, IBeginDragHandler, IDragHandler
{
  private Canvas parentCanvas;
  private Vector3 moveOffset;

  void Start()
  {
      // get the "nearest Canvas"
      parentCanvas = GetComponentsInParent<Canvas>()[0];
      // you could get the Camera of this Canvas here as well and like @ina suggests add a null check for that and otherwise use Camera.Main
  }
  public void OnBeginDrag(PointerEventData eventData)
  {

      //For Offset Position so the object won't "jump" to the mouse/touch position
      RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvas.transform as RectTransform, eventData.position, parentCanvas.worldCamera, out Vector2 pos);
      moveOffset = transform.position - parentCanvas.transform.TransformPoint(pos);

  }

  public void OnDrag(PointerEventData eventData)
  {
      RectTransformUtility.ScreenPointToLocalPointInRectangle(parentCanvas.transform as RectTransform, eventData.position, parentCanvas.worldCamera, out Vector2 pos);
      transform.position = parentCanvas.transform.TransformPoint(pos) + moveOffset;
  }
}

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

person Niek    schedule 15.06.2021