【Unity知识点详解】Button点击事件拓展,单击、双击、长按实现

Button拓展

今天来聊一下关于Button的事件拓展,这里只是拿Button来举例,Unity中其他的UI组件如Toggle、Slider等都也适用。

我们知道在Button中我们可以通过onClick的方式来添加点击事件,但在游戏开发过程中我们往往对Button有着更多的功能需求,比如说双击、长按、按钮按下、按钮弹起等。这里举一个游戏中实际的例子,在游戏背包中的道具,单击道具时我们需要显示道具的tips框,双击时我们会去使用道具,长按时我们则可以拖动道具,当长按弹起时则道具回到原位或移动到新格子内。虽然这里的背包道具不是按钮,但在单个UI组件上集合了单击、双击、长按、按钮弹起等事件的响应。接下来将介绍如何拓展UI组件来实现这些功能。

首先我们来认识一下Selectable这个类,Selectable是所有交互组件的基类,Unity原生的Button组件就是继承了Selectable,我们要拓展Button功能也是对Selectable下的OnPointerDown、OnPointerUp等接口进行重写。

话不多说先上代码,代码有点长大家可以先跳过细节,后面慢慢讲解。

cs 复制代码
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;


public class ExButton : Button
{
    private enum EnumExButtonState
    {
        /// <summary>空</summary>
        None,
        /// <summary>鼠标按下</summary>
        PointerDown,
        /// <summary>鼠标按下</summary>
        PointerUp,
        /// <summary>单击</summary>
        Click,
        /// <summary>双击</summary>
        DoubleClick,
        /// <summary>长按开始</summary>
        PressBegin,
        /// <summary>长按</summary>
        Press,
        /// <summary>长按结束</summary>
        PressEnd,
    }

    /// <summary>按钮状态</summary>
    private EnumExButtonState mButtonState = EnumExButtonState.None;
    /// <summary>鼠标按下时间</summary>
    private float mPointerDownTime = 0.0f;
    [SerializeField]
    /// <summary>双击间隔时间</summary>
    private float mDoubleClickInterval = 0.2f;
    [SerializeField]
    /// <summary>长按开始时间</summary>
    private float mPressBeginTime = 0.3f;
    [SerializeField]
    /// <summary>长按间隔时间,0为每帧调用</summary>
    private float mPressIntervalTime = 0.2f;
    /// <summary>长按缓存时间</summary>
    private float mPressCacheTime = 0f;

    public Action OnClick { get; set; }
    public Action OnDoubleClick { get; set; }
    public Action OnPressBegin { get; set; }
    public Action OnPress { get; set; }
    public Action OnPressEnd { get; set; }

    public override void OnPointerDown(PointerEventData eventData)
    {
        if (OnDoubleClick != null)
        {
            if (mButtonState == EnumExButtonState.None)
            {
                mButtonState = EnumExButtonState.PointerDown;
                mPointerDownTime = Time.time;
            }
            else if (mButtonState == EnumExButtonState.PointerUp)
            {
                if (Time.time - mPointerDownTime < mDoubleClickInterval)
                {
                    mButtonState = EnumExButtonState.DoubleClick;
                    return;
                }
                else
                {
                    mButtonState = EnumExButtonState.PointerDown;
                    mPointerDownTime = Time.time;
                }
            }
        }

        if (OnPressBegin != null || OnPress != null || OnPressEnd != null)
        {
            if (mButtonState != EnumExButtonState.DoubleClick)
            {
                mButtonState = EnumExButtonState.PointerDown;
                mPointerDownTime = Time.time;
            }
        }

        if (OnClick != null) { }
    }

    public override void OnPointerUp(PointerEventData eventData)
    {
        if (OnDoubleClick != null)
        {
            if (mButtonState == EnumExButtonState.DoubleClick)
                return;
        }

        if (OnPressBegin != null || OnPress != null || OnPressEnd != null)
        {
            if (mButtonState == EnumExButtonState.Press)
            {
                mButtonState = EnumExButtonState.PressEnd;
                return;
            }
        }

        if (OnClick != null)
        {
            if (mButtonState == EnumExButtonState.PointerDown)
                mButtonState = EnumExButtonState.PointerUp;
        }
    }

    private void Update()
    {
        ProcessUpdate();
        ResponseButtonState();
    }

    private void ProcessUpdate()
    {
        if (OnDoubleClick != null) { }

        if (OnPressBegin != null || OnPress != null || OnPressEnd != null)
        {
            if (mButtonState == EnumExButtonState.PointerDown)
            {
                if (Time.time - mPointerDownTime > mPressBeginTime)
                {
                    mButtonState = EnumExButtonState.PressBegin;
                    mPressCacheTime = 0f;
                    return;
                }
            }
        }

        if (OnClick != null)
        {
            if (mButtonState == EnumExButtonState.PointerUp)
            {
                if (OnDoubleClick != null)
                {
                    if (Time.time - mPointerDownTime > mDoubleClickInterval)
                        mButtonState = EnumExButtonState.Click;
                }
                else
                {
                    mButtonState = EnumExButtonState.Click;
                }
            }
        }
    }

    private void ResponseButtonState()
    {
        switch (mButtonState)
        {
            case EnumExButtonState.None:
                break;
            case EnumExButtonState.Click:
                OnClick?.Invoke();
                mButtonState = EnumExButtonState.None;
                break;
            case EnumExButtonState.DoubleClick:
                OnDoubleClick?.Invoke();
                mButtonState = EnumExButtonState.None;
                break;
            case EnumExButtonState.PressBegin:
                OnPressBegin?.Invoke();
                mButtonState = EnumExButtonState.Press;
                break;
            case EnumExButtonState.Press:
                {
                    mPressCacheTime += Time.deltaTime;
                    if (mPressCacheTime >= mPressIntervalTime)
                    {
                        mPressCacheTime = mPressCacheTime - mPressIntervalTime;
                        OnPress?.Invoke();
                    }
                    break;
                }
            case EnumExButtonState.PressEnd:
                OnPressEnd?.Invoke();
                mButtonState = EnumExButtonState.None;
                break;
            default:
                break;
        }
    }
}

ExButton组件功能的拓展需要继承自Button类,并且重写OnPointerDown、OnPointerUp方法(这里根据需求只重写了OnPointerDown、OnPointerUp方法,大家可以根据自己的需求重写Selectable下的方法)。根据需求我们要实现点击、双击、长按、长按开始、长按结束事件的回调,所以在代码中我们提供了OnClick、OnDoubleClick、OnPressBegin、OnPress、OnPressEnd回调方法。

cs 复制代码
public Action OnClick { get; set; }
public Action OnDoubleClick { get; set; }
public Action OnPressBegin { get; set; }
public Action OnPress { get; set; }
public Action OnPressEnd { get; set; }

逻辑采用了单状态机来实现,在OnPointerDown、OnPointerUp、Update方法中去改变成员mButtonState的状态,最终在ResponseButtonState方法中根据mButtonState的状态去进行事件的回调。

在逻辑中也进行了事件回调的优化处理,当OnClick、OnDoubleClick、OnPressBegin、OnPress、OnPressEnd所有回调都被注册时,会优先处理OnDoubleClick,其次是OnPressBegin、OnPress、OnPressEnd,最后才是OnClick。例如当OnDoubleClick未被注册时,则会跳过OnDoubleClick对应的逻辑判断,提前处理并响应其他回调事件。

base.OnPointerDown、base.OnPointerUp

cs 复制代码
public override void OnPointerDown(PointerEventData eventData)
{
	base.OnPointerDown(eventData);
}

public override void OnPointerUp(PointerEventData eventData)
{
	base.OnPointerUp(eventData);
}

在重写OnPointerDown、OnPointerUp后如需保留原有功能,需要调用base.OnPointerDown、base.OnPointerUp方法。只有调用基类方法,按钮的按下颜色改变才会有效果,当然这些基类方法也可以在别处调用。

官方文档连接

Selectable文档连接:https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/script-Selectable.html

Selectable类API文档连接:https://docs.unity3d.com/Packages/com.unity.ugui@2.0/api/UnityEngine.UI.Selectable.html?q=selectable

相关推荐
魔士于安1 小时前
Unity太空战舰完整工程,包含战损,实时战损
游戏·unity·游戏引擎·贴图·模型
Nuopiane2 小时前
MyPal3(10)视锥体剔除
unity
爱搞虚幻的阿恺3 小时前
RPG游戏开发【加餐】实现游戏小地图的简单方法
游戏·ue5·游戏引擎·虚幻
海海不瞌睡(捏捏王子)3 小时前
Unity知识点概要
unity·1024程序员节
学不完的4 小时前
Zrlog面试问答及问题解决方案
linux·运维·nginx·unity·游戏引擎
小清兔4 小时前
unity游戏制作中问题汇总(持续更新)
游戏·unity·游戏引擎
WiChP6 小时前
【V0.1B4】从零开始的2D游戏引擎开发之路
前端·javascript·游戏引擎
mxwin18 小时前
Unity Shader SRP深入理解内置渲染管线与 URP/HDRP 的底层架构差异
unity·游戏引擎·单一职责原则
mxwin1 天前
Unity Shader 渲染管线深度解析 — Shader 三阶段
unity·游戏引擎·shader·uv
mxwin1 天前
Unity Shader 数学与几何变换 深入理解渲染管线中的坐标系转换:从模型空间到屏幕空间的完整变换链
unity·游戏引擎·shader