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><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)

时间: 2024-08-05 19:08:15

Unity3D通用UI框架的相关文章

【设计和开发一套简单自己主动化UI框架】

!有兴趣的朋友请直接移步Github,本帖子已经不做更新,框架的详细的实现已经做了优化和代码整理,本文仅仅介绍了详细的设计思路! 目标:编写一个简单通用UI框架用于管理页面和完毕导航跳转 终于的实现效果请拉到最下方查看 框架详细实现的功能和需求 载入.显示,隐藏,关闭页面,依据标示获得对应界面实例 提供界面显示隐藏动画接口 单独界面层级.Collider.背景管理 依据存储的导航信息完毕界面导航 界面通用对话框管理(多类型Message Box) 便于进行需求和功能扩展(比方,在跳出页面之前加入

Unity3D 搭建优雅的UI框架

为什么要使用UI框架?直接使用NGUI或UGUI一拖一拉直接搭载出界面不就行了? 我相信很多小白,包括我在刚学习Unity3D UI的时候都这样想过. 我的第一款款Unity2D游戏<山地赛车>,使用的就是NGUI搭载界面. 弱联网手游一般都没什么复杂的界面,我也是很轻松花一天就把界面搭载好了,看起来好挺好看的,还花了不少时间做动态效果. 界面搭载好后,开始开发游戏内容,这下问题开始来了: 1.如何实现界面间的沟通?例如点击返回按钮,返回上一个界面,点击背包系统,弹出背包. 2.如何实现界面与

游戏UI框架设计(五): 配置管理与应用

游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所谓"配置管理"是指一个游戏项目(软件项目),很多需要经常变化的需求或者数据,最好以配置文件的形式存在,从而代替"硬编码"方式.      这里笔者就对游戏产品中大量应用到动态加载的情形,开发出一套通用的配置管理(脚本)工具.该工具可以很方便的对于具备"键值对&

UI框架,你最中意谁?

现在,移动.跨屏已经成为当下互联网最热门的技术,而前端开发者在开发网页时,时常会陷入重复解决繁复的跨屏.适配问题,耗费精力,影响工作效率,产品开发进度慢这样的恶性循环中.针对以上这些情况,小编整理了几个前端框架,供开发者在开发网页时参考了解. 首先,小编要先说说ZUI.ZUI是禅道项目管理软件团队在完善自己产品过程中,形成的一个开源前端实践方案,能快速构现代跨屏应用.它简单美观,易于使用,快速构建简洁大方的现代web应用.ZUI采用HTML5且支持所有流行的移动及桌面浏览器平台,一些旧的浏览器也

60.Android通用流行框架大全

转载:https://segmentfault.com/a/1190000005073746 Android通用流行框架大全 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picasso 一个强大的图片下载与缓存的库 Fresco 一个用于管理图像和他们使用的内存的库 Glide 一个图片加载和缓存的库 3. 图片处理 名称 描述 Pi

android开发从零开始 -----Android通用流行框架大全

好东西值得分享 ,这是网络上总结的一些开源的东西直接就拿过来了  .... Android通用流行框架大全 先把这张图放在这 ,先来谈一谈项目结构 .我喜欢将东西按模块来划分: 都知道module .它的应用非常方便 .对于一个项目刚开始开发时要考虑这个项目是由那些部分组成 lib_base  :包含各种Base基类 .如 BaseActivty  BaseFragment  BaseApplication   这是一些项目的开始基础. lib_ui:各种自定义UI ,或第三方ui .现在and

基础知识漫谈(2):从设计UI框架开始

说UI能延展出一丢丢的东西来,光java就有swing,swt/jface乃至javafx等等UI toolkit,在桌面上它们甚至都不是主流,在web端又有canvas.svg等等. 基于这些UI工具包\框架,又产生了大量通用的或者业务性的UI框架,比如Draw2d.GEF.easyUI乃至国内的EChart.白鹭等等. 这些框架的业务范围各异,一个程序员的时间和精力有限,你不可能全部都掌握,又不能预言出是哪一个将来会独步天下,甚至,连当前哪一个最流行,都够打一阵嘴炮. 那,我们应该学什么?

C++UI框架

WTL都算不上什么Framework,就是利用泛型特性对Win API做了层封装,设计思路也没摆脱MFC的影响,实际上用泛型做UI Framework也只能算是一次行为艺术,这个思路下继续发展就会变得没法用了,比如 代码过于复杂,编译太慢,出错不好调试等问题难以解决. 而且封装得也不完全,还是随处可见 HWND HDC之类的东西. 用途主要是写一些很小的程序,或者作为其他UI框架的后端实现部分,比如我写过一个小框架用来做安装卸载程序,非常小,其中创建管理窗口部分是用WTL的.MFC是更高级点的W

游戏UI框架设计(五): 配置管理与应用

游戏UI框架设计(五) --配置管理与应用 在开发企业级游戏/VR/AR产品时候,我们总是希望可以总结出一些通用的技术体系,框架结构等,为简化我们的开发起到"四两拨千金"的作用.所谓"配置管理"是指一个游戏项目(软件项目),很多需要经常变化的需求或者数据,最好以配置文件的形式存在,从而代替"硬编码"方式. 这里笔者就对游戏产品中大量应用到动态加载的情形,开发出一套通用的配置管理(脚本)工具.该工具可以很方便的对于具备"键值对"