Unity UI Button 事件优先级调整技术方案

Unity UI Button 事件优先级调整技术方案

在 Unity 项目开发过程中,针对 UI Button 的事件执行顺序控制是一个常见需求。本文详细阐述两种将新添加事件置于第一个执行位置的方法,旨在为开发者提供全面且专业的技术参考。

一、基于反射机制的事件插入方法

(一)核心函数:InsertClickListenerAtStart

以下函数实现了将指定的 UnityAction 插入到 ButtononClick 事件列表头部,确保其在其他已有事件之前执行。

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);
                            }
                        }
                    }
                }
            }
        }
    }
}

此方法通过反射深入到 UnityEventBaseButton 的内部实现机制,精准地操作事件调用列表。其核心步骤包括:

  • 首先,对目标事件进行去重操作,避免同一事件多次添加导致的逻辑混乱。
  • 接着,利用反射获取 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 变量。同时,将 ButtononClick 事件替换为一个新的空事件,以便后续插入自定义事件。
  • 当用户点击按钮时,OnPointerClick 方法被触发。首先,执行自定义的 BeforeOnClick 事件,这是我们希望优先执行的事件。
  • 接着,将 ButtononClick 事件恢复为原始备份的事件,并立即触发该事件,从而保证了自定义事件在原始事件之前执行。
  • 最后,销毁 OneTimeButtonEventInsertor 脚本实例,避免对后续事件处理产生不必要的干扰。

综上所述,这两种方法分别从底层反射和高层脚本代理的角度,为 Unity UI Button 事件优先级控制提供了有效的解决方案。开发者可根据项目的具体需求、性能要求以及代码维护性等因素,灵活选择合适的方法来实现按钮事件的精准排序。

相关推荐
传奇开心果编程3 小时前
【Flutter入门练中学】第1课:从零开始
学习·flutter·ui
goujunwe3 小时前
GEO 评估方法:如何自测你的内容,会不会被 AI 引用
搜索引擎·网络安全·游戏引擎·全文检索·中文分词
林川~0112 小时前
Unity 万能物理检测工具:射线检测 / 范围检测 / 层级过滤 / 编辑器可视化(可直接拿去用)
游戏·unity·c#·射线检测·通用工具
web守墓人13 小时前
【goed/ui】自定义组件设计思想篇
linux·windows·ui·golang
惟恋惜18 小时前
第 3 章 · 列表、滚动与 Lazy 深入:告别 RecyclerView 思维
ui·android jetpack
yi碗汤园20 小时前
MQTT消息队列遥测传输协议
网络·网络协议·unity
Htr_1 天前
Creem 2.0 使用指南:面向 AI 构建时代的资金平台
android·数据库·人工智能·ui·photoshop
林川~011 天前
Unity 常用 API 与核心类全解:从生命周期到协程,附性能避坑清单(Unity 6 适配)
unity·游戏引擎·常用api
sensen_kiss1 天前
CPT306 Coursework 1 个人游戏作业——坦克大战
unity·游戏程序·游戏策划
蒸鱼Yuzheng1 天前
Unity 与 Unreal 测试工具怎么回答:Profiler、日志、自动化与证据链
测试工具·unity·性能分析·游戏测试·unrealengine