整体思路:

unity项目架构 unity框架的搭建_unity

首先我们先要有一些面板的预制体

unity项目架构 unity框架的搭建_ui_02

然后在Resources文件夹下,我们创建一个文件夹,命名为UIPanel,将这些预制体放在UIPanel下(没有Resources文件夹就在Assets文件夹下创建一个)

然后我们根据这些预制体及其路径编写一个json文件,首个类型必须是对象(由于JsonUtility神奇的解析功能,可以不使用JsonUtility,这里主要老师使用的是JsonUtility,我就跟着用了)

{
  "infoList":
  [
    {
      "panelTypeString": "ItemMessage",
      "path": "UIPanel/ItemMessagePanel"
    },
    {
      "panelTypeString": "Knapsack",
      "path": "UIPanel/KnapsackPanel"
    },
    {
      "panelTypeString": "MainMenu",
      "path": "UIPanel/MainMenuPanel"
    },
    {
      "panelTypeString": "Shop",
      "path": "UIPanel/ShopPanel"
    },
    {
      "panelTypeString": "Skill",
      "path": "UIPanel/SkillPanel"
    },
    {
      "panelTypeString": "System",
      "path": "UIPanel/SystemPanel"
    },
    {
      "panelTypeString": "Task",
      "path": "UIPanel/TaskPanel"
    }
  ]
}

我们根据这个json文档,创建一个枚举用于保存对应的面板类型

public enum UIPanelType
{
    ItemMessage,
    Knapsack,
    MainMenu,
    Shop,
    Skill,
    System,
    Task
}

然后创建整个UI框架的核心:UIManager(不用继承MonoBehaviour)

首先声明一个字典用于保存面板对应的路径:

private Dictionary<UIPanelType, string> PanelPathDict;//用于存放面板类型对应的路径

由于必须使UIManager唯一,所以我们将其做成单例

private static UIManager _instance;//单例返回的私有静态对象

    public static UIManager Instance//单例
    {
        get
        {
            if (_instance == null)
            {
                _instance = new UIManager();//将实例化的对象赋给私有静态对象
            }
            return _instance;//返回私有静态对象
        }
    }

我们写一个类用于保存Json数组中的每个对象(由于直接解析枚举类型会有问题,在这里通过一个接口将反序列化后的枚举接收通过字符串解析的内容)

[Serializable]
public class PanelInfo:ISerializationCallbackReceiver
{
    //反序列化  把文本转换成对象
    [NonSerialized]
    public UIPanelType panelType;
    public string panelTypeString;//json中解析的对象名一定得和变量名一致
    public string path;

    //在Unity反序列化对象后,实现此方法以接收回调
    public void OnAfterDeserialize()
    {
        UIPanelType type = (UIPanelType)Enum.Parse(typeof(UIPanelType), panelTypeString);
        panelType = type;
    }

    public void OnBeforeSerialize()
    {
        
    }
}

由于Unity内置的JsonUtility只能读取对象类型的Json文件,所以在此之前先要创建一个类用作Json的第一个对象也就是infoList,infoList内容为一个数组,于是我们在这个类下声明一个列表来存储

[Serializable]
    public class JsonObject//用于获取json文件的对象
    {
        public List<PanelInfo> infoList;//用于保存json中的数组
    }

然后写一个解析Json的方法:

private void ParseUIPanelTypeJson()//解析Json
    {
        PanelPathDict = new Dictionary<UIPanelType, string>();//初始化面板路径字典
        TextAsset ta = Resources.Load<TextAsset>("UIPanelType");//根据Resources下的文件名找到对应的文件
        JsonObject panelInfoList = JsonUtility.FromJson<JsonObject>(ta.text);//解析Json文件
        foreach (PanelInfo info in panelInfoList.infoList)//panelInfoList对应的是json中的第一个对象 infoList对应json文件中的第一个对象的内容也就是那个列表 info则对应列表中的每个对象
        {
            PanelPathDict.Add(info.panelType, info.path);//将解析完的info对象的面板类型和地址添加到字典中
        }
    }

我们在UIManager的构造方法里调用这个解析方法:

private UIManager()//构造方法
    {        
        ParseUIPanelTypeJson();//解析json并将其内容全部添加到地址字典中
    }

然后我们要声明一个面板基类,通过这个基类来控制其下的子面板

public class BasePanel : MonoBehaviour
{
    public virtual void OnEnter()
    {

    }
    public virtual void OnPause()
    {

    }
    public virtual void OnResume()
    {

    }
    public virtual void OnExit()
    {

    }
}

然后写几个子面板脚本继承自它,并将其添加到对应的面板预制体中(功能一会再写)

接下来我们要写一个字典用于存储每个面板对应的面板脚本:

private Dictionary<UIPanelType, BasePanel> PanelDict;//用于存放面板类型对应的面板脚本

然后写一个方法用于通过传入的面板类型获得相应的面板脚本并实例化

不过在此之前应该声明一个Transform变量,为了将面板的父物体位置设置为画布

private Transform canvasTransform;
    private Transform CanvasTransform//用于将画布设为面板的父物体
    {
        get
        {
            if (canvasTransform == null)//如果还没获得游戏物体
            {
                canvasTransform = GameObject.Find("Canvas").transform;//获取画布
                return canvasTransform;
            }
            else
            {
                return canvasTransform;
            }
        }
    }

为了方便再创建一个字典的扩展方法

public static class DictionaryExtension
{
    //根据key来尝试在字典中查找value,返回一个Tvalue的值
    public static Tvalue TryGet<Tkey,Tvalue>(this Dictionary<Tkey,Tvalue> dict,Tkey key)
    {
        Tvalue value;
        dict.TryGetValue(key, out value);
        return value;
    }
}

获取面板对应脚本并实例化方法:

private BasePanel GetPanel(UIPanelType panelType)//通过传入的面板类型获得相应的面板脚本并实例化
    {
        if (PanelDict == null)//如果还没实例化存面板类型对应的游戏物体的字典
        {
            PanelDict = new Dictionary<UIPanelType, BasePanel>();//实例化该字典
        }
        BasePanel panel;//声明一个基类面板变量
        panel = PanelDict.TryGet(panelType);//尝试用面板类型取得字典中的基类面板的对象,返回一个bool值用于判断是否取得成功,并将尝试取得的基类面板的对象传递给变量
        //PanelDict.TryGetValue(panelType, out panel);
        if (panel == null) //如果字典中还没添加这个面板类型
        {
            string path;//声明一个路径变量
            //PanelPathDict.TryGetValue(panelType, out path);
            path = PanelPathDict.TryGet(panelType); //用面板类型取得路径字典的路径,将该面板类型所对应的路径传递给路径变量
            GameObject panelInstance = GameObject.Instantiate(Resources.Load(path)) as GameObject;//加载这个路径对应的游戏物体 并将其实例化
            panelInstance.transform.SetParent(CanvasTransform,false);//将这个面板的游戏物体的父对象设置为画布 false为不保留其在世界位置的坐标
            PanelDict.Add(panelType, panelInstance.GetComponent<BasePanel>());//在游戏物体字典中添加这个面板及其对应的面板脚本
            return panelInstance.GetComponent<BasePanel>();//返回这个面板对应的面板脚本
        }
        else//如果字典中有这个面板类型
        {
            return panel;//返回当前面板类型对应的面板脚本
        }
    }

我们用一个栈来管理面板的层级(比如最后显示的面板则可以交互,禁止与其下的其他面板交互,在面板关闭时又要将其隐藏,并将其下一位面板重新恢复交互等)

private Stack<BasePanel> PanelStack;//面板栈,用于管理面板的层级

并编写它的Push方法

public void PanelPush(UIPanelType panelType)//面板入栈
    {
        if (PanelStack == null)
            PanelStack = new Stack<BasePanel>();
        if (PanelStack.Count > 0)//如果当前栈不为空
        {
            BasePanel topPanel = PanelStack.Peek();//获取栈顶的面板
            topPanel.OnPause();//使栈顶面板无法交互
        }
        BasePanel panel = GetPanel(panelType);//获取要显示的面板
        panel.OnEnter();//面板入栈时使其显示
        PanelStack.Push(panel);//将这个面板放入栈顶
    }

在GameRoot下调用

public class GameRoot : MonoBehaviour
{
    void Start()
    {
        UIManager.Instance.PanelPush(UIPanelType.MainMenu);//调用这个方法时首先会调用它单例的构造方法,然后调用这个方法
    }

}

这样在画布中就会生成MainMenu面板

unity项目架构 unity框架的搭建_unity_03

然后编写栈的Pop方法

public void PanelPop()//面板出栈
    {
        if (PanelStack.Count <= 0) return;//如果栈为空,直接返回
        BasePanel topPanel = PanelStack.Pop();//如果不为空,将栈顶面板出栈
        topPanel.OnExit();//面板出栈时使其隐藏
        if (PanelStack.Count <= 0) return;//如果出栈之后栈为空,直接返回
        topPanel = PanelStack.Peek();//如果还不为空,获取新的栈顶面板
        topPanel.OnResume();//栈顶面板恢复交互状态
    }

写好之后大体的框架就基本完成了,后续只要开发每个面板脚本相应的功能就行,为了以后自己还能看懂,还有一些小细节我也一并写出来

Canvs Group组件

设置其blocksRaycasts为false,使其无法与鼠标交互

设置其Alpha为0,使其隐藏,1为显示

写一下MainMenu的脚本

public class MainMenuPanel : BasePanel
{
    CanvasGroup canvasGroup;
    private void Start()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }

    public override void OnPause()//面板暂停
    {
        canvasGroup.blocksRaycasts = false;
    }
    public override void OnResume()//面板恢复可交互状态
    {
        canvasGroup.blocksRaycasts = true;
    }
    public void OnPushPanel(string panelTypeString)//根据点击到的按钮显示对应的面板
    {
        UIPanelType panelType = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
        UIManager.Instance.PanelPush(panelType);
    }
}

因为MainMenu下有多个其他面板的按钮,所以得写一个点击按钮的方法,也就是OnPushPanel

unity项目架构 unity框架的搭建_unity_04

其他面板的基本功能

public class TestPanel : BasePanel//不同面板不同功能
{
    CanvasGroup canvasGroup;
    void Start()
    {
        if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();
    }
    public override void OnEnter()//面板入栈
    {
        if (canvasGroup == null) canvasGroup = GetComponent<CanvasGroup>();
        canvasGroup.alpha = 1;
        canvasGroup.blocksRaycasts = true;
    }
    public override void OnPause()
    {
        canvasGroup.blocksRaycasts = false;
    }
    public override void OnExit()//面板出栈
    {
        canvasGroup.alpha = 0;
        canvasGroup.blocksRaycasts = false;
    }
    public override void OnResume()
    {
        canvasGroup.blocksRaycasts = true;
    }
    public void OnClose()//面板点击关闭按钮时使其出栈
    {
        UIManager.Instance.PanelPop();
    }
}

完成之后的效果:

unity项目架构 unity框架的搭建_游戏_05

谢谢朋友们!