Unity 单击、双击、长按事件处理

需求

想要一个工具,能够同时集成单击、双击、长按的功能

思路

通过IPointerDownHandler, IPointerUpHandler, IPointerClickHandler这三个接口就可以监听点击状态,然后再通过不同的点击状态来处理相应的事件

遇到的问题

  • 由于可能同时存在多个事件,实际开发过程中会出现多余事件通知,如下:
    • 同时拥有单击和双击事件,点击双击的同时会响应两次单击
    • 同时拥有单击事件和长按事件时,当长按事件响应的时候会同时响应单击事件
    • 同时拥有单击双击和长按事件时点击双击时会同时响应两次单击,长按事件响应时会同时响应单击事件

解决问题

  • 由于我们无法预测用户的具体行为,所以当需要响应单击事件时,检测其他事件有没有在安全时效内的检测体,例如,单击响应的时候如果有其他事件正在检测过程中我们忽略这次的事件响应,并把事件存起来,等待所有事件的安全时效结束时响应这次单击事件。或者是等待过程中有其他事件成功响应那么我们就舍弃这次的单击事件而响应对应的双击或者长按事件。那么这样就完美解决了上述所有问题。

具体代码如下:

ClickEvent

public class ClickEvent : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerClickHandler
    {
        private List<IClickListener> listenerList = new List<IClickListener>();
        private Dictionary<IClickListener, Action> callBackDict = new Dictionary<IClickListener, Action>();

        public void OnPointerDown(PointerEventData eventData)
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.OnPointerDown(eventData);
            }
        }

        public void OnPointerUp(PointerEventData eventData)
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.OnPointerUp(eventData);
            }
        }

        public void OnPointerClick(PointerEventData eventData)
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.OnPointerClick(eventData);
            }
        }

        private void Update()
        {
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listenerList[i]?.Update();
            }
        }

        public T Add<T>() where T : ClickListener, new()
        {
            T t = Get<T>();
            if (t == null)
            {
                t = new T();
                (t as IClickListener).OnCallBack((success) => OnReceive(success, t));
                if (listenerList.Count == 0 || t is ClickButton) listenerList.Add(t);
                else listenerList.Insert(listenerList.Count - 1, t);
            }

            return t;
        }

        public void Remove<T>() where T : ClickListener
        {
            if (!Contains<T>()) return;
            T t = Get<T>();
            if (callBackDict.ContainsKey(t)) callBackDict.Remove(t);
            listenerList.Remove(t);
        }

        public void RemoveAll()
        {
            listenerList.Clear();
            callBackDict.Clear();
        }

        public T Get<T>() where T : ClickListener
        {
            if (listenerList.Count == 0) return default;
            IClickListener listener = default;
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listener = listenerList[i];
                if (listener is T) return (T) listener;
            }

            return default;
        }

        public bool Contains<T>() where T : ClickListener
        {
            if (listenerList.Count == 0) return false;
            IClickListener listener = default;
            for (int i = listenerList.Count - 1; i >= 0; i--)
            {
                listener = listenerList[i];
                if (listener is T) return true;
            }

            return false;
        }

        public bool ContainsCallBack<T>() where T : ClickListener
        {
            T t = Get<T>();
            if (t == null) return false;
            if (callBackDict.ContainsKey(t))
            {
                if (callBackDict[t] == null)
                {
                    callBackDict.Remove(t);
                    return false;
                }

                return true;
            }
            else return false;
        }

        public Action GetCallBack<T>() where T : ClickListener
        {
            T t = Get<T>();
            if (t == null) return null;
            if (callBackDict.ContainsKey(t))
            {
                Action callBack = callBackDict[t];
                if (callBack == null)
                {
                    callBackDict.Remove(t);
                    return null;
                }

                return callBack;
            }
            else return null;
        }

        public bool HasRuning()
        {
            bool isRuning = false;
            foreach (var lis in listenerList)
            {
                if (lis.isRuning)
                {
                    isRuning = true;
                    break;
                }
            }

            return isRuning;
        }

        public T OnRegister<T>(Action callBack) where T : ClickListener, new()
        {
            T t = Add<T>();
            if (callBackDict.ContainsKey(t)) callBackDict[t] = callBack;
            else callBackDict.Add(t, callBack);
            return t;
        }

        private Action clickCallBack = null;

        void OnReceive(bool success, IClickListener listener)
        {
            if (success)
            {
                if (listener is ClickButton) //单击事件
                {
                    Action cbk = GetCallBack<ClickButton>();
                    if (cbk == null) return;
                    if (HasRuning()) //有正在运行的事件检测单击事件滞后分发
                    {
                        clickCallBack = cbk;
                    }
                    else
                    {
                        clickCallBack = null;
                        ResetOther();
                        cbk.Invoke();
                    }
                }
                else //其他事件
                {
                    clickCallBack = null;
                    ResetOther();
                    if (callBackDict.ContainsKey(listener)) callBackDict[listener]?.Invoke();
                }
            }
            else
            {
                if (!HasRuning())
                {
                    clickCallBack?.Invoke();
                    clickCallBack = null;
                }
            }

            void ResetOther()
            {
                foreach (var btn in listenerList)
                {
                    if (btn != listener) btn?.Reset();
                }
            }
        }

#if UNITY_EDITOR

        [UnityEditor.CustomEditor(typeof(ClickEvent))]
        class ClickEventInspector : UnityEditor.Editor
        {
            private ClickEvent clickEvent;

            private void OnEnable()
            {
                clickEvent = target as ClickEvent;
            }

            public override void OnInspectorGUI()
            {
                base.OnInspectorGUI();
                GUILayout.Label("ClickListeners: ");
                IClickListener listener;
                for (int i = clickEvent.listenerList.Count - 1; i >= 0; i--)
                {
                    listener = clickEvent.listenerList[i];
                    GUILayout.Label("    " + listener);
                }
            }
        }

#endif

        private interface IClickListener
        {
            bool isRuning { get; set; }
            void OnPointerDown(PointerEventData eventData);
            void OnPointerUp(PointerEventData eventData);

            void OnPointerClick(PointerEventData eventData);

            void Update();

            void OnCallBack(Action<bool> callBack);

            void Reset();

            void Dispose();
        }

        public abstract class ClickListener : IClickListener
        {
            bool IClickListener.isRuning
            {
                get => isRuning;
                set { isRuning = value; }
            }

            protected bool isRuning = false;

            protected Action<bool> callBack { get; private set; }
            void IClickListener.OnPointerDown(PointerEventData eventData) => OnPointerDown(eventData);
            void IClickListener.OnPointerUp(PointerEventData eventData) => OnPointerUp(eventData);
            void IClickListener.OnPointerClick(PointerEventData eventData) => OnPointerClick(eventData);
            void IClickListener.Update() => Update();
            void IClickListener.Reset() => Reset();
            void IClickListener.Dispose() => Dispose();

            void IClickListener.OnCallBack(Action<bool> callBack)
            {
                this.callBack = callBack;
            }

            protected virtual void OnPointerDown(PointerEventData eventData)
            {
            }

            protected virtual void OnPointerUp(PointerEventData eventData)
            {
            }

            protected virtual void OnPointerClick(PointerEventData eventData)
            {
            }

            protected virtual void Update()
            {
            }

            protected abstract void Reset();

            protected virtual void Dispose()
            {
                callBack = null;
            }
        }
    }

Buttons

 //单击按钮
    public class ClickButton : ClickEvent.ClickListener
    {
        protected override void OnPointerDown(PointerEventData eventData)
        {
            isRuning = true;
        }

        protected override void OnPointerUp(PointerEventData eventData)
        {
            if (isRuning)
            {
                Reset();
                callBack?.Invoke(true);
            }
        }

        protected override void Reset()
        {
            isRuning = false;
        }
    }

    //双击按钮
    public class DoubleClickButton : ClickEvent.ClickListener
    {
        public int maxSpaceTime = 250;
        private int clickCount = 0;
        private Stopwatch stopWatch;
        private float lastPointTime = 0;

        public DoubleClickButton()
        {
            stopWatch = new Stopwatch();
            Reset();
        }

        protected override void OnPointerDown(PointerEventData eventData)
        {
            isRuning = true;
        }

        protected override void OnPointerClick(PointerEventData eventData)
        {
            stopWatch.Start();
            clickCount++;
            if (clickCount == 2)
            {
                Reset();
                callBack?.Invoke(true);
            }
        }

        protected override void Update()
        {
            if (!stopWatch.IsRunning) return;
            if (stopWatch.ElapsedMilliseconds > maxSpaceTime)
            {
                Reset();
                callBack?.Invoke(false);
            }
        }

        protected override void Reset()
        {
            isRuning = false;
            if (stopWatch.IsRunning) stopWatch.Stop();
            stopWatch.Reset();
            clickCount = 0;
        }
    }

    //长按按钮
    public class PressButton : ClickEvent.ClickListener
    {
        public int pressMinTime = 2000;
        private Stopwatch stopWatch;

        public PressButton()
        {
            stopWatch = new Stopwatch();
        }

        protected override void OnPointerDown(PointerEventData eventData)
        {
            isRuning = true;
            stopWatch.Start();
        }

        protected override void OnPointerUp(PointerEventData eventData)
        {
            Reset();
            callBack?.Invoke(false);
        }


        protected override void Update()
        {
            if (!stopWatch.IsRunning) return;
            if (stopWatch.ElapsedMilliseconds > pressMinTime)
            {
                Reset();
                callBack?.Invoke(true);
            }
        }

        protected override void Reset()
        {
            isRuning = false;
            if (stopWatch.IsRunning) stopWatch.Stop();
            stopWatch.Reset();
        }
    }

GameObjectExpand

public static class GameObjectExpand
    {
        public static ClickButton OnClick(this GameObject obj, Action callBack)
        {
            if (obj == null) return null;
            ClickEvent clickEvent = GetClickEvent(obj);
            ClickButton button = clickEvent.OnRegister<ClickButton>(callBack);
            return button;
        }

        public static DoubleClickButton OnDoubleClick(this GameObject obj, Action callBack)
        {
            if (obj == null) return null;
            ClickEvent clickEvent = GetClickEvent(obj);
            DoubleClickButton button = clickEvent.OnRegister<DoubleClickButton>(callBack);
            return button;
        }

        public static PressButton OnPress(this GameObject obj, Action callBack)
        {
            if (obj == null) return null;
            ClickEvent clickEvent = GetClickEvent(obj);
            PressButton button = clickEvent.OnRegister<PressButton>(callBack);
            return button;
        }

        static ClickEvent GetClickEvent(GameObject obj)
        {
            ClickEvent clickEvent = obj.GetComponent<ClickEvent>();
            if (clickEvent == null) clickEvent = obj.AddComponent<ClickEvent>();
            return clickEvent;
        }
    }

使用

public GameObject uiObj;
//点击事件注册
uiObj.OnClick(() =>
{
    Debug.Log("OnClick");
});
//双击事件注册
uiObj.OnDoubleClick(() =>
{
    Debug.Log("OnDoubleClick");
});
//长按事件注册
uiObj.OnPress(() =>
{
    Debug.Log("OnPress");
});
Unity引擎中,处理鼠标按时通常涉及到使用`OnMouseDown`、`OnMouseDrag`和`OnMouseUp`等事件事件通常发生在`OnMouseDrag`中,因为这个函数会在鼠标按下并开始移动期间持续触发,直到鼠标释放。 要在Unity中创建一个事件,你需要按照以下步骤操作: 1. **创建一个空物体或游戏对象**:首先,在场景中选择一个物体作为触发按的对象。 2. **添加组件**:给该物体添加一个`MonoBehaviour`组件,如`EventSystem`或`InputManager`,以便访问鼠标输入事件。 3. **编写脚本**:在新建的`C#`脚本中,你可以这样实现按功能: ```csharp using UnityEngine; using UnityEngine.EventSystems; public class LongClickExample : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler { private bool isDragging = false; void OnPointerDown(PointerEventData eventData) { if (!isDragging) { isDragging = true; Debug.Log("Mouse down - start of long press"); // 添加你的按开始处理逻辑 } } void OnPointerDrag(PointerEventData eventData) { if (isDragging) { // 按过程中执行的逻辑 } } void OnPointerUp(PointerEventData eventData) { isDragging = false; Debug.Log("Mouse up - end of long press"); // 按结束时执行的清理工作 } } ``` 在这个例子中,当鼠标按下并开始拖动时,`OnPointerDown`会被调用,并标记`isDragging`为`true`。如果用户继续拖动鼠标,`OnPointerDrag`将不断更新。一旦鼠标松开,`OnPointerUp`会触发,并且`isDragging`变为`false`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司军礼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值