基於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~ 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章