【问题标题】:Unity3D UI, calculation for position dragging an item?Unity3D UI,计算拖动项目的位置?
【发布时间】:2016-05-27 02:19:58
【问题描述】:

如今,在 Unity 中拖动 UI 元素非常容易:制作一些 UI 项。添加组件 -> 事件 -> 事件触发器。放在下面的脚本上。单击以添加四个明显的触发器。你已经完成了。

但是。

我完全迷失在 指针坐标UI 坐标 之间的关系(如 RectTransform 等)。

在下面的DragIt 中:你到底是如何在手指下正确移动 UI 面板的?

假设您有一个大面板,面板中有十个 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);
        }
    }

【问题讨论】:

  • 要将指针数据转换为 UI 坐标,您可以查看 RectTransformUtilty。
  • 所以在输入所有这些之后,我再次阅读并意识到您将Dragster 附加到每个用户界面,这使得编码更容易。我的解决方案完全不同。它仅使用一个脚本来检测任何面板/图像和按钮,然后拖动它们。您不会将它附加到每个 UI 的每个 UI。它必须附加到父 Canvas。这都是浪费。原谅我浪费你的时间。下次我会读这个问题....
  • 仅当您只想使用一个脚本时才有用。否则就是浪费你的时间。
  • 谢谢。我不知道您所说的 是什么意思,并且您已经弄清楚如何转换
  • ScrollRect 组件不是在做你想做的事吗?

标签: c# unity3d unity3d-ui


【解决方案1】:

我会让你的脚本实现拖动接口

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 到访问增量信息


这实际上是一个基于 Uri 和 Colton 代码的拖放“UnityEngine.UI”项目的完整、完整的解决方案。只需复制和粘贴即可。

用于 Unity UI、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);
        }
    }

【讨论】:

  • 另外@JoeBlow 是的,统一事件系统绑定到 UI 系统中。通过使用增量而不只是设置变换位置,您可以将偏移量保持在用户实际单击的位置,因此如果他们单击一个角而您的枢轴位于另一个角,它将跳转到该新位置。
  • @JoeBlow 尝试了 Uri 的答案,它有效。它确实奏效了。你还需要我的回答吗?我也解决了一些问题哈哈
  • @ColtonWhite 您的代码未编译。变换.位置 += 事件数据.delta;不工作。我更改了它并删除了 + 号,它的 UI 甚至没有移动到正确的位置。它跳到屏幕的底部。乌里的回答虽然有效。也许我做错了......
  • @Programmer 是的,对不起,我没有意识到没有演员表就无法直接添加它,我在没有在我面前打开编辑器的情况下写了我的答案。 JoeBlow 确实修复了我的编译器错误,但这应该可以解决问题并且应该可以正常工作。
  • @Programmer 似乎对我有用:它确实需要演员表,否则你会收到有关 Vector3/Vector2 的警告...ghost.transform.position += (Vector3)eventData.delta;
【解决方案2】:

首先,这篇文章中的所有其他答案都运行良好。我为此工作了很长时间,只是想把它贴在这里。它添加了一种防止其他不需要的 UI 对象被拖动的方法。

我的官方目标是提供一种不使用bool beingDragged = false; 的方法。如果你那样做,你就不会知道哪个ButtonImage 被拖动了。

拖动界面

RectTransformUtility 的帮助下将 Screenpoint 转换为 RectTransform 中的本地点,然后使用 Canvas.transform.TransformPoint 找出子 UI 的确切位置。

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 中拖动该对象。在调用 OnEndDrag 时将该对象设置为 null。

objectToBeDragged = eventData.pointerCurrentRaycast.gameObject;

这必须在OnBeginDrag 函数中完成一次,然后保存到全局变量中。

您不能在OnDrag 函数中执行以下操作

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

即使它应该起作用,但有时却不起作用。它甚至在 OnDrag 期间有时会返回 null。这就是为什么它必须在 OnBeginDrag 函数中完成。

检测和拖动按钮与图像

检测 UI 是否只是 Image 并拖动 Image 非常容易。

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

如果tempImage不是 null 并且tempButtonnull,那么这是一个图像。

检测 UI 是否只是 Button 并拖动 Button不容易。 当在 side/edge 上单击 Button 时,将返回 Button 的名称,这很好。但大多数时候,点击Button 发生在Button中间,它返回按钮的实例或名称,而是返回Text(子对象)。您不能将文本作为按钮移动。它不会工作。

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

如果tempTextnot null,则获取Text 的Image 和Button 组件的GetComponentInParent。如果Image 不为空且Button 不为空,则它是Button

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

下面是拖拽 UI Image/Panel 和 Button 的完整脚本。任何应该拖动的按钮都应该放在UIButtons 数组中,任何应该拖动的面板/图像都应该放在UIPanels 数组中。它将忽略不在数组中的其他 UI。

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

【讨论】:

  • 这似乎不起作用 - 尽管有非平凡的拖动 parentCanvasOfImageToMove.transform.TransformPoint(pos),但该值似乎始终为 0
  • @ina "这个值好像总是0" 什么值?最好解释一下你在做什么以及它是如何不起作用的
  • @programmer parentCanvasOfImageToMove.transform.TransformPoint(pos)pos 似乎总是 0 vecs?
  • 没关系,我只是意识到一切都取决于分配的 worldCamera。添加了一个 if(worldCamera==null) worldCamera=Camera.main
【解决方案3】:

对于拖动的东西,我只是这样做:

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

这是同样令人惊叹的 URIPOPOV 代码,具有两个简单的功能,您在拖动时总是需要这些功能:

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

【讨论】:

  • @JoeBlow 等等,你想拖动非 UI 元素还是 UI 元素?
  • @JoeBlow 我创建了一个完全空的场景。没有相机。只是一个画布,画布中的一个面板,面板中的一个按钮和一个 EventSystem 对象。我的代码可以很好地拖动按钮。缺少摄像头导致一些奇怪的图形故障,但拖动很好。
  • @JoeBlow 对,我已经放弃了,只做有效的。
  • 我只想向所有读者确认,这确实有效 - 三声欢呼。
  • 这个indead适用于ui元素只将它附加到对象上,它会移动
【解决方案4】:

好的,所以第一个解决方案适用于 UI 元素(覆盖画布) 来自@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;
  }
}

代码由从我的实际课程中抓取的行组成,因此它不是经过测试的脚本。当然你还需要一些用法

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-13
    • 2015-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多