• 【转】发布一个基于NGUI编写的UI框架


    发布一个基于NGUI编写的UI框架 
    1.加载,显示,隐藏,关闭页面,根据标示获得相应界面实例 
    2.提供界面显示隐藏动画接口 
    3.单独界面层级,Collider,背景管理 
    4.根据存储的导航信息完成界面导航 
    5.界面通用对话框管理(多类型Message Box) 
    6.便于进行需求和功能扩展(比如,在跳出页面之前添加逻辑处理等) 

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

    • 加载,显示,隐藏,关闭页面,根据标示获得相应界面实例
    • 提供界面显示隐藏动画接口
    • 单独界面层级,Collider,背景管理
    • 根据存储的导航信息完成界面导航
    • 界面通用对话框管理(多类型Message Box)
    • 便于进行需求和功能扩展(比如,在跳出页面之前添加逻辑处理等)

    编写UI框架意义 

    • 打开,关闭,层级,页面跳转等管理问题集中化,将外部切换等逻辑交给UIManager处理
    • 功能逻辑分散化,每个页面维护自身逻辑,依托于框架便于多人协同开发,不用关心跳转和显示关闭细节
    • 通用性框架能够做到简单的代码复用和"项目经验"沉淀

    步入正题,如何实现 

    1. 窗口类设计:基本窗口对象,维护自身逻辑维护
    2. 窗口管理类:控制被管理窗口的打开和关闭等逻辑(具体设计请看下文)
    3. 动画接口:提供打开和关闭动画接口,提供动画完成回调函数等
    4. 层级,Collider背景管理

    窗口基类设计 
    框架中设计的窗口类型和框架所需定义如下 

    复制代码

    public enum UIWindowType
    {
        Normal,    // 可推出界面(UIMainMenu,UIRank等)
        Fixed,     // 固定窗口(UITopBar等)
        PopUp,     // 模式窗口
    }
                    
    public enum UIWindowShowMode
    {
        DoNothing,
        HideOther,     // 闭其他界面
        NeedBack,      // 点击返回按钮关闭当前,不关闭其他界面(需要调整好层级关系)
        NoNeedBack,    // 关闭TopBar,关闭其他界面,不加入backSequence队列
    }
                    
    public enum UIWindowColliderMode
    {
        None,      // 显示该界面不包含碰撞背景
        Normal,    // 碰撞透明背景
        WithBg,    // 碰撞非透明背景
    }


    复制代码

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

    动画接口设计 
    界面可以继承该接口进行实现打开和关闭动画 

    复制代码

    /// <summary>
    /// 窗口动画
    /// </summary>
    interface IWindowAnimation
    {
        /// <summary>
        /// 显示动画
        /// </summary>
        void EnterAnimation(EventDelegate.Callback onComplete);
                        
        /// <summary>
        /// 隐藏动画
        /// </summary>
        void QuitAnimation(EventDelegate.Callback onComplete);
                        
        /// <summary>
        /// 重置动画
        /// </summary>
        void ResetAnimation();
    }


    复制代码

    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即可

     
    具体实现如下: 

    复制代码

    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
    • 不同作用下不同的按钮不会隐藏和显示


    复制代码

    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;
    }

    后续需要改进和增强计划 

    1. 图集管理,针对大中型游戏对游戏内存要求苛刻的项目,一般都会对UI图集贴图资源进行动态管理,加载和卸载图集,保证UI贴图占用较少内存
    2. 增加一些通用处理:变灰操作,Mask遮罩(一般用于新手教程中)等
    3. 在进行切换的过程可以需要Load新场景需求,虽然这个也可以在UI框架外实现
    4. 对话系统也算是UI框架的功能,新手引导系统也可以加入到UI框架中,统一管理和处理新手引导逻辑

    需求总是驱动着系统逐渐强大,逐渐完善,逐渐发展,一步一步来吧~ 
    实现效果 
     

    整个框架的核心部分介绍完毕,有需要的朋友感兴趣的朋友可以下载参考下希望能够给耐心看到结尾的朋友一点启发或者带来一点帮助,存在错误和改进的地方也希望留言交流共同进步学习~有些时候,我们总是知道这么个理明白该怎样实现,但是关键的就是要动手实现出来,实现的过程会发现自己的想法在慢慢优化,不断的需求和bug的产生让框架慢慢成熟,可以投入项目使用提升一些开发效率和减少工作量。 




    希望能够帮助到大家~ 


    UI框架介绍 
    GitHub地址:https://github.com/tinyantstudio/UIFrameWork 
    持续更新,新的想法,针对新的需求框架进行扩展的优化~希望感兴趣的朋友一起针对游戏开发一起交流和学习~issues~ 



    !有兴趣的朋友请直接移步Github,本帖子已经不做更新,框架的具体的实现已经做了优化和代码整理,本文只介绍了具体的设计思路!

  • 相关阅读:
    docker 目录移动到其他磁盘的操作
    linux -- 查看磁盘空间的大小 查看文件夹占用磁盘空间大小
    python flask 反向代理
    shell 脚本换行符的问题
    uniapp使用web-view跳转vue单页面通信,互发消息
    java基础(多线程---lambda)
    SpringBoot开发详解(五)--Controller接收参数以及参数校验
    使用 TypeScript 来开发 React 的注意事项
    video标签通过js实现增加倍速播放功能
    zblog调取置顶文章
  • 原文地址:https://www.cnblogs.com/mimime/p/6240304.html
Copyright © 2020-2023  润新知