环境: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;
}
}