0


Unity UI点击事件系统

一、概述

  • 在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;}}
  • 最后看一下UpdateLongClickPress事件的执行条件
  1. 首先在上面注册事件
  2. 然后当重写的接口触发 按下 或 抬起 的事件,并将状态进行切换
  3. Update中监测到state值变化,就开始一个新的流程如下
  • 进入POINTSTATE.DOWN,初始化需要用到的值
  • 第二次进入Update函数,此时state已被改为POINTSTATE.STAY
  • 开始判断是否存在LongClickPress回调,有则执行,并且在这个时候,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){}}
标签: unity ui c#

本文转载自: https://blog.csdn.net/KamikazePilot/article/details/128241490
版权归原作者 KamikazePilot 所有, 如有侵权,请联系我们删除。

“Unity UI点击事件系统”的评论:

还没有评论