unity游戏框架学习-UI模块

框架概述

1.界面的加载、卸载

2.打开、关闭、隐藏、显示界面,这边隐藏是指界面被遮挡的意思,一般来说,界面被遮住时,应该关闭界面的更新

3.界面栈的管理,主要是用于场景切换时需要回到上一个场景打开的界面栈

4.需要的功能:图片镜像(节省资源)、滑动列表(复用)、模糊背景等

注意点:

1.界面的生成:class的生成、预制体的实例化,类和实例的关联。

业务打开一个界面需要传入界面的标识(枚举、或者字符串),如何通过这个标识找到预制体并实例化go、如何生成指定的界面view,如何绑定view和go

如何销毁一个界面,清除ab缓存、清除引用关系、destory go

2.界面的层级关系:每次打开一个新的UI,都将它堆入栈,关闭时出栈。这个栈是一个特殊的栈,例如它可以实现,某个不在栈顶的UI,可以“TOP”到栈顶。

打开一个界面:1.从已打开界面搜索,避免重复打开界面。2.从缓存界面搜索,避免重复加载。3.隐藏栈顶界面。4.打开新界面

关闭一个界面:1.关闭界面并加入缓存。2.从已打开界面栈顶取出一个界面,显示该界面。3.重复步骤2直到打开一个全屏界面。

退出场景:1.所有界面入栈,当再次回到场景时可以恢复界面栈。2.关闭界面

进入场景:1.如果需要恢复界面栈,从界面栈取出界面并打开显示该界面。2.重复1步骤直到打开一个全屏界面。3.不需要恢复:打开当前场景的界面。

3.界面间的通信:最好不要有界面之间的通信,界面的更新通过数据(逻辑)类发送消息通知给界面。例如使用道具后界面的更新,数据类接收到服务器道具使用成功后,发送道具更新消息BAG_DATA_UPDATE,需要更新的界面监听BAG_DATA_UPDATE消息并刷新界面。

4.界面打开动画控制。统一使用Animator(Animation),且每个界面最多只有一个Animator(Animation),界面获取焦点时调用Animator的Play方法播放动画。

5.界面特效控制:游戏会有很多进入界面播放一次的特效,如果你的界面关闭不是使用SetActive处理的(例如设置layer、移到屏幕外等),那么在你的界面再次打开时特效不会再次被播放。

6.界面内使用的对象怎么获取?(例如要修改界面的某个text)

每个需要在脚本内加载的对象都挂载一个UIExportItem对象,在界面初始化时统一收集这些对象,并存储在一个map里给界面使用

7.界面的模糊效果怎么做?可以参考:https://blog.csdn.net/zhaoguanghui2012/article/details/51462284,原理就是使用一个shader均值周围的像素。

8.弹窗适配,例如实现以下效果:弹窗显示与item的某条边对齐

如上所示,w为target的包围盒宽度,例如绿色框的长度,h为包围盒的高度,例如绿色框的高度。包围盒大小可以使用unity的RectTransformUtility.CalculateRelativeRectTransformBounds接口获取。

那么target的坐标怎么结算呢?

target.x = anchor.x + anchor.w/2 + offect.x + target.w/2

target.y - target.h/2 - offect.y = anchor.y - anchor.h/2 即 target.y = anchor.y - anchor.h/2 + target.h/2 + offect.y

当然对齐方式不一样,计算方法也不一样,例如左上、左下等,而且还需要考虑界面布局是否会超框,超框如何处理等。

具体可以参考:https://blog.csdn.net/SnoopyNa2Co3/article/details/50429604

UI模块分以下几部分:

1.界面类:负责界面的逻辑,提供生命周期方法供业务使用,如OnOpen、OnClose等。负责界面的生成、销毁(以及界面ab包的加载、卸载)。子窗口、子item的管理(生成、缓存、销毁)

2.管理类:提供界面的生命周期管理,如打开、关闭、显示(获得焦点,显示在最上层)、隐藏(失去焦点,可以理解成被其他界面挡住了)一个界面、打开、关闭一堆界面(场景切入、切出时)。缓存界面。维护窗体中间的层级关系。

3.配置类:负责配置界面的预制体路径、类、界面类型等参数。

4.功能类:滑动列表、图片镜像、模糊、弹窗适配、通用的标题、通用的tab等。

5.item类,例如界面的滑动列表的子项。

导出的层级

 详细代码如下:

一、ViewDefine:定义类的配置:这边主要是一些界面定义以及界面的配置,通过配置类名可以用反射实例化界面类,通过配置的路径可以加载并实例化预制体。

using System.Collections.Generic;
using UnityEngine;

public class ViewDefine 
{
    public enum ViewType 
    {
        MAIN = 1,      // 主窗口(全屏)
        POPUP = 2,    // 弹窗
        FIXED = 3,    // 固化窗口
        SCENE = 4,    // 场景UI窗口
        GUIDE = 5,    // 引导UI窗口
    }

    public enum ViewPopModal
    {
        Blur = 1,                       // 带有模糊效果的模态弹窗
        Lucency_ImPenetrable = 2,       // 无模糊,不可穿透
        Lucency_Penetrate = 3,          // 无模糊, 可穿透
    }

    public enum ViewLoadStateDefine
    {
        NONE = 0,
        LOADING = 1,
        LOADED = 2,
    }

    public enum ViewOnLoadDefine
    {
        Cache = 1,
        Destroy = 2,
    }

    public enum ViewAlignmentType
    {
        UpperLeft = 0,
        UpperCenter = 1,
        UpperRight = 2,
        MiddleLeft = 3,
        MiddleCenter = 4,
        MiddleRight = 5,
        LowerLeft = 6,
        LowerCenter = 7,
        LowerRight = 8,
    }

    public enum ViewID
    {
        TOAST,          // 吐司界面
        TOAST_BATTLE,   // 战斗中吐司界面
        NETWAIT,        // 网络等待界面
        NETWORK_TIPS,   // 网络异常提示
    }

    private static Dictionary<int, string> _viewConfig = new Dictionary<int, string>
    {
        { (int)ViewID.TOAST,            "ToastView,Prefab/Common/ToastPanel" },
        { (int)ViewID.TOAST_BATTLE,     "ToastBattleView,Prefab/Common/ToastBattlePanel" },
        { (int)ViewID.NETWAIT,          "NetwaitView,Prefab/Common/NetwaitPanel" }
    };

    public static string GetViewType(ViewID viewID)
    {
        string config = _viewConfig[(int)viewID];
        if (string.IsNullOrEmpty(config))
        {
            Debug.LogErrorFormat("未配置界面路径 : {0}", viewID);
            return null;
        }

        string[] split = config.Split(',');
        return split[0];
    }

    public static string GetViewPath(ViewID viewID)
    {
        string config = _viewConfig[(int)viewID];
        if (string.IsNullOrEmpty(config))
        {
            Debug.LogErrorFormat("未配置界面路径 : {0}", viewID);
            return null;
        }

        string[] split = config.Split(',');
        return split[1];
    }
}

二、UIBase ,ui元素基类,主要提供go的销毁以及导入界面需要引用的对象并保存在_viewObj里,业务可以通过_viewObj["对象名"]访问对象而不用去定义参数。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// item、view的基类,提供预制体的生成、卸载(ab包的维护),提供子节点的生成
/// </summary>
public class UIBase
{
    protected GameObject gameObject;
    protected Transform transform;

    public string Name { get; set; }

    protected Dictionary<string, Object> _viewObj = new Dictionary<string, Object>();//导出的界面对象

    public virtual void Ctor(GameObject obj, Transform parent)
    {
        if (obj != null)
        {
            gameObject = obj;
            transform = obj.transform;
            ExportHierarchy();

            if (parent != null) {
                transform.SetParent(parent);
                transform.localPosition = Vector3.zero;
                transform.localScale = Vector3.zero;
            }
        }
    }

    protected virtual void OnLoad() { }

    public void SetActive(bool isShow)
    {
        gameObject.SetActive(isShow);
    }

    public virtual void Dispose()
    {
        if (gameObject != null) {
            GameObject.Destroy(gameObject);
            transform = null;
            gameObject = null;           
        }
    }

    protected void ExportHierarchy()
    {
        if (gameObject)
        {
            UIHierarchy hierarchy = gameObject.GetComponent<UIHierarchy>();
            if (hierarchy)
            {
                foreach (var item in hierarchy.widgets)
                {
                    _viewObj.Add(item.name, item.item);
                }

                foreach (var item in hierarchy.externals)
                {
                    _viewObj.Add(item.name, item.item);
                }
            }
        }
    }
}

三、UIItemBase ,item类,主要是提供OnItemOpen、OnItemClose方法,方便item在界面打开和关闭时监听(移除)事件

/// <summary>
/// 界面item
/// </summary>
public class UIItemBase:UIBase
{
    protected ViewBase parentView;
    protected bool isItemOpen = false;

    public virtual void OnItemOpen()
    {
        isItemOpen = true;
    }

    public virtual void OnItemClose()
    {
        isItemOpen = false;
    }

    public bool IsItemOpen()
    {
        return isItemOpen;
    }

    public void SetParentView(ViewBase parent)
    {
        parentView = parent;
    }
}

四、PanelBase ,子界面、界面的基类。主要是提供子item的生成、维护。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 所有界面的基类,包括子窗口、所有的界面
/// 这个类主要功能是:提供给子界面生个生成、维护item的接口
/// </summary>
public class PanelBase:UIBase
{
    protected bool isOpen = false;
    protected Dictionary<string, Queue<UIItemBase>> childItemPool = new Dictionary<string, Queue<UIItemBase>>(); //缓存item的池子
    protected List<UIBase> subItems = new List<UIBase>();//维护子对象,包括子窗口、子item

    /// <summary>
    /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面
    /// </summary>
    /// <param name="args"></param> 界面参数
    public virtual void Open(params object[] args)
    {
        
    }

    public virtual void Close()
    {
        
    }

    public bool IsOpen()
    {
        return isOpen;
    }

    /// <summary>
    /// 生成一个子item
    /// </summary>
    /// <param name="className"></param> 子item的类名
    /// <param name="prefabs"></param> 子item的预制体
    /// <param name="parent"></param> 子item的父节点
    /// <returns></returns>
    protected UIItemBase GenerateItem(string className, GameObject prefabs, Transform parent)
    {
        Queue<UIItemBase> pool = childItemPool[className];

        if (pool != null && pool.Count > 0)
        {
            return pool.Dequeue();
        }

        UIItemBase item = InstantiateItem(className, prefabs, parent);
        AddSubItem(item);
        return item;
    }

    protected void GenerateItemList(string className, GameObject prefabs, Transform parent, int count, ref List<UIItemBase> container)
    {
        while (container.Count < count)
        {
            UIItemBase item = GenerateItem(className, prefabs, transform);
            container.Add(item);
        }

        while (container.Count > count)
        {
            UIItemBase item = container[container.Count];
            container.Remove(item);
            RecyleItem(item);
        }
    }

    protected UIItemBase InstantiateItem(string className, GameObject prefabs, Transform parent)
    {
        GameObject obj = GameObject.Instantiate(prefabs, parent);
        UIItemBase item = (UIItemBase)UIModule.Instance.CreateUIClass(className);
        item.Ctor(obj, parent);
        item.Name = className;
        return item;
    }

    /// <summary>
    /// 回收子item
    /// </summary>
    /// <param name="item"></param>
    protected void RecyleItem(UIItemBase item)
    {
        RemoveSubItem(item);

        Queue<UIItemBase> cachePool = childItemPool[item.Name];
        if (cachePool == null)
        {
            cachePool = new Queue<UIItemBase>();
        }

        cachePool.Enqueue(item);
        item.SetActive(false);
        if (item.IsItemOpen())
        {
            item.OnItemClose();
        }
    }

    protected void RecyleItemList(int count, ref List<UIItemBase> container)
    {
        while (container.Count > count)
        {
            UIItemBase item = container[container.Count];
            RecyleItem(item);
            container.Remove(item);
        }
    }

    protected void ClearCache()
    {
        foreach (var pool in childItemPool.Values)
        {
            foreach (var item in pool)
            {
                item.Dispose();
            }
            pool.Clear();
        }

        childItemPool.Clear();
    }

    /// <summary>
    /// 添加子对象,包括item、childview
    /// </summary>
    /// <param name="item"></param>
    protected void AddSubItem(UIBase item)
    {
        if (subItems.Contains(item))
        {
            Debug.Log("item已存在");
            return;
        }

        subItems.Add(item);
    }

    protected void RemoveSubItem(UIBase item)
    {
        if (subItems.Contains(item))
        {
            subItems.Remove(item);
        }
    }

    protected void RemoveAllSubItem()
    {
        foreach (var item in subItems)
        {
            item.Dispose();
        }

        subItems.Clear();
    }

    protected void OnOpenSubItem()
    {
        foreach (var item in subItems)
        {
            if (item is UIItemBase)
            {
                (item as UIItemBase).OnItemOpen();
            }
        }
    }

    protected void OnCloseSubItem()
    {
        foreach (var item in subItems)
        {
            if (item is UIItemBase)
            {
                (item as UIItemBase).OnItemClose();
            }
        }
    }

    public override void Dispose()
    {
        base.Dispose();

        RemoveAllSubItem();
        ClearCache();
    }
}

五、ChildViewBase :重写了Open、Close方法

public class ChildViewBase:PanelBase
{
    /// <summary>
    /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面
    /// </summary>
    /// <param name="args"></param> 界面参数
    public override void Open(params object[] args)
    {
        if (!isOpen)
        {
            SetActive(true);
            OnOpenSubItem();
            isOpen = true;
        }
    }

    public override void Close()
    {
        if (isOpen)
        {
            OnCloseSubItem();
            SetActive(false);
            isOpen = false;
        }
    }
}

六、ViewBase :所有界面基类,主要是提供了界面的加载、卸载(注意ab的加、卸载),子界面的添加、维护。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 界面基类,可以生成ChildView
/// </summary>
public class ViewBase: PanelBase
{
    protected object[] openParam;
    protected ViewDefine.ViewType viewType;
    protected ViewDefine.ViewLoadStateDefine loadState = ViewDefine.ViewLoadStateDefine.NONE;
    protected bool isHide = false;
    protected float closeTime = 0;//用于回收计时

    public ViewDefine.ViewID ViewID { get; set; }

    /// <summary>
    /// 打开界面,一次OnOpen对应一次OnClose,子类实现
    /// </summary>
    /// <param name="args"></param>
    protected virtual void OnOpen(params object[] args){ }

    /// <summary>
    /// 用于刷新界面,每次调用OpenView都会调用,避免业务重复打开界面
    /// </summary>
    /// <param name="args"></param>
    protected virtual void OnRefreshView(params object[] args) { }

    /// <summary>
    /// 获得焦点。1.打开界面。2.上一层界面被关闭,重新获得焦点
    /// </summary>
    protected virtual void OnEnabled() { }

    public virtual void Update(float dt) { }

    /// <summary>
    /// 失去焦点。1.关闭界面。2.有新的界面打开。
    /// </summary>
    protected virtual void OnDisable() { }

    protected virtual void OnClose() { }


    /// <summary>
    /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面
    /// </summary>
    /// <param name="args"></param> 界面参数
    public override void Open(params object[] args)
    {
        openParam = args;
        if (loadState == ViewDefine.ViewLoadStateDefine.LOADED)
        {
            DoRealOpen();
        }
        else
        {
            Load();
        }
    }

    /// <summary>
    /// 加载界面,包括界面的ab包,ab的依赖包,最终返回一个Prefab用于实例化界面
    /// </summary>
    private void Load()
    {
        if (loadState != ViewDefine.ViewLoadStateDefine.NONE) return;

        loadState = ViewDefine.ViewLoadStateDefine.LOADING;
        ResManager.Instance.LoadPrefab(ViewDefine.GetViewPath(ViewID), "", LoadFinish);      
    }

    private void LoadFinish(object prefab)
    {
        loadState = ViewDefine.ViewLoadStateDefine.LOADED;
        gameObject = GameObject.Instantiate((GameObject)prefab, UIModule.Instance.GetViewRoot(viewType));
        transform = gameObject.transform;
        ExportHierarchy();

        OnLoad();
        DoRealOpen();
    }

    private void DoRealOpen()
    {
        if (!isOpen)
        {
            SetActiveEx(true);
            OnOpen(openParam);
            OnOpenSubItem();
        }

        OnRefreshView(openParam);
        ShowView();
    }

    /// <summary>
    /// 显示、隐藏界面,这边使用的方式是将界面移到屏幕外。
    /// 另外几种做法是:1.SetActive直接隐藏go。2.设置Scale为0。3.设置layer out
    /// </summary>
    /// <param name="isActive"></param> 是否显示
    public void SetActiveEx(bool isActive)
    {
        if (transform) {
            if (isActive)
            {
                transform.localPosition = Vector3.zero;
            }
            else
            {
                transform.localPosition = new Vector3(10000, 10000, 0);
            }
        }
    }

    public virtual void ShowView()
    {
        SetActiveEx(true);
        transform.SetAsFirstSibling();

        if (!isHide)
        {
            isHide = true;
            OnEnabled();
        }       
    }

    /// <summary>
    /// 界面失去焦点,如果是打开弹窗,不隐藏该界面。
    /// </summary>
    /// <param name="keepShow"></param>
    public virtual void HideView(bool keepShow = false)
    {
        SetActiveEx(keepShow);

        if (isHide)
        {
            isHide = false;
            OnDisable();
        }
    }

    public override void Close()
    {
        if (isOpen)
        {
            UIModule.Instance.CloseView(ViewID);
        }
    }

    public virtual void CloseView()
    {
        HideView();
        if (isOpen)
        {
            OnClose();
            OnCloseSubItem();
            CloseSubPanel();
            closeTime = Time.realtimeSinceStartup;
        }
    }

    public float GetCloseTime()
    {
        return closeTime;
    }

    public bool IsPopView()
    {
        return viewType == ViewDefine.ViewType.POPUP;
    }

    public bool IsMainView()
    {
        return viewType == ViewDefine.ViewType.MAIN;
    }

    public bool IsFixedView()
    {
        return viewType == ViewDefine.ViewType.FIXED;
    }

    public bool IsShow()
    {
        return !isHide;
    }

    public bool IsOnLoadDestroy()
    {
        return loadState == ViewDefine.ViewLoadStateDefine.LOADING;
    }

    protected List<ChildViewBase> childView = new List<ChildViewBase>();
    protected ChildViewBase AddChildPanel(string className, GameObject obj, Transform parent)
    {
        ChildViewBase view = (ChildViewBase)UIModule.Instance.CreateUIClass(className);
        view.Ctor(obj, parent);
        childView.Add(view);
        AddSubItem(view);
        return view;
    }

    protected void CloseSubPanel()
    {
        foreach (var item in childView)
        {
            if (item.IsOpen())
            {
                item.Close();
            }
        }
    }
}

七、UIHierarchy:保存了业务导出的引用对象。ExportPanelHierarchy寻找UIExportItem 元素并保存到UIHierarchy

public class UIHierarchy : MonoBehaviour
{
    [System.Serializable]
    public class ItemInfo
    {
        public string name;
        public Object item;

        public ItemInfo() { }
        public ItemInfo(string _name, Object _item) { name = _name; item = _item; }
    }

    // 控件
    public List<ItemInfo> widgets;
    public void SetWidgets(List<ItemInfo> data)
    {
        if (data.Count == 0) return;
        if (widgets == null)
        {
            widgets = new List<ItemInfo>();
        }

        widgets.Clear();
        widgets.AddRange(data);
    }

    // 外部引用
    public List<ItemInfo> externals;
}

using UnityEngine;

[DisallowMultipleComponent]
public class UIExportItem : MonoBehaviour
{
    public string FieldName;
}
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using System.Collections.Generic;

public class ExportPanelHierarchy
{
    /// <summary>
    /// 导出组件优先级
    /// </summary>
    private static System.Type[] ms_componentTypes = {
        typeof(Button),
        typeof(InputField),
        typeof(ScrollRect),
        typeof(Dropdown),
        typeof(Image),
        typeof(RawImage),
        typeof(Scrollbar),
        typeof(Slider),
        typeof(Text),
        typeof(Toggle),
        typeof(GridLayoutGroup),
        typeof(HorizontalOrVerticalLayoutGroup),
        typeof(LayoutElement),        
        typeof(CanvasGroup),
        typeof(ToggleGroup),
        typeof(TextMesh),
        typeof(Animation),
        typeof(Camera),
        typeof(SpriteRenderer),
    };

    static Object FindComponent(GameObject go)
    {
        Object component = null;
        for (int i = 0; i < ms_componentTypes.Length; ++i)
        {
            component = go.GetComponent(ms_componentTypes[i]);
            if (component != null)
            {
                break;
            }
        }
        return component;
    }

    //生成嵌套UI层级
    public static void ExportNested(Object obj)
    {
        GameObject root = obj as GameObject;
        if (root == null) return;

        UIHierarchy hierarchy = root.GetComponent<UIHierarchy> ();
        if(hierarchy==null)
        {
            hierarchy = root.AddComponent<UIHierarchy>();
        }

        //生成根节点层级
        List<UIHierarchy.ItemInfo> fields = new List<UIHierarchy.ItemInfo>();
        GetChildComponentUtilHierarchy (root.transform, fields);
        hierarchy.SetWidgets(fields);

        //生成子panel层级
        UIHierarchy[] childHierarchys = root.GetComponentsInChildren<UIHierarchy>(true);
        for(int i=1; i<childHierarchys.Length; i++)
        {
            UIHierarchy childHrcy = childHierarchys [i];

            List<UIHierarchy.ItemInfo> childUIItem = new List<UIHierarchy.ItemInfo>();
            GetChildComponentUtilHierarchy (childHrcy.transform, childUIItem);
            childHrcy.SetWidgets(childUIItem);
        }

        EditorUtility.SetDirty(root);
        AssetDatabase.SaveAssets();
    }
    
    //导出传入节点的层级,直到某个子节点挂有UIHierarchy组件
    private static void GetChildComponentUtilHierarchy(Transform transRoot, List<UIHierarchy.ItemInfo> fields)
    {
        for(int i=0; i<transRoot.childCount; i++)
        {
            Transform trans = transRoot.GetChild (i);

            UIHierarchy hrchy = trans.GetComponent<UIHierarchy> ();
            if(hrchy!=null)
            {
                fields.Add (new UIHierarchy.ItemInfo(hrchy.name, hrchy));
                continue;
            }

            UIExportItem uiItem = trans.GetComponent<UIExportItem>();
            if (uiItem != null)
            {
                Object fieldItem = FindComponent(uiItem.gameObject);
                if (fieldItem == null)
                {
                    fieldItem = uiItem.transform;
                }
                fields.Add(new UIHierarchy.ItemInfo(uiItem.name, fieldItem));
            }

            GetChildComponentUtilHierarchy (trans, fields);
        }
    }
}

八、UIModule:核心管理类,提供给全局唯一的打开界面方法,维护层级栈。维护界面缓存。

using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

public class UIModule
{
    #region Instance
    private static UIModule m_Instance;
    public static UIModule Instance
    {
        get
        {
            return m_Instance ?? (m_Instance = new UIModule());
        }
    }
    #endregion

    private const float CACHE_TIME = 30;//界面缓存时间
    private int curSceneID = 0;//当前打开的场景id,每个场景都有自己的层级栈
    private int curViewID = 0;//当前打开的界面id
    private int lastViewID = 0;//上一个界面id

    private List<int> openViewList;//当前打开的界面列表,按顺序
    private Dictionary<int, ViewBase> cacheView;//缓存区,等待销毁,从缓存区取要移除
    private Dictionary<int, List<int>> naviStack;//场景的层级栈,key为场景id,用于维护场景
    private Dictionary<int, ViewBase> viewPool;//保存所有ViewBase的引用,包括缓存区的,界面销毁时需要移除。
    private Dictionary<ViewDefine.ViewType, Transform> viewRoot;//界面实例化出来的父节点,不同界面的父节点不一样。
    private Transform uiRoot;//ui根节点
    private float lastCheckCacheTime;//上次检查缓存时间,一秒检查一次

    public void Init()
    {
        openViewList = new List<int>();
        cacheView = new Dictionary<int, ViewBase>();
        naviStack = new Dictionary<int, List<int>>();
        viewPool = new Dictionary<int, ViewBase>();

        uiRoot = GameObject.Find("UIRoot").transform;
        viewRoot = new Dictionary<ViewDefine.ViewType, Transform>
        {
            { ViewDefine.ViewType.MAIN, uiRoot.Find("main") },
            { ViewDefine.ViewType.POPUP, uiRoot.Find("main") },
            { ViewDefine.ViewType.FIXED, uiRoot.Find("fixed") },
            { ViewDefine.ViewType.GUIDE, uiRoot.Find("guide") }
        };

        lastCheckCacheTime = Time.realtimeSinceStartup;
    }

    public Transform GetViewRoot(ViewDefine.ViewType viewType)
    {
        if (viewType == ViewDefine.ViewType.SCENE)
        {
            return null;
        }
        else
        {
            return viewRoot[viewType];
        }
    }

    public void Update(float dt)
    {
        int openCount = openViewList.Count;
        ViewBase view;
        for (int i = 0; i < openCount; i++)
        {
            int viewId = openViewList[i];
            if (viewPool.TryGetValue(viewId, out view))
            {
                view.Update(dt);
            }
        }

        //清楚缓存时间到了的界面
        float curTime = Time.realtimeSinceStartup;
        if (curTime - lastCheckCacheTime > 1)
        {
            foreach (var item in cacheView)
            {
                if (curTime - item.Value.GetCloseTime() > CACHE_TIME)
                {
                    item.Value.Dispose();
                    cacheView.Remove(item.Key);
                    viewPool.Remove(item.Key);
                }
            }
        }

        lastCheckCacheTime = curTime;
    }

    /// <summary>
    /// 外部调用 打开一个窗口的唯一方式
    /// 如果上一个界面lastView存在,需要把lastView入栈
    /// </summary>
    /// <param name="viewID"></param>
    /// <param name="param"></param> 界面打开参数,给业务使用的
    /// <returns></returns>
    public ViewBase OpenView(ViewDefine.ViewID viewID, params object[] param)
    {
        int viewKey = (int)viewID;
        ViewBase view = FindOpenView(viewKey);

        if (view == null)
        {
            view = CreateView(viewID);

            if (view == null)
            {
                Debug.LogErrorFormat("OpenView CreateView Fail..viewID:", viewID);
                return null;
            }
            view.ViewID = viewID;
            AddOpenView(viewKey);
        }

        if (curViewID == viewKey)
        {
            view.Open(param);
            return view;
        }

        if (view.IsPopView() || view.IsMainView())
        {
            lastViewID = curViewID;
            curViewID = viewKey;

            if (lastViewID > 0)
            {
                OnBackstage(lastViewID);
            }
        }

        view.Open(param);
        return view;
    }

    private ViewBase FindOpenView(int viewKey)
    {
        ViewBase view = null;
        if (openViewList.Contains(viewKey))
        {            
            viewPool.TryGetValue(viewKey, out view);
        }

        return view;
    }

    private void AddOpenView(int viewKey)
    {
        if (openViewList.Contains(viewKey))
        {
            Debug.LogFormat("界面已打开:{0}", viewKey);
            return;
        }
        openViewList.Add(viewKey);
    }

    private void RemoveOpenView(int viewKey)
    {
        if (openViewList.Contains(viewKey))
        {
            openViewList.Remove(viewKey);
        }
    }

    /// <summary>
    /// 创建新的view,先从缓存里面找
    /// </summary>
    /// <param name="viewID"></param>
    /// <returns></returns>
    private ViewBase CreateView(ViewDefine.ViewID viewID)
    {
        int viewKey = (int)viewID;
        ViewBase view = GetViewFromCache(viewKey);

        if (view == null)
        {
            string viewName = ViewDefine.GetViewType(viewID);
            //加载程序集,创建程序集里面的 命名空间.类型名 实例
            object ect = CreateUIClass(viewName);

            view = (ViewBase)ect;//类型转换并返回
            viewPool.Add((int)viewID, view);
        }
        
        return view;
    }

    public object CreateUIClass(string calssName)
    {
        return Assembly.GetExecutingAssembly().CreateInstance(calssName);
    }

    private ViewBase GetViewFromCache(int viewKey)
    {
        ViewBase view = null;
        if (cacheView.TryGetValue(viewKey, out view)) {
            cacheView.Remove(viewKey);
        }
        
        return view;
    }

    private void AddViewToCache(int viewKey, ViewBase view)
    {
        cacheView[viewKey] = view;
    }

    private int GetTopViewOfStack()
    {
        List<int> stack = naviStack[curSceneID];
        if (stack == null || stack.Count == 0)
        {
            return 0;
        }

        return stack[stack.Count];
    }

    private ViewBase PopViewFormStack()
    {
        List<int> stack = naviStack[curSceneID];
        if (stack == null || stack.Count == 0)
        {
            return null;
        }

        int index = stack.Count;
        int viewId = stack[index];
        ViewBase view = GetViewByKey(viewId);
        stack.RemoveAt(index);

        if (view.IsPopView())
        {
            for (int i = index-1; i > 0; i--)
            {
                ViewBase temp = GetViewByKey(stack[i]);
                temp.SetActiveEx(true);

                if (temp.IsMainView()) break;
            }
        }

        curViewID = viewId;
        view.ShowView();
        AddOpenView(viewId);
        return view;
    }

    /// <summary>
    /// 向栈里添加元素
    /// </summary>
    /// <param name="viewKey"></param>
    /// <param name="isForce"></param>true时,栈里存在会先移除在加入,否则栈里存在就不处理了
    private void AddViewToStack(int viewKey, bool isForce = true)
    {
        List<int> stack = naviStack[curSceneID];
        if (stack == null)
        {
            stack = new List<int>();
        }

        if (stack.Contains(viewKey))
        {
            if (isForce)
            {
                stack.Remove(viewKey);
            }
            else
            {
                return;
            }            
        }

        stack.Add(viewKey);
    }

    private void RemoveFromStack(int viewKey)
    {
        List<int> stack = naviStack[curSceneID];
        if (stack == null || stack.Count == 0)
        {
            return;
        }

        if (stack.Contains(viewKey))
        {
            stack.Remove(viewKey);
        }
    }

    /// <summary>
    /// 进入后台
    /// </summary>
    /// <param name="viewKey"></param>
    private void OnBackstage(int viewKey)
    {
        AddViewToStack(viewKey);
        ViewBase lastView = GetViewByKey(lastViewID);

        if (lastView.IsPopView())
        {
            lastView.HideView(true);
        }
        else
        {
            lastView.HideView(false);
        }        
    }

    /// <summary>
    /// 关闭界面,如果是当前打开界面,需要从栈顶弹出新的界面
    /// </summary>
    /// <param name="viewKey"></param>
    private void InsertClose(int viewKey)
    {
        RemoveView(viewKey);

        if (viewKey == curViewID)
        {
            curViewID = 0;
            PopViewFormStack();
        }
        else
        {
            RemoveFromStack(viewKey);
        }
    }

    /// <summary>
    /// 移除界面,从打开列表移除,添加到缓存
    /// </summary>
    /// <param name="viewKey"></param>
    private void RemoveView(int viewKey)
    {
        RemoveOpenView(viewKey);
        RemoveFromStack(viewKey);
        ViewBase view = GetViewByKey(viewKey);
        view.CloseView();
        AddViewToCache(viewKey, view);
    }

    private ViewBase GetViewByKey(int viewKey)
    {
        return viewPool[viewKey];
    }

    public void CloseView(ViewDefine.ViewID viewID)
    {
        int viewKey = (int)viewID;
        if (openViewList.Contains(viewKey))
        {
            InsertClose(viewKey);
        }
    }

    public void CloseCurView()
    {
        if (curViewID > 0)
        {
            CloseView((ViewDefine.ViewID)curViewID);
        }
    }

    /// <summary>
    /// 进入新的场景
    /// </summary>
    /// <param name="sceneID"></param> 场景id
    /// <param name="isNative"></param> 是否需要打开ui栈,isBack=true时,从当前场景的栈顶弹出界面
    public void EnterScene(int sceneID, bool isBack)
    {
        curSceneID = sceneID;
        if (naviStack[sceneID] == null)
        {
            naviStack[sceneID] = new List<int>();
        }

        if (isBack)
        {
            PopViewFormStack();
        }
    }

    /// <summary>
    /// 退出当前场景
    /// </summary>
    /// <param name="pushToStack"></param> 是否压栈,用于场景返回时恢复ui层级
    public void ExitScene(bool pushToStack)
    {
        int count = openViewList.Count;
        ViewBase temp = null;
        int viewKey = 0;

        for (int i = count; i > 0; i--)
        {
            viewKey = openViewList[i];
            temp = GetViewByKey(viewKey);

            if (temp != null && (temp.IsMainView() || temp.IsPopView()))
            {
                if (pushToStack)
                {
                    RemoveOpenView(viewKey);
                    AddViewToStack(viewKey, false);
                }
                else
                {
                    RemoveView(viewKey);
                }
            }
        }

        List<int> stack = naviStack[curSceneID];
        if (stack != null)
        {
            for (int i = 0; i < stack.Count; i++)
            {
                ViewBase view = GetViewByKey(stack[i]);
                view.HideView();
            }
        }

        curViewID = 0;
        lastViewID = 0;
    }
}
原文地址:https://www.cnblogs.com/wang-jin-fu/p/11252256.html