基于NGUI编写的UI框架(转)

原文链接:http://www.ceeger.com/forum/read.php?tid=21745&fid=2

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

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

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

复制代码
1
2
3
4
5
6
7
8
9
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,     // 碰撞非透明背景
}

复制代码
1
2
3
4
5
6
7
8
9
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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();
         }
     }
}
动画接口设计  
界面可以继承该接口进行实现打开和关闭动画  

复制代码
1
2
3
4
5
6
7
8
9
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();
}

复制代码
1
2
3
4
5
6
7
8
9
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即可
 
具体实现如下:  

复制代码
1
2
3
4
5
6
7
8
9
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
  • 不同作用下不同的按钮不会隐藏和显示

复制代码
1
2
3
4
5
6
7
8
9
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;
}
后续需要改进和增强计划 

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

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




希望能够帮助到大家~  
 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值