You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Unity UI事件回调链?寻求非单例替代方案

解决Unity中避免回调链传递事件的方案(非单例版)

你现在的痛点很明确——不想让事件从C→B→A层层传递,也不想用单例EventManager,下面给你几个实用的非单例方案,都是Unity里常用的思路:

方案1:直接透传回调,跳过B的中转

核心思路是让A把自己的处理方法直接传递给C,而不是通过B来转发。具体可以让B在创建C的时候,把A的回调直接绑定到C的事件上,B本身不再持有事件。

修改后的代码示例:

class A : MonoBehaviour { 
    void Start() { 
        GameObject bGO = InstantiateGameObject(); 
        B b = bGO.GetComponent<B>(); 
        // 直接把A的处理方法传给B,让B转交给C
        b.SetButtonCallback(DoSmthinA); 
    } 
    private void DoSmthinA() { Debug.Log("A"); } 
} 

class B : MonoBehaviour { 
    private C _cInstance;

    void Start() { 
        GameObject cGO = InstantiateGameObject(); 
        _cInstance = cGO.GetComponent<C>(); 
    } 

    // 新增方法,接收A的回调并绑定到C
    public void SetButtonCallback(System.Action callback) {
        if(_cInstance != null) {
            _cInstance.m_onButtonClicked += callback;
        }
    }
} 

class C : MonoBehaviour { 
    public UnityEngine.UI.Button m_button; 
    public event System.Action m_onButtonClicked; 
    void Start() { 
        m_button.onClick.AddListener(OnButtonClicked); 
    } 
    private void OnButtonClicked() { 
        Debug.Log("C"); 
        m_onButtonClicked?.Invoke(); 
    } 
}

这样B就只是个“传递者”,不再需要维护自己的事件,直接把A的回调绑定到C上,事件触发时C直接通知A,砍掉了中间的回调链。

方案2:用ScriptableObject实现无引用事件总线

这个方案是Unity里非常流行的非单例事件方案,用ScriptableObject作为事件的“载体”,C触发事件,A订阅事件,完全不需要A、B、C之间有直接引用。

步骤1:创建ScriptableObject事件类

[CreateAssetMenu(fileName = "ButtonClickedEvent", menuName = "Events/Button Clicked Event")]
public class ButtonClickedEvent : ScriptableObject {
    private event System.Action _onTriggered;

    public void Subscribe(System.Action callback) {
        _onTriggered += callback;
    }

    public void Unsubscribe(System.Action callback) {
        _onTriggered -= callback;
    }

    public void Trigger() {
        _onTriggered?.Invoke();
    }
}

你可以在Project窗口右键创建这个SO实例,命名比如ButtonClickedEvent.asset。

步骤2:修改C和A的代码

class A : MonoBehaviour { 
    public ButtonClickedEvent buttonClickedEvent; // 在Inspector里拖入刚才创建的SO实例

    void OnEnable() { 
        buttonClickedEvent.Subscribe(DoSmthinA); 
    } 

    void OnDisable() { 
        buttonClickedEvent.Unsubscribe(DoSmthinA); 
    } 

    private void DoSmthinA() { Debug.Log("A"); } 
} 

class C : MonoBehaviour { 
    public UnityEngine.UI.Button m_button; 
    public ButtonClickedEvent buttonClickedEvent; // 同样拖入SO实例

    void Start() { 
        m_button.onClick.AddListener(OnButtonClicked); 
    } 
    private void OnButtonClicked() { 
        Debug.Log("C"); 
        buttonClickedEvent.Trigger(); 
    } 
}

B的代码完全不需要修改,甚至B可以不知道这个事件的存在!因为A和C都是通过SO来通信,没有任何实例引用,完美解决回调链问题,而且不是单例——你可以创建多个SO实例,对应不同的按钮事件,非常灵活。

方案3:让A直接获取C的实例并订阅

如果B创建C后,能把C的实例暴露给A,那A可以直接订阅C的事件,跳过B。比如B里保存C的引用,提供一个公共方法让A获取:

class A : MonoBehaviour { 
    void Start() { 
        GameObject bGO = InstantiateGameObject(); 
        B b = bGO.GetComponent<B>(); 
        // 获取B创建的C实例,直接订阅
        C c = b.GetCInstance();
        if(c != null) {
            c.m_onButtonClicked += DoSmthinA;
        }
    } 
    private void DoSmthinA() { Debug.Log("A"); } 
} 

class B : MonoBehaviour { 
    private C _cInstance;

    void Start() { 
        GameObject cGO = InstantiateGameObject(); 
        _cInstance = cGO.GetComponent<C>(); 
        // 这里可以取消B自己的订阅,除非B本身需要处理这个事件
        // _cInstance.m_onButtonClicked += DoSmthinB; 
    } 

    // 提供公共方法返回C的实例
    public C GetCInstance() {
        return _cInstance;
    }

    // 如果B不需要处理事件,可以删掉这个方法
    private void DoSmthinB() { Debug.Log("B"); } 
} 

这个方案最简单直接,但缺点是A需要知道B持有C的关系,耦合度比SO方案高一点,适合小型项目或者简单场景。

总结一下,最推荐的是ScriptableObject事件总线,既解耦又灵活,完全符合你的非单例需求;如果场景简单,直接透传回调或者直接获取C实例也能快速解决问题。

内容的提问来源于stack exchange,提问作者Lovecry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 07:57:42