看了siki老师的UI框架,受益匪浅,非常感谢siki老师。。
在此提出几点改进建议,请老师和同学们批评指正:
1、教程中使用json去加载所有UI面板的信息,事实上我认为是没有必要的。因为所有的面板肯定具有一个窗口基类(视频教程中是“BasePanel"),因此,只要这么做就可以了:
    public void LoadUI()
    {
        _uiResources = new Dictionary<string, HXUIWindowBase>();
        HXUIWindowBase[] objs = Resources.FindObjectsOfTypeAll<HXUIWindowBase>();
        foreach( HXUIWindowBase obj in objs)
        {
            _uiResources.Add(obj.ID, obj);
        }
    }
2、使用栈来管理所有的面板,的确具很方便,但是有一个坏处,就是同一时刻只能有一个面板(位于栈顶的那个)处于可交互状态,其他的都不可以交互。但实际开发中,各种面板不能一概而论。比如:背包面板应该可以跟商人出售东西的面板同时可以交互,人物穿装备的面板应该可以和背包、商人面板等同时交互.....但,还有一些面板必须是模态的,就是说一旦弹出所有其他面板都不能再交互,比如一个MessageBox确定提示:”该物品是贵重装备,您确定要出售该物品吗?“、”您确定要放弃这个任务吗“等等此类的提示。因此,用栈来管理就不太方便不太好了。
我的改进方案是:
首先。将面板分为三类:
1、一般窗口(可以与其他窗口共存);
2、模态窗口,只能独占交互。
3、Tips窗口,仅仅是弹出物品信息等,不需要交互。
这样,增加一个窗口类型的枚举,给每个面板分配一个类型。
public enum HXUIType
{
    NormalWindow,
    ModalWindow,
    TipsWindow
}
然后,仍然使用字典而不是栈来存储窗口信息。当一个面板被激活时(调用PushWindow),先判断这个窗口是什么类型的窗口,如果是Normal或者tips,不需要做额外操作,如果是modalWindow,则首先禁用字典中所有的其他窗口;然后再显示该窗口。显示的步骤是:先检查字典中是否存在,如果不存在,就实例化,如果已经存在,直接修改它的z值(或者深度之类的),使它位于顶层。
以上是我的建议,欢迎老师和同学们品评斧正。
另外,有一个问题还是百思不得其解,请siki老师和同学们指教:
就是,当在某处时,可能需要弹出一个模态信息框,让玩家选择”是“或者”否“,比如,当玩家点了放弃任务的按钮时,我弹出一个对话框问他是否确定。然后我怎么才能知道他点了是还是否呢??
比如:
public SomeWindow : public BasePanel {
     // Other code .........
     public void OnCancelQuest() // 放弃任务按钮的处理
     {
         // Pop a Message Box With Information "Are you soure ? "
            PanelManager.Instance.Push( PanelType.areYouSoureWindow );
         if( // 这里我怎么知道用户点了 确定 还是 取消 ??? )
         {
             // 用户点了确定。确实要放弃任务了。
         }
         else {
             // 只是虚晃一下而已。。并没有真放弃。。
         }
         // 或者,如何实现下面的功能:
         PanelManager.Hinstance.Push( PanelType.areYouSoure ).OnClickOK( CancelTask());
         
     }
}
贴出改进后的完整代码:
// 基础信息类及管理类
using System;
using System.Collections.Generic;
using UnityEngine;
public enum HXUIType
{
    NormalWindow,
    ModalWindow,
    TipsWindow
}
public class HXUIManager
{
    private static HXUIManager _Instance = null;
    private static Dictionary<string, HXUIWindowBase> _uiResources = null;
    private static Dictionary<string, HXUIWindowBase> _uiInstance = null;
    private static Dictionary<string, HXUIWindowBase> _uiShowing;
    /// <summary>
    /// 只读唯一实例
    /// </summary>
    public static HXUIManager hInstance
    {
        get
        {
            if (_Instance == null)
                _Instance = new HXUIManager();
            return _Instance;
        }
    }
    private HXUIManager()
    {
        _uiInstance = new Dictionary<string, HXUIWindowBase>();
        _uiShowing = new Dictionary<string, HXUIWindowBase>();
    }
    /// <summary>
    /// 开启窗口并入栈
    /// </summary>
    /// <param name="win">指定要开启的窗口</param>
    public void ShowWindow( HXUIWindowBase win )
    {
        if (win != null)
        {
            bool bNeedProcess = true;
            if(( win.Type == HXUIType.ModalWindow ) && ( _uiShowing.Count > 0 ))
            {
                foreach( var wb in _uiShowing )
                {
                    if (wb.Key == win.ID)
                    {
                        win.OnShowWindow();
                        bNeedProcess = false;
                    }
                    else
                        wb.Value.OnPauseWindow();
                }
            }
            if( bNeedProcess )
            {
                if (_uiShowing.TryGet(win.ID) == null)
                    _uiShowing.Add(win.ID, win);
                win.OnShowWindow();
            }
        }
    }
    /// <summary>
    /// 开启窗口并入栈
    /// </summary>
    /// <param name="id">要开启窗口的ID</param>
    public void ShowWindow( string id)
    {
        ShowWindow(GetWindow(id));
    }
    public void HideWindow(HXUIWindowBase win )
    {
        if( win  != null )
        {
            if (_uiShowing.TryGet(win.ID) != null)
            {
                _uiShowing.Remove(win.ID);
                win.OnHideWindow();
                if( win.Type == HXUIType.ModalWindow )
                {
                    int i = win.transform.GetSiblingIndex();
                    while (i > 0)
                    {
                        --i;
                        HXUIWindowBase brother = win.transform.parent.GetChild(i).GetComponent<HXUIWindowBase>();
                        if (brother.bIsShow )
                        {
                            brother.OnResume();
                            if (brother.Type == HXUIType.ModalWindow)
                                break;
                        }
                    }
                }
            }
        }
    }
    public void HideWindow( string id  )
    {
        HideWindow(_uiShowing.TryGet(id));
    }
    public HXUIWindowBase GetWindow( string id)
    {
        HXUIWindowBase wb = _uiInstance.TryGet(id);
        if( wb == null )
        {
            wb = _uiResources.TryGet(id);
            if (wb != null)
            {
                GameObject ob = GameObject.Instantiate(wb.gameObject, HXUIRoot.RootCanvas, false);
                wb = ob.GetComponent<HXUIWindowBase>();
                _uiInstance.Add(id, wb);
            }
        }
        return wb;
    }
    public void LoadUI()
    {
        _uiResources = new Dictionary<string, HXUIWindowBase>();
        HXUIWindowBase[] objs = Resources.FindObjectsOfTypeAll<HXUIWindowBase>();
        foreach( HXUIWindowBase obj in objs)
        {
            _uiResources.Add(obj.ID, obj);
        }
    }
}
// 窗口面板基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HXUIWindowBase : MonoBehaviour {
    [SerializeField] protected string m_id;
    [SerializeField] protected HXUIType m_type;
    protected CanvasGroup m_canvasGroup = null;
    public HXUIType Type
    {
        get
        {
            return m_type;
        }
    }
    public string ID
    {
        get
        {
            return m_id;
        }
    }
    public bool bIsShow
    {
        get
        {
            return gameObject.activeInHierarchy;
        }
    }
    public bool CanInteractive
    {
        get
        {
            return m_canvasGroup.blocksRaycasts;
        }
    }
    protected void Awake()
    {
        m_canvasGroup = GetComponent<CanvasGroup>();
        if( m_canvasGroup == null)
        {
            Debug.LogError("Window Must Have A CanvasGroup Component");
        }
    }
    public void HideWindow()
    {
        HXUIManager.hInstance.HideWindow(this.ID);
    }
    public void OnShowWindow()
    {
        if ( ! bIsShow )
            gameObject.SetActive(true);
        m_canvasGroup.blocksRaycasts = true;
        
        // 将窗口移动到最顶端
        transform.SetSiblingIndex(transform.parent.childCount);
    }
    public void OnPauseWindow()
    {
        m_canvasGroup.blocksRaycasts = false;
    }
    public void OnResume()
    {
        m_canvasGroup.blocksRaycasts = true;
    }
    public void OnHideWindow()
    {
        if ( bIsShow )
        {
            gameObject.SetActive(false);
        }
    }
}
PanelManager.Instance.Push( PanelType.areYouSoureWindow );
在弹出 选择是和否的框的时候,注册一下是和否的点击事件,把是和否的处理代码放在是和否的点击处理方法里面
public class HXUIConfirmBox : HXUIWindowBase
{
    [SerializeField]
    protected Text m_text;  // 确认对话框上的文字。
    [SerializeField]
    protected Button m_okButton;   // 确认按钮
    [SerializeField]
    protected Button m_cancelButton;  // 取消按钮
    protected Text m_btOKText;      //确认按钮上的文字
    protected Text m_btCancelText;  // 取消按钮上的文字
    protected  override void Awake()
    {
        base.Awake();
        // 下面是一系列的获取,组建内的子物体。便于显示信息。
        // 子物体包括一个文本,两个按钮,用户可以手工指定,也可以保持默认,系统会自动检索。
        if (m_text == null)
        {
            // 如果用户没有指定哪个组建代表主文字,那么就默认孩子中的第一个Text组件。
            m_text = transform.Find("Text").GetComponent<Text>();
        }
        if (m_okButton == null)
        {
            m_okButton = transform.Find("OK").GetComponent<Button>();
        }
        if (m_cancelButton == null)
        {
            m_cancelButton = transform.Find("Cancel").GetComponent<Button>();
        }
        if (m_okButton != null)
        {
            m_btOKText = m_okButton.GetComponentInChildren<Text>();
        }
        if (m_cancelButton != null)
        {
            m_btCancelText = m_cancelButton.GetComponentInChildren<Text>();
        }
    }
    // 这个函数用来修改确认框的内容。
    // 参数意义依次是:确认框上要显示的文本信息、当用户点ok的时候的回调函数、当用户点取消按钮时的回调函数、OK按钮上的文本、Cancel按钮上的文本
    public void ConfirmBox( string text = null, UnityAction okCall = null, UnityAction cancelCall = null, string okText = null, string cancelText = null )
    {
        m_text.text = (text == null) ? "您确定吗?" : text;
        
        if((m_btOKText != null) && ( okText != null ))
            m_btOKText.text = okText;
        
        if(( m_btCancelText != null) && ( cancelText != null ))
            m_btCancelText.text = cancelText;
 
        // 清理OK按钮上的监听事件,如果不清理,上次调用时的回调这次还会去调用,所以必须清理。
        m_okButton.onClick.RemoveAllListeners();
        // 如果用户指定了OK按钮的回调,那么就监听他,否则给定一个关闭本对话框的默认功能。
        if (okCall != null)
            m_okButton.onClick.AddListener(okCall);
        else
            m_okButton.onClick.AddListener(new UnityAction(ButtonClickDefault));
        m_cancelButton.onClick.RemoveAllListeners();
        // 如果用户指定了OK按钮的回调,那么就监听他,否则给定一个关闭本对话框的默认功能。
        if (cancelCall != null)
            m_cancelButton.onClick.AddListener(cancelCall);
        else
            m_cancelButton.onClick.AddListener(new UnityAction(ButtonClickDefault));
    }
    // 这里是默认回调
    public void ButtonClickDefault()
    {
        HideWindow();
    }
在BasePanel中添加一个函数如下:
    public void doConfirmBox( string id, string text = null, UnityAction okCall = null, UnityAction cancelCall = null, string okText = null, string cancelText = null )
    {
        // 获取模态确认框的实例,如果没有实例化,会自动实例化。
        HXUIConfirmBox wb = GetWindow(id) as HXUIConfirmBox;
        // 设置提示信息
        wb.ConfirmBox(text, okCall, cancelCall, okText, cancelText);
        // 显示确认框
        ShowWindow(wb);
    }
然后,在任意地方,只要这么调用就行了:
    public void onClose()
    {
        // 如果用户点了关闭按钮,弹出是否关闭的提示。
        HXUIManager.hInstance.doConfirmBox("confirmBox", "确定要关闭吗?", new UnityEngine.Events.UnityAction(OnCloseClick));
    }
    public void OnCloseClick()
    {
        // 如果用户点了关闭按钮,并且在弹出的对话框中点击了确定按钮,那么这个函数将会被调用
        HXUIManager.hInstance.HideWindow("confirmBox");
        HideWindow();
    }