Unity中仅单个PanelControl实例事件非空的原因排查
问题分析与解决方案
核心错误原因
- 静态单例覆盖问题:你用了
public static PanelControl panelControl;,并且在OnEnable()里赋值panelControl = this;。这意味着每个PanelControl实例启用时,都会把这个静态变量覆盖成自己。最终只有最后一个启用的实例会被存在这个静态变量中。如果你的订阅脚本是通过PanelControl.panelControl.OnClickedPanel += ...来订阅事件,那只会订阅这个最后实例的事件,其他实例的OnClickedPanel自然是null——因为根本没人订阅它们的事件。 - 事件逻辑断裂:你的
MakeRoom方法依赖MenuControl的OnMakeRoom事件,但这个事件的触发和OnClickedPanel的关联逻辑不完整,加上前面的单例问题,导致大部分实例无法收到关闭通知。
修正方案一:直接管理面板状态(更简洁)
去掉静态变量和复杂事件,在点击时直接关闭其他面板,逻辑更直观:
public class PanelControl : MonoBehaviour { [SerializeField] GameObject panel; // 用静态集合保存所有PanelControl实例 private static List<PanelControl> allPanelControls = new List<PanelControl>(); private void OnEnable() { // 实例启用时加入集合 if (!allPanelControls.Contains(this)) { allPanelControls.Add(this); } } public void TogglePanel() { bool shouldOpen = !panel.activeSelf; // 如果要打开当前面板,先关闭所有其他面板 if (shouldOpen) { CloseAllOtherPanels(); } // 切换当前面板状态 panel.SetActive(shouldOpen); } private void CloseAllOtherPanels() { foreach (var control in allPanelControls) { if (control != this) { control.panel.SetActive(false); } } } private void OnDisable() { // 实例禁用时从集合移除 allPanelControls.Remove(this); } }
修正方案二:保留事件模式(修正订阅逻辑)
如果一定要用事件实现,需要确保每个PanelControl实例的事件都被订阅,而不是只订阅静态变量指向的那个:
1. 修改PanelControl脚本
去掉静态单例变量,保留事件:
public class PanelControl : MonoBehaviour { [SerializeField] GameObject panel; public event Action<string> OnClickedPanel; public void TogglePanel() { bool isActive = panel.activeSelf; panel.SetActive(!isActive); // 只有打开面板时才触发事件,通知其他面板关闭 if (!isActive) { OnClickedPanel?.Invoke(this.name); } } }
2. 修改订阅脚本(比如PanelManager)
在Start里找到所有PanelControl实例,逐个订阅事件:
public class PanelManager : MonoBehaviour { private void Start() { // 获取所有PanelControl实例 var allPanels = FindObjectsOfType<PanelControl>(); foreach (var panel in allPanels) { panel.OnClickedPanel += HandlePanelClicked; } } private void HandlePanelClicked(string openedPanelName) { // 关闭所有非当前打开的面板 var allPanels = FindObjectsOfType<PanelControl>(); foreach (var panel in allPanels) { if (panel.name != openedPanelName) { panel.panel.SetActive(false); } } } private void OnDestroy() { // 销毁时取消订阅,避免内存泄漏 var allPanels = FindObjectsOfType<PanelControl>(); foreach (var panel in allPanels) { panel.OnClickedPanel -= HandlePanelClicked; } } }
内容的提问来源于stack exchange,提问作者L.Vezzani
相关产品推荐
相关产品推荐

