环境:Unity5.5,UGUI

前言:(这里讨论的是overlay camera 2DUI,UGUI的WorldSpace 3DUI可能会有点不同)

Unity开发游戏中,有些功能玩法里会加入拖拽一个UI到另一个UI上,如拖拽一个技能池里的技能到玩家身上技能槽里。

仔细想想这里的功能需求:

按住一个UI,移动鼠标时拖拽它(可能新一个新的图标),跟随鼠标移动,松开时候根据鼠标的位置做不同的处理,如果在鼠标在技能槽范围内,则替换目标位置的技能(UI).


知识点:

1.UGUI里判断鼠标是否在指定UI范围内

(这里UI范围指UI的RectTransform的范围,可在Scene场景观察其大小)

RectTransformUtility 矩阵变换工具类 帮助我们实现了这个判断功能。

bool RectTransformUtility. RectangleContainsScreenPoint(RectTransform rect, Vector2 screenPoint, Camera cam)

参数1:目标矩形(目标UI的RectTransform组件) 

参数2:屏幕上的点二维坐标  

参数3:观察UI和这个点的相机

如果这个点在矩形范围内,则返回true。

现在我们的第一个参数可以确认,直接去目标UI身上的RectTransform组件,那第二个参数鼠标的位置二维坐标.


2.Input.mousePostion当前所在像素坐标的鼠标位置(屏幕坐标)Unity圣典

屏幕坐标:三维坐标,屏幕或窗口的左下角是坐标系的(0,0)坐标。右上角的坐标是(屏幕宽度值,屏幕高度值),z保持不变,向右x变大,向上y变大


取到这个三维坐标,我们新建一个二维坐标,分别去三维坐标的x,y值,得到的二维坐标就是我们要的第二个参数。

第三个参数便是显示这个UI的摄像机,然后我们就可以随时知道鼠标位置是否在指定UI范围了。


3.让UI显示在鼠标位置

前面知道了Input.mousePosition是屏幕坐标,但是UI的位置是世界坐标,所以我们把鼠标位置转换一下:

Camera.ScreenToWorldPoint(Vector3 position)


把屏幕坐标转为世界坐标(

Unity官方文档),相机为看这个UI的相机,位置参数传入鼠标屏幕坐标,得到鼠标位置在这个相机下世界坐标。

然后把这个值赋给要移动位置的UI的transform.postion


4.按下、松开

鼠标的按下松开用Input.GetMouseButtonDown 和 InPut.GetMouseButtonUp,好在Update中做逻辑判断(限制、验证)

UI按下可重写方法监听OnPointerDown,OnPointerUp,下面给出一个示例脚本。


现在我们再来理一下:

技能池中技能Icon按下,我们做完逻辑判断,知道这个技能可以拖拽,则下一步:

在鼠标位置显示一个Icon(可以是预先做好的默认隐藏的Image),设置Icon数据值,用上知识点3让Icon显示在鼠标位置;

然后在Update里去判断,如果这个Icon显示着(activeSelf == true或者做个变量标记),每帧更新他的位置到鼠标位置,实现拖拽功能;

然后在update中判断,鼠标抬起时Input.GetMouseButtonUp(0),移动的icon隐藏,移动icon结束,根据这时候鼠标的位置,用方法1判断鼠标是否在技能槽UI内,如果在,则执行替换技能逻辑;不在则不做处理。

注意:

1.上面按住技能显示icon这里,可以根据需要加上限制,如按住不直接出icon,而是按住后鼠标出了当前按住的技能icon范围后再显示,可以在按住的时候设置状态:当前按住的物体A给到一个变量,update中判断该变量不为空,那么现在是按下状态,在这基础上在加判断,如果鼠标不在这个物体UI范围,则把icon显示出来。

2.这种做法比较简单,注意按下和松开时候变量标记就好,好在Update中判断。

3.注意松开时候隐藏移动的Icon。

4.Update中判断,按下时候显示移动icon,鼠标松开时清除按住标记,隐藏显示的移动UI,根据鼠标位置做逻辑处理。

而物体UI的按下回调中(onDown),可以做标记,当前按下事件的自定义id好在松开处理逻辑时用,调用按下的其他事件(逻辑处理,显示这个技能的详细信息),根据情况处理逻辑判断技能能否拖拽以至于icon是否要显示,可以则给Icon数据赋值,显示icon好在Update中移动位置;否则不显示。

5.这种方法可以直接在lua里用(lua中使用update性能有待研究),下面会说一下官方demo中C#中封装使用的方法。

----------------------------------------------------------------

Unity Samples UI:Unity资源商店

这里只有两个重要脚本:DragMe DropMe,代码见下拓展2

我们可以看到这里的方法是继承的Drag Drop相关的接口,重写了他的一些方法。

测了一下发现,拖拽的时候稍微偏移一些才会有新icon出来,而我上面的做法是为了避免点击就出来,没有计算偏移,直接鼠标移除本UI才会出新icon,似乎想要它这种效果也可以加。

寄几看看用哪种吧,个人看着代码有点繁琐。

一般像界面上用RawImage渲染显示模型的,按住拖拽可左右旋转的,可以用onDrag获得x轴偏移量结合OnDown OnUp来做。

----------------------------------------------------------------

拓展:

1.示例 点击按下松开事件监听脚本:

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class EventTriggerListener : MonoBehaviour,IPointerClickHandler, IPointerDownHandler, IPointerEnterHandler,
IPointerExitHandler, IPointerUpHandler
{
	public delegate void VoidDelegate1(GameObject go, PointerEventData eventData);
	public event VoidDelegate1 OnClick;
    public event VoidDelegate1 OnDown;
	public event VoidDelegate1 OnEnter;
	public event VoidDelegate1 OnExit;
	public event VoidDelegate1 OnUp;

    static public EventTriggerListener Get(GameObject go)
    {
        EventTriggerListener listener = go.GetComponent<EventTriggerListener>();
        if (listener == null) listener = go.AddComponent<EventTriggerListener>();
        return listener;
    }
	public void OnPointerClick (PointerEventData eventData)
	{
		if (OnClick != null) OnClick(gameObject, eventData);
	}
    public void OnPointerDown(PointerEventData eventData)
    {
        if (OnDown != null) OnDown(gameObject, eventData);
    }
    public void OnPointerEnter(PointerEventData eventData)
    {
        if (OnEnter != null) OnEnter(gameObject, eventData);
    }
    public void OnPointerExit(PointerEventData eventData)
    {
        if (OnExit != null) OnExit(gameObject, eventData);
    }
    public void OnPointerUp(PointerEventData eventData)
    {
        if (OnUp != null) OnUp(gameObject, eventData);
    }
  
}



2.Unity Samples UI拖拽代码:

可拖拽的UI上挂的脚本:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public class DragMe : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
	public bool dragOnSurfaces = true;
	
	private Dictionary<int,GameObject> m_DraggingIcons = new Dictionary<int, GameObject>();
	private Dictionary<int, RectTransform> m_DraggingPlanes = new Dictionary<int, RectTransform>();

	public void OnBeginDrag(PointerEventData eventData)
	{
		var canvas = FindInParents<Canvas>(gameObject);
		if (canvas == null)
			return;

		// We have clicked something that can be dragged.
		// What we want to do is create an icon for this.
		m_DraggingIcons[eventData.pointerId] = new GameObject("icon");

		m_DraggingIcons[eventData.pointerId].transform.SetParent (canvas.transform, false);
		m_DraggingIcons[eventData.pointerId].transform.SetAsLastSibling();
		
		var image = m_DraggingIcons[eventData.pointerId].AddComponent<Image>();
		// The icon will be under the cursor.
		// We want it to be ignored by the event system.
		var group = m_DraggingIcons[eventData.pointerId].AddComponent<CanvasGroup>();
		group.blocksRaycasts = false;

		image.sprite = GetComponent<Image>().sprite;
		image.SetNativeSize();
		
		if (dragOnSurfaces)
			m_DraggingPlanes[eventData.pointerId] = transform as RectTransform;
		else
			m_DraggingPlanes[eventData.pointerId]  = canvas.transform as RectTransform;
		
		SetDraggedPosition(eventData);
	}

	public void OnDrag(PointerEventData eventData)
	{
		if (m_DraggingIcons[eventData.pointerId] != null)
			SetDraggedPosition(eventData);
	}

	private void SetDraggedPosition(PointerEventData eventData)
	{
		if (dragOnSurfaces && eventData.pointerEnter != null && eventData.pointerEnter.transform as RectTransform != null)
			m_DraggingPlanes[eventData.pointerId] = eventData.pointerEnter.transform as RectTransform;
		
		var rt = m_DraggingIcons[eventData.pointerId].GetComponent<RectTransform>();
		Vector3 globalMousePos;
		if (RectTransformUtility.ScreenPointToWorldPointInRectangle(m_DraggingPlanes[eventData.pointerId], eventData.position, eventData.pressEventCamera, out globalMousePos))
		{
			rt.position = globalMousePos;
			rt.rotation = m_DraggingPlanes[eventData.pointerId].rotation;
		}
	}

	public void OnEndDrag(PointerEventData eventData)
	{
		if (m_DraggingIcons[eventData.pointerId] != null)
			Destroy(m_DraggingIcons[eventData.pointerId]);

		m_DraggingIcons[eventData.pointerId] = null;
	}

	static public T FindInParents<T>(GameObject go) where T : Component
	{
		if (go == null) return null;
		var comp = go.GetComponent<T>();

		if (comp != null)
			return comp;
		
		var t = go.transform.parent;
		while (t != null && comp == null)
		{
			comp = t.gameObject.GetComponent<T>();
			t = t.parent;
		}
		return comp;
	}
}



可放置的UI上挂的脚本:

using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class DropMe : MonoBehaviour, IDropHandler, IPointerEnterHandler, IPointerExitHandler
{
	public Image containerImage;
	public Image receivingImage;
	private Color normalColor;
	public Color highlightColor = Color.yellow;
	
	public void OnEnable ()
	{
		if (containerImage != null)
			normalColor = containerImage.color;
	}
	
	public void OnDrop(PointerEventData data)
	{
		containerImage.color = normalColor;
		
		if (receivingImage == null)
			return;
		
		Sprite dropSprite = GetDropSprite (data);
		if (dropSprite != null)
			receivingImage.overrideSprite = dropSprite;
	}

	public void OnPointerEnter(PointerEventData data)
	{
		if (containerImage == null)
			return;
		
		Sprite dropSprite = GetDropSprite (data);
		if (dropSprite != null)
			containerImage.color = highlightColor;
	}

	public void OnPointerExit(PointerEventData data)
	{
		if (containerImage == null)
			return;
		
		containerImage.color = normalColor;
	}
	
	private Sprite GetDropSprite(PointerEventData data)
	{
		var originalObj = data.pointerDrag;
		if (originalObj == null)
			return null;
		
		var dragMe = originalObj.GetComponent<DragMe>();
		if (dragMe == null)
			return null;
		
		var srcImage = originalObj.GetComponent<Image>();
		if (srcImage == null)
			return null;
		
		return srcImage.sprite;
	}
}