一、概述
- 在Unity 的UGUI开发过程中,我们经常需要对UI图片进行操作响应各种处理比如:点击,长点击,长按,拖拽等多种功能的实现,这时原本的Button组件就不够用了
- 我们需要自己实现一个简单的点击事件系统来响应用户的各种操作,下面可以分析一下思路。
- 首先你需要提前了解点击事件的各个接口功能,了解C#中的委托使用,然后再学习这个系统
二、如何实现
- 当我们想要响应长按等根时间挂钩的功能,就必须要使用
Update
函数的多帧调用来计算时间(FixUpdate应该也行) - 所以,我建议将自己实现的功能尽量写在
Updata
函数中,并通过一个流程来实现按键的状态切换。
1 基础框架
下图中
POINTSTATE
枚举用于改变当前按键状态,通过外部修改按键状态,或者自身状态的变更,来实现按键的多种状态监测。(这里不添加拖拽状态,拖拽状态将会另外添加一个拖拽脚本专门用于响应拖拽事件)
publicenumPOINTSTATE{
NONE,
DOWN,
STAY,
UP,
UPSPACE,
EXIT,
EXITSPACE,}publicclassUIEvent:MonoBehaviour{voidUpdate(){if(!gameObject.activeSelf)return;switch(state){case POINTSTATE.NONE://无状态return;case POINTSTATE.DOWN://按键按下return;case POINTSTATE.STAY://点击停留时return;case POINTSTATE.UP://抬起return;case POINTSTATE.UPSPACE:return;case POINTSTATE.EXIT://退出return;case POINTSTATE.EXITSPACE:return;}}}
- 其次我们需要继承点击事件接口来进行按键响应
- 每次监测到按下,抬起,结束时,都需要将按键状态进行变更,以便于在Update中进行响应(点击状态这里在Update中更改,所以在重写的
OnClick
方法中没有进行状态改变) - 同时可以将注册的点击事件(后面会写)进行调用,点击监测别直接写这里,后面需要的话可以进行动态添加,所以这里要进行判空操作
- 注意点击事件只能被有图片的节点响应,所以添加一个特性使其不被无图片的节点继承
[RequireComponent(typeof(Image))]//有图片组件才能被继承publicclassUIEvent:MonoBehaviour,IPointerDownHandler,IPointerClickHandler,IPointerExitHandler,IPointerUpHandler{//点击回调Action OnClick =null;//按下回调Action OnDown =null;//抬起回调Action OnUp =null;//结束回调Action OnExit =null;//当前点击状态POINTSTATE state = POINTSTATE.NONE;publicvoidOnPointerClick(PointerEventData eventData){if(OnClick !=null)OnClick();}publicvoidOnPointerDown(PointerEventData eventData){
state = POINTSTATE.DOWN;if(OnDown !=null)OnDown();}publicvoidOnPointerUp(PointerEventData eventData){
state = POINTSTATE.UP;if(OnDown !=null)OnUp();}publicvoidOnPointerExit(PointerEventData eventData){
state = POINTSTATE.EXIT;if(OnDown !=null)OnExit();}}
2 功能结构
- 之后便可以进行外部接口的设计,这里添加以下几个
//点击回调Action OnClick =null;//按下回调Action OnDown =null;//抬起回调Action OnUp =null;//结束回调Action OnExit =null;//长按回调 单次相应Action OnLongClick =null;//按下回调 多次相应,且相应速度加快Action OnPress =null;// 设置点击间隔(防止短时间大量连点操作)publicstaticvoidSetClickSpace(int space)// 注册一般点击publicstaticvoidAddClick(GameObject _go,Action _func)// 注册按键按下publicstaticvoidAddDown(GameObject _go,Action _func)// 注册按键抬起publicstaticvoidAddUp(GameObject _go,Action _func)// 注册按键退出publicstaticvoidAddExit(GameObject _go,Action _func)// 注册长点击publicstaticvoidAddLongClick(GameObject _go,Action _func,float _space =1f)// 注册长按publicstaticvoidAddPress(GameObject _go,Action _func,float _space =1f,float _minSpace =0.1f)
- 到这里整个结构就比较清晰了,首先通过调用
UIEvent
的静态接口,给需要的节点添加事件,并在按键按下时,在Update函数中调用已经添加的事件。
2 具体实现
- 首先实现一个可能被多次调用的接口, 对传入的对象更改
RayCastTarget
,并返回UIEvent
脚本,没有挂载就添加一个。 - 这里为了方便起见直接写在
UIEvent
中,如果想结构更加清晰一点可以分开写
//检查图像并挂载脚本staticUIEventGet(GameObject _go){Graphic graphic = _go.GetComponent<Graphic>();if(graphic) graphic.raycastTarget =true;UIEvent uiEvent = _go.GetComponent<UIEvent>();if(uiEvent ==null) uiEvent = _go.AddComponent<UIEvent>();return uiEvent;}
- 然后说外部接口,具体实现如下:
- 先挂载脚本,然后将改脚本下得相应点击事件值进行更改即可。
- 其中
LongClick
函数和Press
函数可以额外传入长按时间、最短响应间隔时间。
#region 外部注册相关接口// 设置点击间隔publicstaticvoidSetClickSpace(int space){
timeClickSpace = space;}/// <summary>/// 注册点击/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddClick(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnClick = _func;}/// <summary>/// 注册按下/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddDown(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnDown = _func;}/// <summary>/// 注册抬起/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddUp(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnUp = _func;}/// <summary>/// 注册结束点击/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddExit(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnClick = _func;}/// <summary>/// 注册长点击/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>/// <param name="_space">长按时间</param>publicstaticvoidAddLongClick(GameObject _go,Action _func,float _space =1f){UIEvent uIEvent =Get(_go);if(uIEvent){
uIEvent.timeLongClickSpace = _space;
uIEvent.OnLongClick = _func;}}/// <summary>/// 注册长按/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>/// <param name="_space">长按时间</param>/// <param name="_minSpace">最短响应间隔</param>publicstaticvoidAddPress(GameObject _go,Action _func,float _space =1f,float _minSpace =0.1f){UIEvent uIEvent =Get(_go);if(uIEvent){
uIEvent.timePressSpace = _space;
uIEvent.OnPress = _func;
uIEvent.timePressMinSpace = _minSpace;}}
- 最后看一下
Update
中LongClick
和Press
事件的执行条件
- 首先在上面注册事件
- 然后当重写的接口触发 按下 或 抬起 的事件,并将状态进行切换
Update
中监测到state
值变化,就开始一个新的流程如下
- 进入
POINTSTATE.DOWN
,初始化需要用到的值 - 第二次进入
Update
函数,此时state
已被改为POINTSTATE.STAY
- 开始判断是否存在
LongClick
和Press
回调,有则执行,并且在这个时候,state
值不会Update
函数内被改变 - 直到在接口中将值改为
POINTSTATE.UP
为止
#region 注册相关的属性//点击回调Action OnClick =null;//按下回调Action OnDown =null;//抬起回调Action OnUp =null;//结束回调Action OnExit =null;//长按回调 单次相应Action OnLongClick =null;//按下回调 多次相应,且相应速度加快Action OnPress =null;//单词击计数器float timeClick =0;//一秒内点击限制staticint timeClickSpace =10;//长点击计数器float timeLongClick =0;//长点击生效时长float timeLongClickSpace =1.0f;//长按计数器float timePress =0;//长按递减间隔float timePressSpace =1.0f;//长按递减间隔缓存float timePressSpaceCache =0;//长按最低间隔float timePressMinSpace =0.1f;//当前点击状态POINTSTATE state = POINTSTATE.NONE;#endregion//事件响应流程写在Update中voidUpdate(){if(!gameObject.activeSelf)return;switch(state){case POINTSTATE.NONE://无状态return;case POINTSTATE.DOWN://
timeLongClick = Time.time;//记录长点击开始时间// 如果注册了OnPress事件则直接执行if(OnPress !=null&& Time.time - timePress >(1/ timeClickSpace))OnPress();
timePress = Time.time;//记录长按开始时间
timePressSpaceCache = timePressSpace;// 长按事件响应间隔
state = POINTSTATE.STAY;return;case POINTSTATE.STAY://点击停留时if(OnLongClick !=null)//长点击{if(Time.time - timeLongClick > timeLongClickSpace)//到时间了开始执行{OnLongClick();
state = POINTSTATE.NONE;//进入结束状态}}if(OnPress !=null)//长按{float spaceTime = Time.time - timePress;//距离上一次间隔时间if(spaceTime > timePressSpaceCache && spaceTime >(1/ timeClickSpace)){//记录相应时间点
timePress = Time.time;//缩短相应间隔
timePressSpaceCache *=(2/3f);
timePressSpaceCache = Mathf.Max(timePressSpaceCache, timePressMinSpace);//执行回调OnPress();}}return;case POINTSTATE.UP://抬起
state = POINTSTATE.UPSPACE;return;case POINTSTATE.UPSPACE:return;case POINTSTATE.EXIT://退出
state = POINTSTATE.EXITSPACE;return;case POINTSTATE.EXITSPACE:return;}}
关于窗口拖拽事件,这里没有选择实现在UIEvent上,只是使用了UIEvent 进行事件注册,过程与之前的方式类似。两个函数分别是
staticUIEventDragGetDrag(GameObject _go){UIEventDrag uIEventDrag = _go.GetComponent<UIEventDrag>();if(!uIEventDrag) uIEventDrag = _go.AddComponent<UIEventDrag>();return uIEventDrag;}/// <summary>/// 注册拖拽/// </summary>/// <param name="_go">被移动对象</param>/// <param name="_window">需要点击的对象</param>publicstaticvoidAddDrag(GameObject _go,Transform _window){UIEventDrag uIEvent =GetDrag(_go);
uIEvent.IsWindowDrag =true;
uIEvent.Window = _window;}
UIEventDrag
是另一个挂了Nomo的脚本,代码贴在最后面了,它的实现可以相对独立;这里的实现针对性较强,大家用的时候可以根据需要进行自定义
新增双击
//双击回调Action OnDoubleClick =null;//双击最大间隔时间privatefloat timeDoubleClick =0.5f;//双击计时privatefloat timeDoubleClickCount =0f;voidUpdate(){switch(pointState){case POINTERSTATE.UP://Log.Print("抬起"); //双击if(OnDoubleClick !=null&& Time.time - timeDoubleClickCount <= timeDoubleClick)OnDoubleClick();
timeDoubleClickCount = Time.time;break;}}//注册双击publicstaticvoidAddDoubleClick(GameObject _go,Action _fun,float _space =0.7f){var uievent =Get(_go);if(uievent){
uievent.timeDoubleClick = _space;
uievent.OnDoubleClick = _fun;}}
三、完整代码
usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingUnityEngine.EventSystems;usingUnityEngine.UI;publicenumPOINTSTATE{
NONE,
DOWN,
STAY,
UP,
UPSPACE,
EXIT,
EXITSPACE,}[RequireComponent(typeof(Image))]//有图片组件才能被继承publicclassUIEvent:MonoBehaviour,IPointerDownHandler,IPointerClickHandler,IPointerExitHandler,IPointerUpHandler{#region 注册相关的属性//点击回调Action OnClick =null;//按下回调Action OnDown =null;//抬起回调Action OnUp =null;//结束回调Action OnExit =null;//长按回调 单次相应Action OnLongClick =null;//按下回调 多次相应,且相应速度加快Action OnPress =null;//双击回调Action OnDoubleClick =null;//单词击计数器float timeClick =0;//一秒内点击限制staticint timeClickSpace =10;//双击最大间隔时间privatefloat timeDoubleClick =0.5f;//双击计时privatefloat timeDoubleClickCount =0f;//长点击计数器float timeLongClick =0;//长点击生效时长float timeLongClickSpace =1.0f;//长按计数器float timePress =0;//长按递减间隔float timePressSpace =1.0f;//长按递减间隔缓存float timePressSpaceCache =0;//长按最低间隔float timePressMinSpace =0.1f;//当前点击状态POINTSTATE state = POINTSTATE.NONE;#endregion//事件响应流程写在Update中voidUpdate(){if(!gameObject.activeSelf)return;switch(state){case POINTSTATE.NONE://无状态return;case POINTSTATE.DOWN://
timeLongClick = Time.time;//记录长点击开始时间// 如果注册了OnPress事件则直接执行if(OnPress !=null&& Time.time - timePress >(1/ timeClickSpace))OnPress();
timePress = Time.time;//记录长按开始时间
timePressSpaceCache = timePressSpace;// 长按事件响应间隔
state = POINTSTATE.STAY;return;case POINTSTATE.STAY://点击停留时if(OnLongClick !=null)//长点击{if(Time.time - timeLongClick > timeLongClickSpace)//到时间了开始执行{OnLongClick();
state = POINTSTATE.NONE;//进入结束状态}}if(OnPress !=null)//长按{float spaceTime = Time.time - timePress;//距离上一次间隔时间if(spaceTime > timePressSpaceCache && spaceTime >(1/ timeClickSpace)){//记录相应时间点
timePress = Time.time;//缩短相应间隔
timePressSpaceCache *=(2/3f);
timePressSpaceCache = Mathf.Max(timePressSpaceCache, timePressMinSpace);//执行回调OnPress();}}return;case POINTSTATE.UP://抬起//双击if(OnDoubleClick !=null&& Time.time - timeDoubleClickCount <= timeDoubleClick)OnDoubleClick();
timeDoubleClickCount = Time.time;
state = POINTSTATE.UPSPACE;return;case POINTSTATE.UPSPACE:return;case POINTSTATE.EXIT://退出
state = POINTSTATE.EXITSPACE;return;case POINTSTATE.EXITSPACE:return;}}//检查图像并挂载脚本staticUIEventGet(GameObject _go){Graphic graphic = _go.GetComponent<Graphic>();if(graphic) graphic.raycastTarget =true;UIEvent uiEvent = _go.GetComponent<UIEvent>();if(uiEvent ==null) uiEvent = _go.AddComponent<UIEvent>();return uiEvent;}staticUIEventDragGetDrag(GameObject _go){UIEventDrag uIEventDrag = _go.GetComponent<UIEventDrag>();if(!uIEventDrag) uIEventDrag = _go.AddComponent<UIEventDrag>();return uIEventDrag;}#region 外部注册相关接口// 设置点击间隔publicstaticvoidSetClickSpace(int space){
timeClickSpace = space;}/// <summary>/// 注册点击/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddClick(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnClick = _func;}/// <summary>/// 注册按下/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddDown(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnDown = _func;}/// <summary>/// 注册抬起/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddUp(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnUp = _func;}/// <summary>/// 注册结束点击/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>publicstaticvoidAddExit(GameObject _go,Action _func){UIEvent uIEvent =Get(_go);if(uIEvent) uIEvent.OnClick = _func;}/// <summary>/// 注册长点击/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>/// <param name="_space">长按时间</param>publicstaticvoidAddLongClick(GameObject _go,Action _func,float _space =1f){UIEvent uIEvent =Get(_go);if(uIEvent){
uIEvent.timeLongClickSpace = _space;
uIEvent.OnLongClick = _func;}}/// <summary>/// 注册长按/// </summary>/// <param name="_go">被注册对象</param>/// <param name="_func">回调函数</param>/// <param name="_space">长按时间</param>/// <param name="_minSpace">最短响应间隔</param>publicstaticvoidAddPress(GameObject _go,Action _func,float _space =1f,float _minSpace =0.1f){UIEvent uIEvent =Get(_go);if(uIEvent){
uIEvent.timePressSpace = _space;
uIEvent.OnPress = _func;
uIEvent.timePressMinSpace = _minSpace;}}/// <summary>/// 注册拖拽/// </summary>/// <param name="_go">被移动对象</param>/// <param name="_window">需要点击的对象</param>publicstaticvoidAddDrag(GameObject _go,Transform _window){UIEventDrag uIEvent =GetDrag(_go);
uIEvent.IsWindowDrag =true;
uIEvent.Window = _window;}//注册双击publicstaticvoidAddDoubleClick(GameObject _go,Action _fun,float _space =0.7f){var uievent =Get(_go);if(uievent){
uievent.timeDoubleClick = _space;
uievent.OnDoubleClick = _fun;}}#endregion#region 点击重写publicvoidOnPointerClick(PointerEventData eventData){//检查间隔if(Time.time - timeClick <1.0f/ timeClickSpace){return;}if(OnClick !=null){OnClick();
timeClick = Time.time;}}publicvoidOnPointerDown(PointerEventData eventData){
state = POINTSTATE.DOWN;if(OnDown !=null)OnDown();}publicvoidOnPointerUp(PointerEventData eventData){
state = POINTSTATE.UP;if(OnDown !=null)OnUp();}publicvoidOnPointerExit(PointerEventData eventData){
state = POINTSTATE.EXIT;if(OnDown !=null)OnExit();}#endregion}
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;usingUnityEngine.EventSystems;publicclassUIEventDrag:MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler{publicbool IsWindowDrag =false;publicTransform Window;privateVector2 winDiffPoint;privateVector2 winDeltaPoint;privatefloat lx;privatefloat ly;publicvoidOnDrag(PointerEventData _eventData){if(IsWindowDrag){
winDeltaPoint = _eventData.position - winDiffPoint;
Window.localPosition +=newVector3(winDeltaPoint.x, winDeltaPoint.y,0);if(Window.localPosition.x > lx) Window.localPosition =newVector3(lx, Window.localPosition.y,0);if(Window.localPosition.x <-lx2) Window.localPosition =newVector3(-lx2, Window.localPosition.y,0);if(Window.localPosition.y > ly) Window.localPosition =newVector3(Window.localPosition.x, ly,0);if(Window.localPosition.y <-ly2) Window.localPosition =newVector3(Window.localPosition.x,-ly2,0);
winDiffPoint = _eventData.position;}}publicvoidOnBeginDrag(PointerEventData _eventData){if(IsWindowDrag){
winDiffPoint = _eventData.position;RectTransform rt =GetComponent<RectTransform>();
lx =(Screen.width - rt.rect.width)/2- transform.localPosition.x;
lx2 =(Screen.width - rt.rect.width)/2+ transform.localPosition.x;
ly =(Screen.height - rt.rect.height)/2- transform.localPosition.y;
ly2 =(Screen.height - rt.rect.height)/2+ transform.localPosition.y;}}publicvoidOnEndDrag(PointerEventData eventData){}}
版权归原作者 KamikazePilot 所有, 如有侵权,请联系我们删除。