0评论

UGUI实现按住拖拽UI功能

文章来自https://blog.csdn.net/qq_33337811/article/details/78450071 2019-02-22 503浏览

想免费获取内部独家PPT资料库?观看行业大牛直播?点击加入腾讯游戏学院游戏美术行业精英群167422913

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

环境:Unity5.5,UGUI
注:(这里讨论的是overlay camera 2DUI,UGUI的WorldSpace 3DUI可能会有点不同)

功能需求如下:

按住一个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当前所在像素坐标的鼠标位置(屏幕坐标)

屏幕坐标:三维坐标,屏幕或窗口的左下角是坐标系的(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;
	}
}