前言
写这篇文章的契机是因为要实现一个按钮交互的不同状态的视觉反馈,在深入了解了Button的原生选择状态后发现,这里面暗藏玄机,值得留下一笔。
Button 状态属性介绍
在Inspector面板中可以看到,unity提供了该按钮的五种状态,分别是:
- Normal:普通状态,即什么也发现
- Highlighted:高亮状态,即pointer进入按钮后,没有按下等其他操作
- Pressed:按下状态,即pointer在按钮内按下
- Disabled:禁用状态,对应参数Interactable(是否可交互)
并且在Transition中可以选择状态的表现形式,分别为
- Color Tint:颜色过渡
- Sprite Swap:图片过渡
- Animation:动画过渡
玄机之处
按钮的选择状态改变由谁决定的呢,这里就要进入Unity的底层代码查看了。
负责选择状态的判断和实现逻辑都是在”Selectable.cs”脚本里完成,其中里面有个参数名为“currentSelectionState
”,这个就是这篇文章的主人公了。这里面可以很清楚的看到,指针的不同事件触发对应着currentSelectionState
不同的状态。
有了上述的了解,回到按钮的表现层来仔细观察一下所说的“暗藏玄机”的地方。
为了方便讲解,我把按钮的状态做了可视化的显示(后文有在线体验连接)。
通过对按钮不同的交互可以发现:
- 按钮一开始默认是Normal状态,当指针进入按钮后(OnPointerEnter),会立即触发Highlighted状态。如果此时没有做按下的动作,离开按钮(OnPointerExit),则会返回到Normal状态
- 其次,如果当指针进入了按钮后,并且执行了按下(OnPointerDown)的动作且没有松开指针(OnPointerUp)的这个时间段内,按钮是处于Pressed状态。
- 接着,在按下并且释放了指针后,按钮会由Pressed状态转成Selected状态。
- 此时,如果指针离开按钮,或者离开后重新进入按钮,按钮会一直是Selected状态。
- 只有当指针执行了按下的动作,Selected的状态才会被取消。也就是说,当按钮被Selected之后,只有按别的地方才能让按钮退出Selected的状态。
- 最后,Disable状态只由Interactable的开关值来决定
这里面”有趣“的地方,就是Selected状态。我不清楚Unity这样设计的具体原因是什么,但是可以理解为按钮被按下之后,Selected的状态其实相当于一个”lock(锁定)“状态,需要执行一步”unlock(解锁)“的动作才能将按钮返回普通状态。
从设计的角度来看,这视觉反馈其实是与交互状态是起冲突的,对底层原理不熟悉的人,是不会顾忌这么多的。我一个按钮,交互时的状态无非就几种,哪需要管这个按钮是不是被锁定了,我要的就是鼠标移进去按钮,按钮显示为Highlighted状态,这没问题,然后点击按钮的时候,按钮显示为Pressed状态,这也没问题,可是松开之后,这锁住状态是什么鬼,我鼠标就在按钮内啊,应该是回到刚刚Highlight的状态啊,甚至是我移出去按钮,也回不去Normal状态啊,这unity什么玩意儿啊!
也就是说,明明很简单的交互状态切换,在原本的Button上却达不到实现要求。
可替代方案——Alternative Button
不知道是不是会有人为了这样的效果,而写一套专门的逻辑,通过TriggerEvent对Pointer的不同事件监听然后改变按钮的颜色或者贴图,这样做不是不能实现,但是按钮一多起来的话就非常的难维护,不是一个可取的方案。
为了达到正确的效果,并且以最小开销的方式来做的话,不妨试试以下所说的方式。
首先需要确定的是,我的按钮只是用于“按钮”作用,不需要所谓的“Selected”判定。
实现原理:
因为currentSelectionState
不是对外的参数,没有办法通过直接修改这个状态值达到效果。
只能通过"Selectable.cs"提供的接口函数—— DoStateTransition() ,这个函数的作用就是当状态变化的时候执行对应的动画过渡效果,也就是我们刚刚看到的颜色变化。
/// 底层代码
/// <summary>
/// Transition the Selectable to the entered state.
/// </summary>
/// <param name="state">State to transition to</param>
/// <param name="instant">Should the transition occur instantly.</param>
protected virtual void DoStateTransition(SelectionState state, bool instant)
{
.....
}
有了这个接口就好办了,就可以对Button脚本进行拓展了。
首先,可以明确的清楚现在Button所缺少的状态切换有三个地方:
- state1:点击按钮后,如果没有离开按钮,应该切换为Highlighted状态
- state2:点击按钮后,离开按钮瞬间,应该切换为Normal状态
- state3:点击按钮后,离开按钮又返回按钮,应该切换为Highlighted状态
然后,就在对应指针事件中执行改变状态的动画即可。
state1的补充放在OnClicked事件里完成
onClick.AddListener(() => {
DoStateTransition(SelectionState.Highlighted, false); });
state2的补充放在OnPointerExit里完成
DoStateTransition(SelectionState.Normal, true);
state3的补充放在OnPointerEnter里完成
DoStateTransition(SelectionState.Highlighted, true);
完整的代码如下,若要实现效果直接将该脚本替换成要实现效果的按钮的Button脚本
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class PicoButton: Button
{
protected override voidAwake()
{
base.Awake();
onClick.AddListener(() => {
DoStateTransition(SelectionState.Highlighted, false); });
}
public override void OnPointerEnter(PointerEventData eventData)
{
base.OnPointerEnter(eventData);
if (interactable)
{
DoStateTransition(SelectionState.Highlighted, true);
}
}
public override void OnPointerExit(PointerEventData eventData)
{
base.OnPointerExit(eventData);
if (interactable)
{
DoStateTransition(SelectionState.Normal, true);
}
}
}
同样,我将改进后的Button做了可视化的显示(后文有在线体验连接)。
值得注意的是,这里实现只是从视觉上改变了状态,实际上按钮的currentSelectionState
还是保持原样(即按下还是被selected了),但是Button又没有获取这个state的状态接口,所以这并不影响我们正常的交互。
最后附上按钮Demo在线体验,可自行体验两者区别:
----> UguiButtonSelectionStateVisualization