天天看點

小眼遊戲架構:UI篇:三層架構(UI層)

上一篇我們分析了UI的架構原則以及為什麼要這麼架構的一些原因,這篇會具體實作架構的方方面面,東西會有點多。我輩求道,豈能求快!

為了避免架構的幹擾我們先來配置一下開發環境。

LuaFrameWork的啟動是在:

小眼遊戲架構:UI篇:三層架構(UI層)

加載的相關可以看這裡(加載)

上面主要做的就是去除和我們現在沒有聯系的子產品,避免一些問題。

現在開始分析具體流程。

經過前一篇的分析我們可以總結出:

UI管理層(UIManager):加載、打開、關閉、解除安裝。管理UI集合的自動打開和關閉,以及對外的接口。

UI集合層(UIBaseCollect):加載、打開、關閉、解除安裝。管理多個UI。

UI層(UIBaseView):加載、打開、關閉、解除安裝。管理UI本身的生命周期。

其實流程都是類似的,一層層的管理下去。

流程總覽

小眼遊戲架構:UI篇:三層架構(UI層)

上面的圖是我們的三層架構流程總覽圖。

按列劃分:

第一列:UI管理層(UIManager)相關操作;

第二列:UI集合層(UIBaseColelct)相關操作;

第三列:UI層(UIBaseView)相關操作;

實作代表同步,虛線代表異步(不在同一幀執行)。

我們采取自下而上的講解方式。那麼先開始UI層吧。

UI層

一般的UI流程肯定是有以下流程:加載、打開、關閉、解除安裝。

在分析詳細四個流程前,我們先确認下Prefab的制作規範,這樣對于講解後面的流程是有幫助的。

Prefab的制作要求:

小眼遊戲架構:UI篇:三層架構(UI層)

上圖有兩個部分:編輯器模式下的Prefab顯示,和運作時的Prefab顯示

這樣一對比會發現這兩個圖有點對不上,是的,運作時和編輯器模式下的顯示不是一樣的。

  1. 先看編輯器模式:

    綠色是UIRoot,黃色是UI相機,紅色是Prefab的主題結構。

  2. 再看運作時部分:

    紅色是代表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層)
  1. 打開參數:有時候我們打開一個UI需要特定的一些資料去做顯示,比如:UI上有多個頁簽,我現在想打開UI前就決定顯示到哪個頁簽。我們經常會自己寫一個管理器去管理UI的行為,這并沒有問題,但是有時候我們會将資料放到管理器中,然後UI調用管理器中的資料,這樣的設計并不好,與UI相關的資料最好通過參數的形式傳進去,這就是我們打開參數設定的目的。
  2. 加載prefab是異步的:是以需要注意異步的問題,解除安裝的時候,在請求加載中需要取消加載。
  3. 綁定控件:這時一種省時省力的做法,後面會有單獨的一個章節來講解,這裡就不說了。
  4. 注冊UI事件 :UI的監聽事件不會動态的改變,是以放在加載裡面注冊就可以了。我們處理事件的方式是:隻有顯示的UI才能收到監聽事件的回調。你可能會問不顯示的UI就收不到?是的,因為UI在顯示時會重新重新整理頁面,是以就不需要在隐藏的時候更新了。
  5. 加載完成:加載完成之後會通知子類,這樣子類就能知道加載已經完成,可以開始自己的操作了。

事件設計

既然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層)
  1. 計算UI層級:想讓UI呈現出前後的關系,我們需要改變UIPanel的depth值,我們在制作Prefab的過程中也會把UIPanel當成一個頁面的容器。
  2. 最後顯示的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設計如下:

小眼遊戲架構:UI篇:三層架構(UI層)

3. atals的加載:這裡的atlas包括圖集、圖檔。Prefab包括:自身的序列化資訊和引用的資訊。Prefab本身隻是序列化資料,不大,但是每次加載解析卻浪費了時間,尤其是Prefab的結構越複雜,需要的時間就越多。既然Prefab本身不大那我們保證Prefab在不切換場景前不解除安裝,隻解除安裝綁定在Prefab上的圖集和圖檔,這樣記憶體也沒有增加多少,再次打開這個界面的時候會很快。

4. 顯示完成:Prefab顯示之後會通知子類,Prefab已經顯示完成,可以開始自己的操作了。自己的操作:根據資料重新整理頁面顯示。

UI關閉

先看圖

小眼遊戲架構:UI篇:三層架構(UI層)

關閉UI的流程和打開UI的流程剛還是相反的,是以就不說了。

UI解除安裝
小眼遊戲架構:UI篇:三層架構(UI層)

解除安裝UI流程和加載UI流程也是相反的,是以就不說了。

Lua類的設計:

既然我們采取類的模式,那麼lua得提供類的功能,lua實作類的邏輯很巧妙,lua的table中有一個重要特性,可以給目前的table設定一個元表(也是一個table),這樣在目前table中找不到的屬性和方法可以去元表table中去尋找,這個機制不就和類的方式是類似的麼?這樣就能輕松實作類了。

小眼遊戲架構:UI篇:三層架構(UI層)

基類UIBaseVIew設計:

小眼遊戲架構:UI篇:三層架構(UI層)

建立一個UIBaseView基類(所有UI的父類),ctor是構造函數。

使用者的子類設計:

小眼遊戲架構:UI篇:三層架構(UI層)

UIPanel_MessageBox繼承自UIBaseView,這樣UIPanel_MessageBox也就擁有和UIBaseView一樣的生命周期。

這樣我們UI層(UIBaseView)就設計完成了。

項目位址:https://github.com/xiaoyanxiansheng/SmallEyeGame

下一篇:UI集合層

繼續閱讀