BridgeUI-第二代綜合性UGUI框架
—-基於Unity3d及UGUI,結合編輯器擴展之節點圖製作而成的利於編輯,接口簡潔的人性化界面框架.
- 從開源項目AssetBundleGraph工程中提取出優異的節點編輯器
- 分離界面自身屬性及界面關聯屬性
- 分離屬性的設置及預製體的製作
- 支持雙定義層級 即基本層級類型和int型層級
- 支持打開動畫及關閉動畫的編輯器狀態指定
- 將遊戲自身邏輯完全獨立於界面創建和關聯的邏輯
- 支持同父級面板互斥不同顯功能
- 支持編輯器模式快速打開面板及批量保存功能
接口
1.任意腳本中打開一個面板
var handle = UIFacade.Instence.Open(PanelName:string);
2.在面板中打開面板爲子面板
var handle = selfFacade.Open(PanelName:string, data:object);
3.強行關閉對應名稱的面板
UIFacade.Instence.Close(PanelName:string);
4.強行隱藏對應的面板
UIFacade.Instence.Hide(PanelName:string);
5.註冊界面打開事件
handle.onCreate = (panel)=>{
}
6.註冊界面關閉事件
handle.onClose = (panel)=>{
}
7.註冊界面信息回調
handle.onCallBack = (panel,data:object)=>{
}
8.定向發送信息
handle.Send(data:object);
圖形化
1.利用線來表示界面與界面之間的關係
2.將節點信息記錄獨立於Prefab
3.快速展開編輯和快速保存
UML設計
1.Facade及生成器
2.界面類型信息模型
3.界面類型信息模型
4.連接器及句柄對象
5.MonoBehaiver
開發過程
實際上正確的流程應該是先設計好類與類之間的關係,然後進行具體的編程,但由於前期製作過了個簡單的界面打開關閉的功能,並在實際使用過程中遇到一些急需解決的問題.因此在做這個框架的時候基本上就是想到什麼就寫什麼了.最後才完善的這個UML類圖(其實目前還是不能正確的生成腳本).下面是開發的過程,希望引以爲戒.
1.找到一個合適的節點連接框架
一開始就想設計成一個利於編輯和利用的界面管理器,所以找了不少開源的節點連接的框架,最開始用NodeEditorFrameWork,說真的它還能支持非編輯器模式,我開始幻想以後是不是可以開始一個用戶可以指定界面打開規則的框架,然而在使用過程中發現其並不支持對連接線信息的記錄和保存功能,而且線還不能點擊,所以果斷放棄(畢竟實在沒興趣把它改成可以點線...),這時選擇了unity日本的一個AssetBundleGraph,這個工具在當時的unity2017大會上有人專門講過,雖然這個框架寫的非常好,基本上涵括了整個開發流程用到的東西,但實際開發中有太多的需要自定義,一直擱置在那沒研究,這次倒用上了.
2.保留上一版框架的界面編輯功能
雖然有了這樣了個可以直觀看到的編輯面板加載和關聯的信息的殼子,但還是不能滿足開發過程中需要時常編輯面板和代碼的功能,所以果斷把上一版本的繪製腳本拷貝過程改改直接用了.但由於上一板中用的支持多個面板組的功能在這種框架的基礎上並沒有太多存在的意義,最後就去掉了.同時由於不再需要在這個面板內進行信息修改,所以BridgeInfo的信息也沒有繪製出來了.
3.將編輯器狀態保存的信息轉換到可運行時讀取的信息
一開始並不打算用兩個數據模型來轉換,想直接是所見就是所得,但AssetBundleGraph中的數據模型只支持編輯器模式,所以也沒有改了(以後到是可以合併下).雖然這樣不利也有一些好處,就是改的時候不一定應用,而且支持多個asset信息保存到一個PanelGroup中.
4.Bridge的思路來源
上一板資源創建的方式是一開始就註冊到事件中,需要創建和銷燬的及隱藏的功能都是動態註冊的事件,而且邊信息傳遞的方式都是事件.這樣的思路個人覺得對性能還是很好的,不應該不用每次創建或者刪除都去找到相應的信息再進行操作.但這樣也有個程序擴展性不好,可讀性不高的問題.這次就直接放棄了這種方式.但如果不用事件系統那麼信息傳遞和保留通道是個問題,最後纔想到Bridge(橋)的解決方案,就是在面板打開過程中形成一個動態的Bridge對象,那麼就可以通過這個對象進行一系列的操作了.因爲這個框架是支持多個PanelGroup的,就是說你可能會同時打開兩個一樣名稱的面板,那麼你怎麼能同時對兩個面板發送信息呢.所以UIFacade中並沒有返回Bridge而是返回了一個Handle,通過這個Handle就可以同時和幾個Bridge通信了
核心邏輯
1.利用UIType來進行界面自身的初始化
private void AppendComponentsByType()
{
if (UType == null)
{
UType = new UIType();
}
if (UType.form == UIFormType.DragAble)
{
if (gameObject.GetComponent<DragPanel>() == null)
{
gameObject.AddComponent<DragPanel>();
}
}
if (UIAnimType.NoAnim != UType.enterAnim)
{
animPlayer = GetComponent<AnimPlayer>();
if (animPlayer == null){
animPlayer = gameObject.AddComponent<AnimPlayer>();
}
animPlayer.EnterAnim(UType.enterAnim, null);
}
}
public virtual void Hide()
{
_isShowing = false;
switch (UType.hideRule)
{
case HideRule.AlaphGameObject:
AlaphGameObject(true);
break;
case HideRule.HideGameObject:
gameObject.SetActive(false);
break;
default:
break;
}
}
public virtual void UnHide()
{
_isShowing = true;
switch (UType.hideRule)
{
case HideRule.AlaphGameObject:
AlaphGameObject(false);
break;
case HideRule.HideGameObject:
gameObject.SetActive(true);
break;
default:
break;
}
}
public virtual void Close()
{
if (IsShowing && UType.quitAnim != UIAnimType.NoAnim)
{
var tweenPanel = GetComponent<AnimPlayer>();
if (tweenPanel == null)
{
tweenPanel = gameObject.AddComponent<AnimPlayer>();
}
tweenPanel.QuitAnim(UType.quitAnim, CloseInternal);
}
else
{
CloseInternal();
}
}
2.利用ShowMode處理不打開過程中的顯示方案
private void TryAutoOpen(Transform content, IPanelBase parentPanel = null)
{
var panelName = parentPanel == null ? "" : parentPanel.Name;
var autoBridges = bridges.FindAll(x => x.inNode == panelName && (x.showModel & ShowMode.Auto) == ShowMode.Auto);
if (autoBridges != null)
{
foreach (var autoBridge in autoBridges)
{
InstencePanel(parentPanel, autoBridge.outNode, content);
}
}
}
private void TryMakeCover(IPanelBase panel, BridgeInfo info)
{
if ((info.showModel & ShowMode.Cover) == ShowMode.Cover)
{
panel.Cover();
}
}
private void TryHideParent(IPanelBase panel, BridgeInfo bridge)
{
if ((bridge.showModel & ShowMode.HideBase) == ShowMode.HideBase)
{
var parent = createdPanels.Find(x => x.Name == bridge.inNode);
if (parent != null)
{
panel.SetParent(Trans);
HidePanelInteral(panel, parent);
}
}
}
private void TryHideMutexPanels(IPanelBase childPanel, BridgeInfo bridge)
{
if ((bridge.showModel & ShowMode.Mutex) == ShowMode.Mutex)
{
var mayBridges = bridges.FindAll(x => x.inNode == bridge.inNode);
foreach (var bg in mayBridges)
{
var mayPanels = createdPanels.FindAll(x => x.Name == bg.outNode && x.UType.layer == childPanel.UType.layer && x != childPanel);
foreach (var mayPanel in mayPanels)
{
if (mayPanel != null && mayPanel.IsShowing)
{
HidePanelInteral(childPanel, mayPanel);
}
}
}
}
}
private void TryHideGroup(IPanelBase panel, BridgeInfo bridge)
{
if ((bridge.showModel & ShowMode.Single) == ShowMode.Single)
{
var parent = createdPanels.Find(x => x.Name == bridge.inNode);
if (parent != null)
{
panel.SetParent(Trans);
}
foreach (var oldPanel in createdPanels)
{
if (oldPanel != panel)
{
HidePanelInteral(panel, oldPanel);
}
}
}
}
3.面板銷燬及橋的回收
protected override void OnDestroy()
{
base.OnDestroy();
_isAlive = false;
_isShowing = false;
if (bridge != null)
{
bridge.Release();
}
if (onDelete != null)
{
onDelete.Invoke(this);
}
}
private void OnDeletePanel(IPanelBase panel)
{
if (createdPanels.Contains(panel))
{
createdPanels.Remove(panel);
}
if (bridgeDic.ContainsKey(panel))
{
bridgeDic.Remove(panel);
}
if (panel.ChildPanels != null)
{
var childs = panel.ChildPanels.ToArray();
foreach (var item in childs)
{
if (item.IsAlive)
{
item.Close();
}
}
}
if (hidedPanelStack.ContainsKey(panel))
{
var stack = hidedPanelStack[panel];
if (stack != null)
{
while (stack.Count > 0)
{
var item = stack.Pop();
if (item.IsAlive && !item.IsShowing)
{
item.UnHide();
}
//Debug.Log("Pop:" + item);
}
}
hidedPanelStack.Remove(panel);
}
}
public void UnRegistBridge(Bridge obj)
{
if (bridges.Contains(obj))
{
obj.onCallBack -= OnBridgeCallBack;
obj.onRelease -= UnRegistBridge;
obj.onCreate -= OnCreatePanel;
bridges.Remove(obj);
}
if (onClose != null)
{
onClose(obj.OutPanel);
}
if (bridges.Count == 0)
{
Release();
}
}
後記
目前這個框架還沒有投入具體的項目中使用,所以可能會存在較多的漏洞,比較場景切換等極端狀態發生的時候,但會持續的改進中…如有興趣可到GitHub中搜索BridgeUI.