button拓展
今天来聊一下关于button的事件拓展,这里只是拿button来举例,unity中其他的ui组件如toggle、slider等都也适用。
我们知道在button中我们可以通过onclick的方式来添加点击事件,但在游戏开发过程中我们往往对button有着更多的功能需求,比如说双击、长按、按钮按下、按钮弹起等。这里举一个游戏中实际的例子,在游戏背包中的道具,单击道具时我们需要显示道具的tips框,双击时我们会去使用道具,长按时我们则可以拖动道具,当长按弹起时则道具回到原位或移动到新格子内。虽然这里的背包道具不是按钮,但在单个ui组件上集合了单击、双击、长按、按钮弹起等事件的响应。接下来将介绍如何拓展ui组件来实现这些功能。
首先我们来认识一下selectable这个类,selectable是所有交互组件的基类,unity原生的button组件就是继承了selectable,我们要拓展button功能也是对selectable下的onpointerdown、onpointerup等接口进行重写。
话不多说先上代码,代码有点长大家可以先跳过细节,后面慢慢讲解。
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)
{
base.onpointerdown(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)
{
mbuttonstate = enumexbuttonstate.pointerdown;
}
}
public override void onpointerup(pointereventdata eventdata)
{
base.onpointerup(eventdata);
if (ondoubleclick != null)
{
if (mbuttonstate == enumexbuttonstate.pointerdown)
{
mbuttonstate = enumexbuttonstate.pointerup;
return;
}
else 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回调方法。
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
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
发表评论