【設計和開發一套簡單自己主動化UI框架】
阿新 • • 發佈:2017-08-19
depth 定義 其它 而是 例如 選擇 span debug etl
動畫接口設計
界面能夠繼承該接口進行實現打開和關閉動畫
窗體管理和導航設計實現 導航功能實現通過一個顯示窗體堆棧實現。每次打開和關閉窗體通過推斷窗體屬性和類型更新處理BackSequence數據
窗體層級,Collider。統一背景加入怎樣實現? 有非常多方式進行層級管理,該框架選擇的方法例如以下
詳細實現例如以下:
多形態MessageBox實現 這個應該是項目中一定會用到的功能,說下該框架簡單的實現
!有興趣的朋友請直接移步Github,本帖子已經不做更新,框架的詳細的實現已經做了優化和代碼整理,本文僅僅介紹了詳細的設計思路!
目標:編寫一個簡單通用UI框架用於管理頁面和完畢導航跳轉
終於的實現效果請拉到最下方查看框架詳細實現的功能和需求
- 載入。顯示,隱藏,關閉頁面,依據標示獲得對應界面實例
- 提供界面顯示隱藏動畫接口
- 單獨界面層級。Collider。背景管理
- 依據存儲的導航信息完畢界面導航
- 界面通用對話框管理(多類型Message Box)
- 便於進行需求和功能擴展(比方,在跳出頁面之前加入邏輯處理等)
編寫UI框架意義
- 打開,關閉,層級,頁面跳轉等管理問題集中化,將外部切換等邏輯交給UIManager處理
- 功能邏輯分散化,每一個頁面維護自身邏輯,依托於框架便於多人協同開發,不用關心跳轉和顯示關閉細節
- 通用性框架可以做到簡單的代碼復用和"項目經驗"沈澱
步入正題,怎樣實現
- 窗體類設計:基本窗體對象,維護自身邏輯維護
- 窗體管理類:控制被管理窗體的打開和關閉等邏輯(詳細設計請看下文)
- 動畫接口:提供打開和關閉動畫接口,提供動畫完畢回調函數等
- 層級,Collider背景管理
public enum UIWindowType { Normal, // 可推出界面(UIMainMenu,UIRank等) Fixed, // 固定窗體(UITopBar等) PopUp, // 模式窗體 } public enum UIWindowShowMode { DoNothing, HideOther, // 閉其它界面 NeedBack, // 點擊返回button關閉當前,不關閉其它界面(須要調整好層級關系) 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數據
- 返回操作(主動關閉當前界面或者點擊返回button):從堆棧中Pop出一個界面狀態,將對應的界面又一次打開
- 怎麽銜接:比方從一個界面沒有回到上一個狀態而是直接的跳轉到其它的界面,這個時候須要將BackSequence清空由於當前的導航鏈已經被破壞。當BackSequence為空須要依據當前窗體指定的PreWindowId告知系統當從該界面返回,須要到達的指定頁面。這樣就能解決怎麽銜接的問題,假設沒斷,繼續運行導航,否則清空數據,依據PreWindowId進行導航
窗體層級,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實現 這個應該是項目中一定會用到的功能,說下該框架簡單的實現
- 三個button三種回調邏輯:左中右三個button,提供設置內容,設置回調函數的接口就可以
- 提供接口設置核心Content
- 不同作用下不同的button不會隱藏和顯示
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框架中,統一管理和處理新手引導邏輯
實現效果
整個框架的核心部分介紹完成,有須要查看源代碼的請移步GitHub。興許會繼續完好和整理,希望可以給耐心看到結尾的朋友一點啟示或者帶來一點幫助。存在錯誤和改進的地方也希望留言交流共同進步學習~
有些時候,我們總是知道這麽個理明確該如何實現。可是關鍵的就是要動手實現出來,實現的過程會發現自己的想法在慢慢優化。不斷的需求和bug的產生讓框架慢慢成熟,能夠投入項目使用提升一些開發效率和降低工作量。
【設計和開發一套簡單自己主動化UI框架】