Unity3D之搭建简易有效的UI框架

引言:为了小生的三个粉丝,坚持更新。今日分享的是如何搭建自己的UI框架,从而有效管理用户界面。这里主要讲解实现的思想及主要步骤。在此,感谢Siki老师。
开发版本:Unity 2017.1.1f1、VS 2017
适合人群:适合有一定基础的童鞋!

一、什么是UI框架

UI框架用于管理场景中的所有面板,控制面板之间的切换,可以加快开发进度、提高代码质量。

二、实现思路

根据用户界面调用情况,分析有如下四种状态:
  1. 进入状态:界面第一次被动态加载使用的时候
  2. 暂停状态:切换到其他界面的时候
  3. 继续状态:重新回到界面的时候
  4. 退出状态:界面不显示的时候
实现步骤:
  1. 使用JSON保存面板路径,枚举保存面板类型
  2. 根据界面共有的四种状态,创建UI基类BasePanel,场景中的界面继承该基类,并将四种状态写成虚方法,依次分别为OnEnter(),OnPause(),OnResume(),OnExit(),提供给子类重写。
  3. 通过管理类UIManager,解析JSON,管理UI界面的加载和切换。为了方便调用,做成单例模式。分别用两个字典保存从JSON读取的面板信息和已动态加载实例化的面板。通过栈来管理场景中所有面板之间的切换。
缺点也比较明显,因为用栈来存储场景中依次打开的界面,也只能依次从栈顶界面开始关闭。

页面状态流程图如下所示:

UI框架类图如下:

三、代码实现

UI界面基类
BasePanel.cs
public class BasePanel : MonoBehaviour
{
    /// <summary>
    /// 界面显示出来
    /// </summary>
    public virtual void OnEnter() { }

    /// <summary>
    /// 界面暂停(弹出了其他界面)
    /// </summary>
    public virtual void OnPause() { }

    /// <summary>
    /// 界面继续(其他界面移除,回复本来的界面交互)
    /// </summary>
    public virtual void OnResume() { }

    /// <summary>
    /// 界面不显示,退出这个界面,界面被关闭
    /// </summary>
    public virtual void OnExit() { }
}

UIManager.cs

public class UIManager
{
    // 单例模式:定义一个静态的对象,构造方法私有化,内部构造,用于外部访问
    private static UIManager _instance;
    public static UIManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new UIManager();
            }
            return _instance;
        }
    }

    //字典存储所有面板的Prefabs路径
    private Dictionary<UIPanelType, string> panelPathDict = new Dictionary<UIPanelType, string>();
    //保存所有已实例化面板的游戏物体身上的BasePanel组件
    private Dictionary<UIPanelType, BasePanel> panelDict = new Dictionary<UIPanelType, BasePanel>();
    //存储当前场景中的界面
    private Stack<BasePanel> panelStack = new Stack<BasePanel>();

    private Transform canvasTransform;
    private Transform CanvasTransform
    {
        get
        {
            if (canvasTransform == null)
            {
                canvasTransform = GameObject.Find("Canvas").transform;
            }
            return canvasTransform;
        }
    }

    private UIManager()
    {
        ParseUIPanelTypeJson();
    }

    /// <summary>
    /// 解析JSON,获取所有面板的路径信息
    /// </summary>
    private void ParseUIPanelTypeJson()
    {
        TextAsset ta = Resources.Load<TextAsset>("UIPanelType");
        JsonData jsonDataArray = JsonMapper.ToObject(ta.text);
        foreach (JsonData item in jsonDataArray)
        {
            UIPanelType panelType = (UIPanelType)Enum.Parse(typeof(UIPanelType), item["panelType"].ToString());
            string path = item["path"].ToString();
            panelPathDict.Add(panelType, path);
        }
    }

    /// <summary>
    /// 根据面板类型,返回对应的BasePanel组件
    /// </summary>
    /// <param name="panelType">需要返回的面板类型</param>
    /// <returns>返回该面板组件</returns>
    private BasePanel GetPanel(UIPanelType panelType)
    {
        BasePanel basePanel = panelDict.GetValue(panelType);
        //如果panel为空,根据该面板prefab的路径,实例化该面板
        if (basePanel == null)
        {
            string path = panelPathDict.GetValue(panelType);
            GameObject newPanel = GameObject.Instantiate(Resources.Load<GameObject>(path)) as GameObject;
            newPanel.transform.SetParent(CanvasTransform, false); 
            //第一次实例化的面板需要保存在字典中
            panelDict.Add(panelType, newPanel.GetComponent<BasePanel>());
            return newPanel.GetComponent<BasePanel>();
        }
        else
        {
            return basePanel;
        }
    }

    /// <summary>
    /// 设置默认的栈顶元素
    /// </summary>
    /// <param name="panelType">界面类型</param>
    /// <param name="basePanel">组件</param>
    public void SetDefaultPopPanel(UIPanelType panelType,BasePanel basePanel)  
    {
        panelDict.Add(panelType, basePanel);
        panelStack.Push(basePanel);
    }

    /// <summary>
    /// 把该页面显示在场景中
    /// </summary>
    /// <param name="panelType">需要显示界面的类型</param>
    public void PushPanel(UIPanelType panelType)
    {
        //判断一下栈里面是否有页面
        if (panelStack.Count > 0)
        {
            panelStack.Peek().OnPause();//原栈顶界面暂停
        }
        BasePanel panel = GetPanel(panelType);
        panel.OnEnter();//调用进入动作
        panelStack.Push(panel);//页面入栈
    }

    /// <summary>
    /// 关闭栈顶界面显示
    /// </summary>
    public void PopPanel()
    {
        //当前栈内为空,则直接返回
        if (panelStack.Count <= 0) return;
        panelStack.Pop().OnExit();//Pop删除栈顶元素,并关闭栈顶界面的显示,
        if (panelStack.Count <= 0) return;
        panelStack.Peek().OnResume();//获取现在栈顶界面,并调用界面恢复动作
    }
}

四、案例讲解

点击主菜单按钮,实现各个面板之间的切换效果,源文件在末尾。
效果展示如下:

将制作好的面板做成预制体放在Resources文件中,如下所示:

将状态面板类型记录在枚举中,如下所示:
public enum UIPanelType
{
    ItemMessagePanel, 
    CharacterPanel,
    KnapsackPanel,
    MainMenuPanel,
    ShopPanel,
    SkillPanel,
    SystemPanel,
    TaskPanel
}
根据面板和其路径,记录在JSON中,如下所示:
[
    {
        "panelType": "ItemMessagePanel",
        "path": "UIPanel/ItemMessagePanel"
    },
    {
        "panelType": "CharacterPanel",
        "path": "UIPanel/CharacterPanel"
    },

    {
        "panelType": "KnapsackPanel",
        "path": "UIPanel/KnapsackPanel"
    },

    {
        "panelType": "ShopPanel",
        "path": "UIPanel/ShopPanel"
    },

    {
        "panelType": "SkillPanel",
        "path": "UIPanel/SkillPanel"
    },

    {
        "panelType": "SystemPanel",
        "path": "UIPanel/SystemPanel"
    },
    {
        "panelType": "TaskPanel",
        "path": "UIPanel/TaskPanel"
    }
]

各个界面分别创建BasePanel的子类,用于实现界面的四种状态。为了方便实现界面的动画,使用DOTween插件,如果不会的话,可以看我之前的学习博客。 https://blog.csdn.net/qq_35361471/article/details/79353071
为各个界面添加CanvasGroup组件,如下:

Alpha可以方便实现隐藏和显示,取消勾选Blocks Raycasts,可以实现屏蔽UI射线检测
主菜单界面需要实现一个提供给各个界面按钮的方法接口,如下:
public void OnPushPanel(string panelTypeString)
 {
        UIPanelType panelType = (UIPanelType)System.Enum.Parse(typeof(UIPanelType), panelTypeString);
        UIManager.Instance.PushPanel(panelType);
}
给对应按钮添加监听方法的时候,需要注意,这里的字符串要和枚举UIPanelType里的值写的一致。

以CharacterPanel.cs为例,继承BasePanel,并重写四种方法,使用DOTween实现动画,代码如下:
using UnityEngine;
using DG.Tweening;

public class CharacterPanel : BasePanel
{
    private 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;

        Vector3 temp = transform.localPosition;
        temp.x = -800;
        transform.localPosition = temp;
        transform.DOLocalMoveX(0, 0.5f);
    }

    public override void OnPause()
    {
        canvasGroup.blocksRaycasts = false;
    }

    public override void OnResume()
    {
        canvasGroup.blocksRaycasts = true;
    }

    public override void OnExit()
    {
        transform.DOLocalMoveX(-800, .5f).OnComplete(() => canvasGroup.alpha = 0);
    }

    public void OnClosePanel()
    {
        UIManager.Instance.PopPanel();
    }
}


其他界面代码实现类似,可以自己实现不同的切换效果。觉得我分享的学习博客有用的话,记得点赞关注我哦!

源文件百度云链接:链接:https://pan.baidu.com/s/1r-4AKfeO1WxUiENuo1lLLQ 密码:vrs1

 

猜你喜欢

转载自blog.csdn.net/qq_35361471/article/details/79894901
今日推荐