拖动在游戏中使用频繁,例如将装备拖动到指定的快捷栏,或者大地图中拖动以查看局部信息等。

Unity的EventSystems中可以直接继承几个接口来实现拖动功能,如下:

namespace UnityEngine.EventSystems
{
public interface IBeginDragHandler : IEventSystemHandler
{
void OnBeginDrag(PointerEventData eventData);
}
}
namespace UnityEngine.EventSystems
{
public interface IDragHandler : IEventSystemHandler
{
void OnDrag(PointerEventData eventData);
}
}
namespace UnityEngine.EventSystems
{
public interface IEndDragHandler : IEventSystemHandler
{
void OnEndDrag(PointerEventData eventData);
}
}

他们分别代表拖动开始,持续和结束时的处理方法。然而遗憾的是,每有一个要拖动的物件对象,都需要重新写一遍如何去处理它们,而大部分时候拖动的功能都相对通用,一般就是根据你鼠标或者手指滑动的方向对应的移动物体的方向,只有在拖动结束的时候可能需要额外判断一下物体的状态,例如是不是在指定的范围内不是的话可能需要复位,是的话可能增加了某项属性或者完成了一些其他功能,这时才是因情况而异。基于这样的思考,考虑将一些通用的拖动实现过程再封装一下,只留一个拖动结束后的委托用于外部调用即可,这样省去了每次都写一遍地图拖动时如何移动,拖动到边界了如何判断等。

幸运的是,Unity在EventTrigger中已经包含了拖动的事件,具体如何动态添加EventTrigger的侦听可以详细见上一篇随笔的末尾处:

https://www.cnblogs.com/koshio0219/p/12808063.html

在上面的基础上参加一个新的扩展方法:

     public static void AddDragListener(this Canvas canvas, Component obj, DragMode mode, UnityAction complete, float speed = 1f)
{
var dragable = obj.gameObject.GetOrAddComponent<Dragable>();
dragable.Init(mode,speed); canvas.AddTriggerListener(obj, EventTriggerType.BeginDrag, dragable.OnBeginDrag);
canvas.AddTriggerListener(obj, EventTriggerType.Drag, dragable.OnDrag);
canvas.AddTriggerListener(obj, EventTriggerType.EndDrag, (x) => dragable.OnEndDrag(x, complete));
} public static void RemoveDragListener(this Canvas canvas, Component obj)
{
canvas.RemoveTriggerListener(obj);
}

调用时如下:

 //添加
Canvas.AddDragListener(View.Map, DragMode.Map, OnDragComplete); //处理
private void OnDragComplete()
{
//Do something else...
} //移除
Canvas.RemoveDragListener(View.Map);

Dragable类就是重新封装过的一个专门用于处理拖动的类,外部使用它时不需要了解任何它的实现细节,而且使用时也简单了许多,什么都不用关心,直接添加侦听即可,不用再像原来一样还要继承三个接口分别写。

当然了,接下来就是要讨论Dragable这个类具体的实现方式,它需要处理通用的拖动操作,首先就是能让拖的物体动起来,其次就是不能乱动,到了拖动范围边缘就不能再朝那个方位动了。

值得注意的是,拖动物件和拖动地图一般是不同的,因为在拖动物件时,整个物件的轮廓范围都应该保持在拖动范围之内,而拖动地图时则完全相反,一般地图大于整个范围才需要拖动来看,所以要保证地图边缘永远大于拖动范围。

见下图:

假设上图中黑色框代表拖动范围,同样贴近范围左边缘的情况下,左图的物件不能再往向左的方向拖动,而右图的地图则不能再往向右的方向拖动。

分别定义两种拖动模式如下,在初始化中可以设置模式与拖动速度:

     public DragMode DragMode = DragMode.Map;
[Range(0.1f, 1.9f)]
public float DragSpeed = 1f; public void Init(DragMode dragMode,float dragSpeed=1f)
{
DragMode = dragMode;
DragSpeed = dragSpeed > 1.9f ? 1.9f : dragSpeed < 0.1f ? 0.1f : dragSpeed;
}
 public enum DragMode
{
Map,
Obj
}

拖动开始时:

     Vector2 lastPos;
RectTransform rt;
Vector2 lastAnchorMin;
Vector2 lastAnchorMax; public void OnBeginDrag(BaseEventData data)
{
//将基类的Data转化为对应子类
var d = data as PointerEventData;
//初始化屏幕位置
lastPos = d.position; rt = GetComponent<RectTransform>();
lastAnchorMin = rt.anchorMin;
lastAnchorMax = rt.anchorMax; //将锚框设置为四周扩展类型的预设,方便后续判断和边缘范围的距离
rt.SetRtAnchorSafe(Vector2.zero, Vector2.one);
}

有一个位置需要注意,动态改变锚框时Unity并不会像是在编辑器中一样友好帮你自动计算RectTransform,而是会各种乱,位置也可能不对了,大小也可能不对了,所以这里写一个扩展方法进行安全改变锚框:

     public static void SetRtAnchorSafe(this RectTransform rt, Vector2 anchorMin, Vector2 anchorMax)
{
if (anchorMin.x < || anchorMin.x > || anchorMin.y < || anchorMin.y > || anchorMax.x < || anchorMax.x > || anchorMax.y < || anchorMax.y > )
return; var lp = rt.localPosition;
//注意不要直接用sizeDelta因为该值会随着anchor改变而改变
var ls = new Vector2(rt.rect.width, rt.rect.height); rt.anchorMin = anchorMin;
rt.anchorMax = anchorMax; //动态改变anchor后size和localPostion可能会发生变化需要重新设置
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, ls.x);
rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, ls.y);
rt.localPosition = lp;
}

在拖动的过程中最重要的是要检测是否到达父物体设置的拖动范围,只有该方向上没有到达边缘才能朝这个方向移动:

     public void OnDrag(BaseEventData data)
{
var d = data as PointerEventData;
//一帧内拖动的向量
Vector2 offse = d.position - lastPos; //检测拖动的方向与边缘的关系
if (CheckDragLimit(offse))
{
rt.anchoredPosition += offse * DragSpeed; //极限快速拖动时单帧拖动距离超出范围的归位检测
ResetRtOffset();
}
lastPos = d.position;
}
     bool CheckDragLimit(Vector2 offse)
{
bool result = false;
if (offse.x >= && offse.y >= )
{
//向右上拖动
return DragMode == DragMode.Map ? rt.offsetMin.x < && rt.offsetMin.y < :
rt.offsetMax.x < && rt.offsetMax.y < ;
}
else if (offse.x >= && offse.y < )
{
//向右下拖动
return DragMode == DragMode.Map ? rt.offsetMin.x < && rt.offsetMax.y > :
rt.offsetMax.x < && rt.offsetMin.y > ; }
else if (offse.x < && offse.y >= )
{
//向左上拖动
return DragMode == DragMode.Map ? rt.offsetMax.x > && rt.offsetMin.y < :
rt.offsetMin.x > && rt.offsetMax.y < ;
}
else if (offse.x < && offse.y < )
{
//向左下拖动
return DragMode == DragMode.Map ? rt.offsetMax.x > && rt.offsetMax.y > :
rt.offsetMin.x > && rt.offsetMin.y > ;
}
return result;
}

先判断拖动的方向,再根据拖动的方向结合拖动模式和相对边缘的偏移来判断是否还能朝对应方向拖动。

如果需要在全屏范围内拖动,其上的父物体层都需要四周扩展类型的锚框预设且切合屏幕边缘。

这里的offsetMin和offsetMax并不完全是对应Unity面板上的以下四个值,需要特别注意,网上的很多说法都存在一些未有考虑全面的地方:

比如上面这样的数据,offsetMin实际上的(730,1724),但offsetMax则是(-608,-1138),这里不注意可能会出现很多错误。

那为什么会是这样呢,其实那就要看offsetMin和offsetMax实际代表的是什么,他们分别是以其父物体大小的范围的左下,右上为原点,右,上分别为X轴Y轴正方向得出的偏移值。

注意,无论是offsetMin还是offsetMax都是以右上为X轴和Y轴的正方向作为计算标准的,只不过原点不同。

然而恶意的是,在ugui的编辑面板中却是用的边到边的距离,故而对于左下的点不会产生任何影响,但对于右上的点就会变为其相反数。

有时检测边缘也有丢失的情况,那就是单帧拖动的速度过快了,例如上一帧还远远不到边缘,下一帧已经超出很远,这时就需要对超出的部分进行重新复位到边缘:

     void ResetRtOffset()
{
switch (DragMode)
{
case DragMode.Map:
if (rt.offsetMin.x > )
rt.anchoredPosition -= new Vector2(rt.offsetMin.x, ); if (rt.offsetMin.y > )
rt.anchoredPosition -= new Vector2(, rt.offsetMin.y); if (rt.offsetMax.x < )
rt.anchoredPosition -= new Vector2(rt.offsetMax.x, ); if (rt.offsetMax.y < )
rt.anchoredPosition -= new Vector2(, rt.offsetMax.y);
break;
case DragMode.Obj:
if (rt.offsetMin.x < )
rt.anchoredPosition -= new Vector2(rt.offsetMin.x, ); if (rt.offsetMin.y < )
rt.anchoredPosition -= new Vector2(, rt.offsetMin.y); if (rt.offsetMax.x > )
rt.anchoredPosition -= new Vector2(rt.offsetMax.x, ); if (rt.offsetMax.y > )
rt.anchoredPosition -= new Vector2(, rt.offsetMax.y);
break;
}
}

归为操作实际就是一个平移变换,超过多少就对应方位平移多少,这样即使一帧内拖动的距离远超过了边缘,也只能到达紧紧贴合边缘的程度。

拖动完成后,复位拖动前的锚框预设,执行整个过程完成后的委托:

     public void OnEndDrag(BaseEventData data,UnityAction complete)
{
//还原拖动之前的预设
rt.SetRtAnchorSafe(lastAnchorMin, lastAnchorMax);
complete();
}

当然了,如果真的遇到拖动过程中也需要执行个性化命令,这时也可考虑自行添加其他委托。

完整Dragable脚本:

 using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events; public enum DragMode
{
Map,
Obj
} public class Dragable : MonoBehaviour
{
public DragMode DragMode = DragMode.Map;
[Range(0.1f, 1.9f)]
public float DragSpeed = 1f; public void Init(DragMode dragMode,float dragSpeed=1f)
{
DragMode = dragMode;
DragSpeed = dragSpeed > 1.9f ? 1.9f : dragSpeed < 0.1f ? 0.1f : dragSpeed;
} Vector2 lastPos;
RectTransform rt;
Vector2 lastAnchorMin;
Vector2 lastAnchorMax; public void OnBeginDrag(BaseEventData data)
{
//将基类的Data转化为对应子类
var d = data as PointerEventData;
//初始化屏幕位置
lastPos = d.position; rt = GetComponent<RectTransform>();
lastAnchorMin = rt.anchorMin;
lastAnchorMax = rt.anchorMax; //将锚框设置为四周扩展类型的预设,方便后续判断和屏幕边缘的距离
rt.SetRtAnchorSafe(Vector2.zero, Vector2.one);
} public void OnDrag(BaseEventData data)
{
var d = data as PointerEventData;
//一帧内拖动的向量
Vector2 offse = d.position - lastPos; //检测拖动的方向与边缘的关系
if (CheckDragLimit(offse))
{
rt.anchoredPosition += offse * DragSpeed; //极限快速拖动时单帧拖动距离超出范围的归位检测
ResetRtOffset();
}
lastPos = d.position;
} public void OnEndDrag(BaseEventData data,UnityAction complete)
{
//还原拖动之前的预设
rt.SetRtAnchorSafe(lastAnchorMin, lastAnchorMax);
complete();
} bool CheckDragLimit(Vector2 offse)
{
bool result = false;
if (offse.x >= && offse.y >= )
{
//向右上拖动
return DragMode == DragMode.Map ? rt.offsetMin.x < && rt.offsetMin.y < :
rt.offsetMax.x < && rt.offsetMax.y < ;
}
else if (offse.x >= && offse.y < )
{
//向右下拖动
return DragMode == DragMode.Map ? rt.offsetMin.x < && rt.offsetMax.y > :
rt.offsetMax.x < && rt.offsetMin.y > ; }
else if (offse.x < && offse.y >= )
{
//向左上拖动
return DragMode == DragMode.Map ? rt.offsetMax.x > && rt.offsetMin.y < :
rt.offsetMin.x > && rt.offsetMax.y < ;
}
else if (offse.x < && offse.y < )
{
//向左下拖动
return DragMode == DragMode.Map ? rt.offsetMax.x > && rt.offsetMax.y > :
rt.offsetMin.x > && rt.offsetMin.y > ;
}
return result;
} void ResetRtOffset()
{
switch (DragMode)
{
case DragMode.Map:
if (rt.offsetMin.x > )
rt.anchoredPosition -= new Vector2(rt.offsetMin.x, ); if (rt.offsetMin.y > )
rt.anchoredPosition -= new Vector2(, rt.offsetMin.y); if (rt.offsetMax.x < )
rt.anchoredPosition -= new Vector2(rt.offsetMax.x, ); if (rt.offsetMax.y < )
rt.anchoredPosition -= new Vector2(, rt.offsetMax.y);
break;
case DragMode.Obj:
if (rt.offsetMin.x < )
rt.anchoredPosition -= new Vector2(rt.offsetMin.x, ); if (rt.offsetMin.y < )
rt.anchoredPosition -= new Vector2(, rt.offsetMin.y); if (rt.offsetMax.x > )
rt.anchoredPosition -= new Vector2(rt.offsetMax.x, ); if (rt.offsetMax.y > )
rt.anchoredPosition -= new Vector2(, rt.offsetMax.y);
break;
}
}
}

Unity ugui拖动控件(地图模式与物件模式)的更多相关文章

  1. wxpython 中 用鼠标拖动控件 总结

    #encoding: utf-8 import wx import os import noname class Frame( noname.MyFrame1 ): def __init__(self ...

  2. win10 uwp 拖动控件

    我们会使用控件拖动,可以让我们做出好看的动画,那么我们如何移动控件,我将会告诉大家多个方法.其中第一个是最差的,最后的才是我希望大神你去用. Margin 移动 我们可以使用Margin移动,但这是w ...

  3. Unity中uGUI的控件事件穿透逻辑

    1.正常来说Image和Text是会拦截点击事件的,假设加入EventTrigger的话,就能够响应相应的交互事件. 2.假设Image和Text是一个Button的子控件.那么尽管其会显示在Butt ...

  4. C#程序员整理的Unity 3D笔记(十五):Unity 3D UI控件至尊–NGUI

    目前,UGUI问世不过半年(其随着Unity 4.6发布问世),而市面上商用的产品,UI控件的至尊为NGUI:影响力和广度(可搜索公司招聘Unity 3D,常常能看到对NGUI关键词). NGUI虽然 ...

  5. UGUI Toggle控件

    今天我们来看看Toogle控件, 它由Toogle + 背景 + 打勾图片 + 标签组成的. 它主要用于单选和多选 属性讲解: Is On: 代表是否选中. Toogle Transition: 在状 ...

  6. UGUI Scrollbar控件

    如题就是Scrollbar控件,它简单可以看成 Scrollbar 和 Image组件组成 它基本上不单独使用多数是制作滚动视图.我们来看看他独特的属性,重复的属性就不在介绍了! 属性讲解: Hand ...

  7. UGUI Text控件

    学习UGUI的Text控件,用于显示文本!.  基本属性就不再啰嗦了! Alignment: 文字以 水平和垂直 对齐方式, Horizontal Overflow: 水平 Wrap: 文字大小和数量 ...

  8. C# 在窗体上可拖动控件

    最近做了一个标签打印配置功能,需要根据客户需求自定义标签格式.显示内容,这时就用到了后台生成控件,并且其控件可在窗口中进行拖动,这里仅为记录一下实现过程,方便以后使用. 结果图: 源码: using ...

  9. Unity编辑器 - 输入控件聚焦问题

    Unity编辑器整理 - 输入控件聚焦问题 EditorGUI的输入控件在聚焦后,如果在其他地方改变值,聚焦的框不会更新,而且无法取消聚焦,如下图: 在代码中取消控件的聚焦 取消聚焦的"时机 ...

随机推荐

  1. 恶劣的网络环境下,Netty是如何处理写事件的?

    更多技术分享可关注我 前言 前面,在Netty在接收完新连接后,默认为何要为其注册读事件,其处理I/O事件的优先级是什么?这篇文章,分析到了Netty处理I/O事件的优先级——读事件优先,写事件仅仅是 ...

  2. 怎么用python 3 开发钉钉群机器人

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:Python绿色通道 PS:如有需要Python学习资料的小伙伴可以加 ...

  3. 资料整理:python自动化测试——操作测试对象

    文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:爱吃米饭的猪 PS:如有需要Python学习资料的小伙伴可以加点击下方链接自 ...

  4. 百度AI开发平台简介

    AIstudio https://aistudio.baidu.com/aistudio/index 关于AI Studio AI Studio是基于百度深度学习平台飞桨的一站式AI开发平台,提供在线 ...

  5. ChaosBlade--动态脚本实现 Java 实验场景

    动态脚本实现 : 参考文档:https://github.com/chaosblade-io/chaosblade/wiki/%E5%8A%A8%E6%80%81%E8%84%9A%E6%9C%AC% ...

  6. PHP函数:memory_get_usage

    memory_get_usage()  -返回分配给 PHP 的内存量 说明: memory_get_usage ([ bool $real_usage = false ] ) : int 参数: r ...

  7. selenium 键盘鼠标模拟

    一.键盘模拟常用的键 sendKeys(Keys.BACK_SPACE);  //删除键--Backspace sendKeys(Keys.SPACE);   //空格键 Space sendKeys ...

  8. windows 系统使用技巧

    1 自定义发送到C:\Users\adm\AppData\Roaming\Microsoft\Windows\SendTo 2 自定义关机shutdown -s -t time,可写在快捷方式中shu ...

  9. 开发一款图片压缩工具(三):使用 click 实现命令行

    上一篇实现了图片的压缩函数.现在如果需要对图片进行压缩,可以调用实现的函数进行压缩: pngquant_compress('elephant.png', force=True, quality=20) ...

  10. 算法笔记刷题3(codeup 5901)

    今天刷题的速度依旧很慢(小乌龟挥爪.jpg) 我觉得codeup5901中回文串的处理很妙,如果是我自己写的话可能会把数组直接倒过来和原来对比.按照对称规律进行比对的话,工作量可以减少一半. #inc ...