看了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();
}