• Unity3D通用UI框架




    目标:编写一个简单通用UI框架用于管理页面和完成导航跳转最终的实现效果请拉到最下方查看

    框架具体实现的功能和需求
    • 加载,显示,隐藏,关闭页面,根据标示获得相应界面实例
    • 提供界面显示隐藏动画接口
    • 单独界面层级,Collider,背景管理
    • 根据存储的导航信息完成界面导航
    • 界面通用对话框管理(多类型Message Box)
    • 便于进行需求和功能扩展(比如,在跳出页面之前添加逻辑处理等)
    编写UI框架意义
    • 打开,关闭,层级,页面跳转等管理问题集中化,将外部切换等逻辑交给UIManager处理
    • 功能逻辑分散化,每个页面维护自身逻辑,依托于框架便于多人协同开发,不用关心跳转和显示关闭细节
    • 通用性框架能够做到简单的代码复用和"项目经验"沉淀
    步入正题,如何实现
    • 窗口类设计:基本窗口对象,维护自身逻辑维护
    • 窗口管理类:控制被管理窗口的打开和关闭等逻辑(具体设计请看下文)
    • 动画接口:提供打开和关闭动画接口,提供动画完成回调函数等
    • 层级,Collider背景管理
    窗口基类设计



    框架中设计的窗口类型和框架所需定义如下
    [C#] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public enum UIWindowType
    {
        Normal,    // 可推出界面(UIMainMenu,UIRank等)
        Fixed,     // 固定窗口(UITopBar等)
        PopUp,     // 模式窗口
    }
     
    public enum UIWindowShowMode
    {
        DoNothing,
        HideOther,     // 闭其他界面
        NeedBack,      // 点击返回按钮关闭当前,不关闭其他界面(需要调整好层级关系)
        NoNeedBack,    // 关闭TopBar,关闭其他界面,不加入backSequence队列
    }
     
    public enum UIWindowColliderMode
    {
        None,      // 显示该界面不包含碰撞背景
        Normal,    // 碰撞透明背景
        WithBg,    // 碰撞非透明背景
    }


    [C#] 纯文本查看 复制代码
    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    using UnityEngine;
    using System.Collections;
    using System;
     
    namespace CoolGame
    {
        /// <summary>
        /// 窗口基类
        /// </summary>
        public class UIBaseWindow : MonoBehaviour
        {
            protected UIPanel originPanel;
     
            // 如果需要可以添加一个BoxCollider屏蔽事件
            private bool isLock = false;
            protected bool isShown = false;
     
            // 当前界面ID
            protected WindowID windowID = WindowID.WindowID_Invaild;
     
            // 指向上一级界面ID(BackSequence无内容,返回上一级)
            protected WindowID preWindowID = WindowID.WindowID_Invaild;
            public WindowData windowData = new WindowData();
     
            // Return处理逻辑
            private event BoolDelegate returnPreLogic = null;
     
            protected Transform mTrs;
            protected virtual void Awake()
            {
                this.gameObject.SetActive(true);
                mTrs = this.gameObject.transform;
                InitWindowOnAwake();
            }
     
            private int minDepth = 1;
            public int MinDepth
            {
                get { return minDepth; }
                set { minDepth = value; }
            }
     
            /// <summary>
            /// 能否添加到导航数据中
            /// </summary>
            public bool CanAddedToBackSeq
            {
                get
                {
                    if (this.windowData.windowType == UIWindowType.PopUp)
                        return false;
                    if (this.windowData.windowType == UIWindowType.Fixed)
                        return false;
                    if (this.windowData.showMode == UIWindowShowMode.NoNeedBack)
                        return false;
                    return true;
                }
            }
     
            /// <summary>
            /// 界面是否要刷新BackSequence数据
            /// 1.显示NoNeedBack或者从NoNeedBack显示新界面 不更新BackSequenceData(隐藏自身即可)
            /// 2.HideOther
            /// 3.NeedBack
            /// </summary>
            public bool RefreshBackSeqData
            {
                get
                {
                    if (this.windowData.showMode == UIWindowShowMode.HideOther
                        || this.windowData.showMode == UIWindowShowMode.NeedBack)
                        return true;
                    return false;
                }
            }
     
            /// <summary>
            /// 在Awake中调用,初始化界面(给界面元素赋值操作)
            /// </summary>
            public virtual void InitWindowOnAwake()
            {
            }
     
            /// <summary>
            /// 获得该窗口管理类
            /// </summary>
            public UIManagerBase GetWindowManager
            {
                get
                {
                    UIManagerBase baseManager = this.gameObject.GetComponent<UIManagerBase>();
                    return baseManager;
                }
                private set { }
            }
     
            /// <summary>
            /// 重置窗口
            /// </summary>
            public virtual void ResetWindow()
            {
            }
     
            /// <summary>
            /// 初始化窗口数据
            /// </summary>
            public virtual void InitWindowData()
            {
                if (windowData == null)
                    windowData = new WindowData();
            }
     
            public virtual void ShowWindow()
            {
                isShown = true;
                NGUITools.SetActive(this.gameObject, true);
            }
     
            public virtual void HideWindow(Action action = null)
            {
                IsLock = true;
                isShown = false;
                NGUITools.SetActive(this.gameObject, false);
                if (action != null)
                    action();
            }
     
            public void HideWindowDirectly()
            {
                IsLock = true;
                isShown = false;
                NGUITools.SetActive(this.gameObject, false);
            }
     
            public virtual void DestroyWindow()
            {
                BeforeDestroyWindow();
                GameObject.Destroy(this.gameObject);
            }
     
            protected virtual void BeforeDestroyWindow()
            {
            }
     
            /// <summary>
            /// 界面在退出或者用户点击返回之前都可以注册执行逻辑
            /// </summary>
            protected void RegisterReturnLogic(BoolDelegate newLogic)
            {
                returnPreLogic = newLogic;
            }
     
            public bool ExecuteReturnLogic()
            {
                if (returnPreLogic == null)
                    return false;
                else
                    return returnPreLogic();
            }
        }
    }


    动画接口设计
    界面可以继承该接口进行实现打开和关闭动画
    [C#] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /// <summary>
    /// 窗口动画
    /// </summary>
    interface IWindowAnimation
    {
        /// <summary>
        /// 显示动画
        /// </summary>
        void EnterAnimation(EventDelegate.Callback onComplete);
         
        /// <summary>
        /// 隐藏动画
        /// </summary>
        void QuitAnimation(EventDelegate.Callback onComplete);
         
        /// <summary>
        /// 重置动画
        /// </summary>
        void ResetAnimation();
    }

    [C#] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public void EnterAnimation(EventDelegate.Callback onComplete)
    {
        if (twAlpha != null)
        {
            twAlpha.PlayForward();
            EventDelegate.Set(twAlpha.onFinished, onComplete);
        }
    }
     
    public void QuitAnimation(EventDelegate.Callback onComplete)
    {
        if (twAlpha != null)
        {
            twAlpha.PlayReverse();
            EventDelegate.Set(twAlpha.onFinished, onComplete);
        }
    }
     
    public override void ResetWindow()
    {
        base.ResetWindow();
        ResetAnimation();
    }

    窗口管理和导航设计实现
    导航功能实现通过一个显示窗口堆栈实现,每次打开和关闭窗口通过判断窗口属性和类型更新处理BackSequence数据
    • 打开界面:将当前界面状态压入堆栈中更新BackSequence数据
    • 返回操作(主动关闭当前界面或者点击返回按钮):从堆栈中Pop出一个界面状态,将相应的界面重新打开
    • 怎么衔接:比如从一个界面没有回到上一个状态而是直接的跳转到其他的界面,这个时候需要将BackSequence清空因为当前的导航链已经被破坏,当BackSequence为空需要根据当前窗口指定的PreWindowId告知系统当从该界面返回,需要到达的指定页面,这样就能解决怎么衔接的问题,如果没断,继续执行导航,否则清空数据,根据PreWindowId进行导航

    导航系统中关键性设计:
    游戏中可以存在多个的Manager进行管理(一般在很少需求下才会使用),每个管理对象需要维护自己的导航信息BackSequence,每次退出一个界面需要检测当前退出的界面是否存在相应的Manager管理,如果存在则需要先执行Manager退出操作(退出过程分步进行)保证界面一层接着一层正确退出


    窗口层级,Collider,统一背景添加如何实现?
    有很多方式进行层级管理,该框架选择的方法如下
    • 设置三个常用层级Root,根据窗口类型在加载到游戏中时添加到对应的层级Root下面即可,每次添加重新计算设置层级(通过UIPanel的depth实现)保证每次打开一个新窗口层级显示正确,每次窗口内通过depth的大小区分层级关系
    • 根据窗口Collider和背景类型,在窗口的最小Panel上面添加Collider或者带有碰撞体的BackGround即可
    <ignore_js_op>Scene目录<ignore_js_op>层级信息 


    具体实现如下:
    [C#] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    private void AdjustBaseWindowDepth(UIBaseWindow baseWindow)
    {
        UIWindowType windowType = baseWindow.windowData.windowType;
        int needDepth = 1;
        if (windowType == UIWindowType.Normal)
        {
            needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UINormalWindowRoot.gameObject, false) + 1, normalWindowDepth, int.MaxValue);
            Debug.Log("[UIWindowType.Normal] maxDepth is " + needDepth + baseWindow.GetID);
        }
        else if (windowType == UIWindowType.PopUp)
        {
            needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UIPopUpWindowRoot.gameObject) + 1, popUpWindowDepth, int.MaxValue);
            Debug.Log("[UIWindowType.PopUp] maxDepth is " + needDepth);
        }
        else if (windowType == UIWindowType.Fixed)
        {
            needDepth = Mathf.Clamp(GameUtility.GetMaxTargetDepth(UIFixedWidowRoot.gameObject) + 1, fixedWindowDepth, int.MaxValue);
            Debug.Log("[UIWindowType.Fixed] max depth is " + needDepth);
        }
        if(baseWindow.MinDepth != needDepth)
            GameUtility.SetTargetMinPanel(baseWindow.gameObject, needDepth);
        baseWindow.MinDepth = needDepth;
    }
     
    /// <summary>
    /// 窗口背景碰撞体处理
    /// </summary>
    private void AddColliderBgForWindow(UIBaseWindow baseWindow)
    {
        UIWindowColliderMode colliderMode = baseWindow.windowData.colliderMode;
        if (colliderMode == UIWindowColliderMode.None)
            return;
     
        if (colliderMode == UIWindowColliderMode.Normal)
            GameUtility.AddColliderBgToTarget(baseWindow.gameObject, "Mask02", maskAtlas, true);
        if (colliderMode == UIWindowColliderMode.WithBg)
            GameUtility.AddColliderBgToTarget(baseWindow.gameObject, "Mask02", maskAtlas, false);
    }


    多形态MessageBox实现
    这个应该是项目中一定会用到的功能,说下该框架简单的实现
    • 三个按钮三种回调逻辑:左中右三个按钮,提供设置内容,设置回调函数的接口即可
    • 提供接口设置核心Content
    • 不同作用下不同的按钮不会隐藏和显示
    [C#] 纯文本查看 复制代码
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void SetCenterBtnCallBack(string msg, UIEventListener.VoidDelegate callBack)
    {
        lbCenter.text = msg;
        NGUITools.SetActive(btnCenter, true);
        UIEventListener.Get(btnCenter).onClick = callBack;
    }
     
    public void SetLeftBtnCallBack(string msg, UIEventListener.VoidDelegate callBack)
    {
        lbLeft.text = msg;
        NGUITools.SetActive(btnLeft, true);
        UIEventListener.Get(btnLeft).onClick = callBack;
    }
     
    public void SetRightBtnCallBack(string msg, UIEventListener.VoidDelegate callBack)
    {
        lbRight.text = msg;
        NGUITools.SetActive(btnRight, true);
        UIEventListener.Get(btnRight).onClick = callBack;
    }




    后续需要改进和增强计划
    • 图集管理,针对大中型游戏对游戏内存要求苛刻的项目,一般都会对UI图集贴图资源进行动态管理,加载和卸载图集,保证UI贴图占用较少内存
    • 增加一些通用处理:变灰操作,Mask遮罩(一般用于新手教程中)等
    • 在进行切换的过程可以需要Load新场景需求,虽然这个也可以在UI框架外实现
    • 对话系统也算是UI框架的功能,新手引导系统也可以加入到UI框架中,统一管理和处理新手引导逻辑
    需求总是驱动着系统逐渐强大,逐渐完善,逐渐发展,一步一步来吧~

    实现效果
    <ignore_js_op>效果演示 

    整个框架的核心部分介绍完毕,这是项目Demo的下载链接,有需要的朋友感兴趣的朋友可以下载参考下,希望能够给耐心看到结尾的朋友一点启发或者带来一点帮助,存在错误和改进的地方也希望留言交流共同进步学习~


    有些时候,我们总是知道这么个理明白该怎样实现,但是关键的就是要动手实现出来,实现的过程会发现自己的想法在慢慢优化,不断的需求和bug的产生让框架慢慢成熟,可以投入项目使用提升一些开发效率和减少工作量。


    By 漂流燕(Andy)

  • 相关阅读:
    Prometheus实现微信邮件钉钉报警
    产品需求文档和原型
    各类数据集
    redis与mysql数据同步
    hadoop hbase hive spark对应版本
    Redis集群的搭建
    mysql数据库数据与redis同步
    企业级Zabbix监控实战(一)
    mysql实现高可用架构之MHA
    04-爬取单个英雄联盟英雄的符文图片
  • 原文地址:https://www.cnblogs.com/littleYellowDoggy/p/5024788.html
Copyright © 2020-2023  润新知