上一篇我們分析了UI的架構原則以及為什麼要這麼架構的一些原因,這篇會具體實作架構的方方面面,東西會有點多。我輩求道,豈能求快!
為了避免架構的幹擾我們先來配置一下開發環境。
LuaFrameWork的啟動是在:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLx0EVONTQU5UMFpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5QDOwMTN0YDM3EzNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
加載的相關可以看這裡(加載)
上面主要做的就是去除和我們現在沒有聯系的子產品,避免一些問題。
現在開始分析具體流程。
經過前一篇的分析我們可以總結出:
UI管理層(UIManager):加載、打開、關閉、解除安裝。管理UI集合的自動打開和關閉,以及對外的接口。
UI集合層(UIBaseCollect):加載、打開、關閉、解除安裝。管理多個UI。
UI層(UIBaseView):加載、打開、關閉、解除安裝。管理UI本身的生命周期。
其實流程都是類似的,一層層的管理下去。
流程總覽
上面的圖是我們的三層架構流程總覽圖。
按列劃分:
第一列:UI管理層(UIManager)相關操作;
第二列:UI集合層(UIBaseColelct)相關操作;
第三列:UI層(UIBaseView)相關操作;
實作代表同步,虛線代表異步(不在同一幀執行)。
我們采取自下而上的講解方式。那麼先開始UI層吧。
UI層
一般的UI流程肯定是有以下流程:加載、打開、關閉、解除安裝。
在分析詳細四個流程前,我們先确認下Prefab的制作規範,這樣對于講解後面的流程是有幫助的。
Prefab的制作要求:
上圖有兩個部分:編輯器模式下的Prefab顯示,和運作時的Prefab顯示
這樣一對比會發現這兩個圖有點對不上,是的,運作時和編輯器模式下的顯示不是一樣的。
-
先看編輯器模式:
綠色是UIRoot,黃色是UI相機,紅色是Prefab的主題結構。
-
再看運作時部分:
紅色是代表UIRoot,綠色代表Prefab的主題就夠。
因為整個UI使用一個UIRoot和一個UI相機,但是現在每個UI都有一個UIRoot和一個相機,是以我們将這兩個部分抽離出來形成"ui/prefab/uibase"結構,這個節點将會是所有UI的父節點。後續UI在加載後會将Prefab下Core節點改名成"ui/prefab/uipanel_messagebox"放到UIRoot(“ui/prefab/uipanel_base”)下面去;
好了Prefab的制作規範就完成了。
下圖是代碼解釋:加載完成Prefab之後執行個體化過程。
public static GameObject InitGameObject(int type, GameObject obj, string assetName)
{
......
// UI
else if(type == 2)
{
GameObject root = GameObject.Find("ui/prefab/uipanel_base");
parent = root.transform;
son = obj.transform.FindChild("Core");
GameObject.Destroy(obj);
}
......
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
現在來說UI的四個流程
UI加載
先看圖
- 打開參數:有時候我們打開一個UI需要特定的一些資料去做顯示,比如:UI上有多個頁簽,我現在想打開UI前就決定顯示到哪個頁簽。我們經常會自己寫一個管理器去管理UI的行為,這并沒有問題,但是有時候我們會将資料放到管理器中,然後UI調用管理器中的資料,這樣的設計并不好,與UI相關的資料最好通過參數的形式傳進去,這就是我們打開參數設定的目的。
- 加載prefab是異步的:是以需要注意異步的問題,解除安裝的時候,在請求加載中需要取消加載。
- 綁定控件:這時一種省時省力的做法,後面會有單獨的一個章節來講解,這裡就不說了。
- 注冊UI事件 :UI的監聽事件不會動态的改變,是以放在加載裡面注冊就可以了。我們處理事件的方式是:隻有顯示的UI才能收到監聽事件的回調。你可能會問不顯示的UI就收不到?是的,因為UI在顯示時會重新重新整理頁面,是以就不需要在隐藏的時候更新了。
- 加載完成:加載完成之後會通知子類,這樣子類就能知道加載已經完成,可以開始自己的操作了。
事件設計
既然UI上用到了事件注冊,那我們就來說說事件的設計。
--[[
注冊消息,發送消息
--]]
local Message = {};
-- 消息體
function BeginMessage(msgName)
local msg = {};
msg.name = msgName;
return msg;
end
-- 發送消息
function SendMessage(msg)
DispatchMessage(msg);
end
-- 注冊消息
function RegisterMessage(msgName,tCall,t)
if not Message[msgName] then
Message[msgName] = {};
end
local inIndex = table.ContainValue(Message[msgName],tCall,"tCall");
if inIndex ~= 0 then
return;
end
local msg = {};
msg.t = t;
msg.tCall = tCall;
table.insert(Message[msgName],msg);
end
-- 删除消息
function RemoveMessage(msgName,call)
if not Message[msgName] then
return;
end
local inIndex = table.ContainValue(Message[msgName],call,"tCall");
if inIndex ~= 0 then
table.remove(Message[msgName],inIndex);
end
end
-- 觸發注冊消息
function DispatchMessage(msg)
local registers = Message[msg.name];
if not registers then
return;
end
for i,v in ipairs(registers) do
v.tCall(v.t,msg);
end
end
事件的流程比較簡單,lua就中這點好,包羅萬象,是以事件的實作方式簡單。
UI打開
先看圖
- 計算UI層級:想讓UI呈現出前後的關系,我們需要改變UIPanel的depth值,我們在制作Prefab的過程中也會把UIPanel當成一個頁面的容器。
- 最後顯示的Prafab層級是最高的。
-- 設定UI層級
function _M:SetUILayer()
local obj = GetGameObjectById(self.uiInstanceId);
if not obj then
print("SetUILayer is error " , self.name);
return;
end
-- 擷取一個層級,這個層級是目前最大的
local layer = UILayer:CalculateLayer(self);
self:AddUILayerHelper(layer);
end
-- 删除UI層級
function _M:DelUILayer()
local obj = GetGameObjectById(self.uiInstanceId);
if not obj then
print("SetUILayer is error " , self.name);
return;
end
local layer = UILayer:DelLayer(self);
self:AddUILayerHelper(-layer);
end
function _M:AddUILayerHelper(layer)
local obj = GetGameObjectById(self.uiInstanceId);
if not obj then
print("AddUILayerHelper is error " .. self.name);
return;
end
local addLayer = layer * 100;
local panels = CommonUtil.GetUIPanels(obj);
-- C#中的用法 在lua中顯得很另類
for i = 0, panels.Length - 1 do
panels[i].depth = panels[i].depth + addLayer;
end
end
實際上就是尋找整個Preafab的UIPanel然後全部加上一個值,這個值是計算出來的,用來保證目前顯示的Prefab的UIPanel的depth是最大的,計算的方法是寫在UILayer裡面:儲存一個目前UI對應的清單,每次打開UI的時候就去找到最後打開的一個然後+1。
UILayer設計如下:
3. atals的加載:這裡的atlas包括圖集、圖檔。Prefab包括:自身的序列化資訊和引用的資訊。Prefab本身隻是序列化資料,不大,但是每次加載解析卻浪費了時間,尤其是Prefab的結構越複雜,需要的時間就越多。既然Prefab本身不大那我們保證Prefab在不切換場景前不解除安裝,隻解除安裝綁定在Prefab上的圖集和圖檔,這樣記憶體也沒有增加多少,再次打開這個界面的時候會很快。
4. 顯示完成:Prefab顯示之後會通知子類,Prefab已經顯示完成,可以開始自己的操作了。自己的操作:根據資料重新整理頁面顯示。
UI關閉
先看圖
關閉UI的流程和打開UI的流程剛還是相反的,是以就不說了。
UI解除安裝
解除安裝UI流程和加載UI流程也是相反的,是以就不說了。
Lua類的設計:
既然我們采取類的模式,那麼lua得提供類的功能,lua實作類的邏輯很巧妙,lua的table中有一個重要特性,可以給目前的table設定一個元表(也是一個table),這樣在目前table中找不到的屬性和方法可以去元表table中去尋找,這個機制不就和類的方式是類似的麼?這樣就能輕松實作類了。
基類UIBaseVIew設計:
建立一個UIBaseView基類(所有UI的父類),ctor是構造函數。
使用者的子類設計:
UIPanel_MessageBox繼承自UIBaseView,這樣UIPanel_MessageBox也就擁有和UIBaseView一樣的生命周期。
這樣我們UI層(UIBaseView)就設計完成了。
項目位址:https://github.com/xiaoyanxiansheng/SmallEyeGame
下一篇:UI集合層