Unity UI Button 事件优先级调整技术方案
在 Unity 项目开发过程中,针对 UI Button 的事件执行顺序控制是一个常见需求。本文详细阐述两种将新添加事件置于第一个执行位置的方法,旨在为开发者提供全面且专业的技术参考。
一、基于反射机制的事件插入方法
(一)核心函数:InsertClickListenerAtStart
以下函数实现了将指定的 UnityAction
插入到 Button
的 onClick
事件列表头部,确保其在其他已有事件之前执行。
csharp
private void InsertClickListenerAtStart(Button button, UnityAction action)
{
// 1. 事件去重处理
var onClick = button.onClick;
onClick.RemoveListener(action); // 确保不会重复添加
// 2. 反射获取 m_Calls 字段
var field = typeof(UnityEventBase).GetField("m_Calls", BindingFlags.NonPublic | BindingFlags.Instance);
if (field!= null)
{
var invokableCallList = field.GetValue(onClick);
if (invokableCallList!= null)
{
// 3. 获取 m_RuntimeCalls 字段
var runtimeCallsField = invokableCallList.GetType().GetField("m_RuntimeCalls", BindingFlags.NonPublic | BindingFlags.Instance);
if (runtimeCallsField!= null)
{
// 4. 创建新的 InvokableCall
var invokableCallType = typeof(UnityEvent).Assembly.GetType("UnityEngine.Events.InvokableCall");
if (invokableCallType!= null)
{
var constructor = invokableCallType.GetConstructor(new[] { typeof(UnityAction) });
if (constructor!= null)
{
var newCall = constructor.Invoke(new object[] { action });
// 5. 获取 Insert 方法并插入新事件
var runtimeCalls = runtimeCallsField.GetValue(invokableCallList);
var insertMethod = runtimeCalls.GetType().GetMethod("Insert");
if (insertMethod!= null)
{
insertMethod.Invoke(runtimeCalls, new object[] { 0, newCall });
// 6. 设置 m_NeedsUpdate 为 true
var needsUpdateField = invokableCallList.GetType().GetField("m_NeedsUpdate", BindingFlags.NonPublic | BindingFlags.Instance);
if (needsUpdateField!= null)
{
needsUpdateField.SetValue(invokableCallList, true);
}
}
}
}
}
}
}
}
此方法通过反射深入到 UnityEventBase
和 Button
的内部实现机制,精准地操作事件调用列表。其核心步骤包括:
- 首先,对目标事件进行去重操作,避免同一事件多次添加导致的逻辑混乱。
- 接着,利用反射获取
m_Calls
字段,该字段存储了事件的可调用列表信息。在确保获取成功后,进一步获取m_RuntimeCalls
字段,这是实际存储运行时事件调用信息的关键结构。 - 随后,根据
UnityEvent
程序集动态获取InvokableCall
类型,并创建一个新的实例,该实例包装了我们要插入的action
。 - 最后,获取
m_RuntimeCalls
列表的Insert
方法,将新创建的InvokableCall
实例插入到列表头部(索引为 0),并设置m_NeedsUpdate
标志为true
,以确保事件系统在后续处理中能够正确更新状态。
二、基于脚本代理的事件暂存与插入方法
(一)核心脚本:OneTimeButtonEventInsertor
通过创建 OneTimeButtonEventInsertor
脚本,实现了一种更为直观的事件顺序控制方式。
csharp
public class OneTimeButtonEventInsertor : MonoBehaviour, IPointerClickHandler
{
private Button button;
// 自定义事件,用于在 EventTrigger 之前调用
public UnityEvent BeforeOnClick = new UnityEvent();
public Button.ButtonClickedEvent OnClick;
private void Awake()
{
button = GetComponent<Button>();
OnClick = button.onClick;
button.onClick = new Button.ButtonClickedEvent();
}
public void OnPointerClick(PointerEventData eventData)
{
// 先执行 BeforeOnClick 事件
BeforeOnClick?.Invoke();
button.onClick = OnClick;
button.onClick.Invoke();
Destroy(this);
}
}
该脚本的工作原理如下:
- 在
Awake
方法中,获取目标Button
组件,并备份其原始的onClick
事件到OnClick
变量。同时,将Button
的onClick
事件替换为一个新的空事件,以便后续插入自定义事件。 - 当用户点击按钮时,
OnPointerClick
方法被触发。首先,执行自定义的BeforeOnClick
事件,这是我们希望优先执行的事件。 - 接着,将
Button
的onClick
事件恢复为原始备份的事件,并立即触发该事件,从而保证了自定义事件在原始事件之前执行。 - 最后,销毁
OneTimeButtonEventInsertor
脚本实例,避免对后续事件处理产生不必要的干扰。
综上所述,这两种方法分别从底层反射和高层脚本代理的角度,为 Unity UI Button 事件优先级控制提供了有效的解决方案。开发者可根据项目的具体需求、性能要求以及代码维护性等因素,灵活选择合适的方法来实现按钮事件的精准排序。