在上篇文章 Unity3D熱更新之LuaFramework篇[01]--從零開始 中,我們了解了怎么獲得一個可用的LuaFramework框架。
本篇將我會先介紹一下如何配置Lua開發環境,然后分析在此框架中加載面板的流程,以及如何創建自己的面板。
1、配置Lua開發環境
有一點要說明的是,使用此種方式(ToLua+LuaFramework)做熱更新,則意味着你的大部分邏輯都需要改用Lua語言來編寫。
因此,開發前得先得配置好Lua開發環境。畢竟,工欲善其事,必先利其器。
環境配置大概分以下三個步驟:
1.安裝IntelliJ IDEA Community Edition 2018.2.4 x64
官網地址 http://www.jetbrains.com/idea/download/#section=windows
直接下載即可,下載 Community 版本,也就是社區版,免費的
2.下載Lua For Windows
https://github.com/rjpcomputing/luaforwindows/releases
下載最新的就行,然后安裝。
3.安裝emmylua插件
安裝插件有2種方法,可以直接搜插件庫安裝,或者下載好插件后本地加載。
以上安裝步驟均來自:三頁菌 的文章 最好用的lua編輯器--------emmylua使用匯總。
其文章極其詳細的介紹了如何搭建並配置一個好用的Lua開發環境,請自行參考。
2、Lua中是怎么加載一個面板的
在上一篇文章最后,我們運行框架,最終顯示了一個Lua腳本動態創建的面板,即PromptPanel,如圖2-1所示。
圖2-1
翻看框架的目錄結構,會在Assets/LuaFrame/Examples/Builds/Prompt目錄找到兩個預制體,PromptPanel和PromptItem,也就是這個面板的主體和獸人頭像,如圖2-2所示。
圖2-2
用上一節中安裝的IntelliJ IDEA打開工程目錄,在Controller目錄和View目錄會找到與PromptPanel密切相關的兩個文件PromptCtrl.lua、PromptPanel.lua,如圖2-3所示
圖2-3
由目錄名稱可知,此框架采用了一種MVC結構,用以對代碼功能做區分。XxxPanel負責頁面顯示邏輯,XxxCtrl負責事件處理,示例沒有給出明顯的Model層,讀者可以根據自身項目酌情添加。
繼續查看框架代碼,會在Logic/Game.lua中找到游戲的入口:Game.OnInitOK函數,見圖2-4。
圖2-4
在這個函數中,有3個重要邏輯:
1、初始化View
2、初始化Ctrl
3、啟動Ctrl
1、初始化View
初始化View就是調用InitViewPanels這個函數,InitViewPanels函數用於加載View目錄下定義的XxxPanel,在Game.lua的17行中可以看到定義。
function Game.InitViewPanels() for i = 1, #PanelNames do require ("View/"..tostring(PanelNames[i])) end endPanelName則是在LuaFramwwork/Lua/Common/define.lua的第7行中定義的,對應面板的名稱。
PanelNames = { "PromptPanel", "MessagePanel", }
2、初始化Ctrl
初始化Ctrl是指CtrlManager.Init();這句,可以在LuaFramwwork/Lua/Logic/CtrlManager.lua第9行中看到相關定義。這個函數中通過調用New函數創建了Ctrl的實例。
function CtrlManager.Init() logWarn("CtrlManager.Init----->>>"); ctrlList[CtrlNames.Prompt] = PromptCtrl.New(); ctrlList[CtrlNames.Message] = MessageCtrl.New(); return this; end
3、啟動Ctrl
啟動就是根據CtrlNames找到對應的Ctrl的實例,然后調用其Awake方法,見代碼:
local ctrl = CtrlManager.GetCtrl(CtrlNames.Prompt); if ctrl ~= nil and AppConst.ExampleMode == 1 then ctrl:Awake(); end
以上都是推測,
為了驗證猜測的對不對,我把CtrlManager.GetCtrl(CtrlNames.Prompt)這一句改為CtrlManager.GetCtrl(CtrlNames.Message),如果這次加載出來的是MessagePanel,則說明上述過程推斷正確。
....
改完后運行,發現加載的還是PromptPanel,難道確實是找錯地方了?
別急,這里還涉及另一個概念。
在熱更框架中,程序運行的並不是我們在LuaFramework/lua目錄下編寫的代碼,而是在Assets/StreamingAssets目錄下的打包后的代碼,見圖2-5。
圖2-5
那么有什么辦法讓我們剛剛改的代碼生效呢?
思路有兩個:
- 將們的寫的代碼打包到StreamAssets中;
- 讓程序直接運行打包前的代碼;
思路1的操作方法是:執行LuaFramework菜單下的Build XXX Resources菜單(見圖2-6),因為我現在的程序是運行在Windows平台,所以選擇Build Windows Resource。
圖2-6
點擊菜單,等待重新打包完成。打包結束后,能看到整個StreamingAssets目錄中的內容都更新了,在里邊可以找到message和prompt相關的資源,見圖2-7。
圖2-7
重新運行后,得到了想的結果,程序直接加載了MessagePanel面板,見圖2-8。
圖2-8
由此印證我們對整個面板流程的加載的推測分析。
關於思路2讓程序直接運行打包前的代碼,只需要關閉Lua的AssetBundle模式就好了。
找到LuaFramework/Scripts/ConstDefine/AppConst.cs文件,將LuaBundleMode = true;改為
LuaBundleMode = false;即可,見圖2-8,圖中是改過之后的。
圖2-8
LuaBundleMode 改為false之后,Lua代碼修改后就無需重新Build xxx Resources就能直接看到效果。
盡管思路1和思路2是二選一即可的,但為方便后邊的示例,這里要統一修改為false。
3、如何創建自己的面板
在上一步的分析中,我們得知創建一個面板需要先初始化View,再實例化Ctrl,然后調用Ctrl的Awake。這些都是代碼層面的,前提還有一個,我們需要一個XxxPanel預制體。
總結一下,如果要創建一個我們自己的面板,則需要如下步驟:
1、創建一個XxxPanel預制體
2、創建對應的XxxPanel腳本
3、創建對應的XxxCtrl腳本
4、添加CtrlNames及PanelNames
5、加載XxxCtrl
下面我將以FirstPanel為例進行演示。
1、創建FirstPanel預制體。
在Hierarchy面板中創建一個FirstPanel,並在LuaFramework目錄下新建CustomPrj/FirstTest目錄,將FirstPanel拖到此做成預制體,見圖3-1。
圖3-1
然后刪掉Hierarchy面板中的FirstPanel,因為后面我們會動態加載它。
2、創建FirstPanel.lua腳本。
在Lua/View目錄下創建一個FirstPanel的lua腳本,腳本結構參照MessageView編寫,如下:
View Codelocal transform; local gameObject; FirstPanel = {}; local this = FirstPanel; --啟動事件-- function FirstPanel.Awake(obj) gameObject = obj; transform = obj.transform; this.InitPanel(); logWarn("Awake lua--->>"..gameObject.name); end --初始化面板-- function FirstPanel.InitPanel() --這句要注釋掉,因為我們的FirstPanel中沒有按鈕 --this.btnClose = transform:FindChild("Button").gameObject; end --單擊事件-- function FirstPanel.OnDestroy() logWarn("OnDestroy---->>>"); end
注:lua腳本的創建方法是在IDEA中,選中目錄,右鍵->New->Lua File。
3、創建FirstCtrl.lua腳本。
在Lua/Controller目錄下創建一個FirsCtrl的lua腳本,腳本結構參照MessagCtrl編寫,如下:
View Code1 FirstCtrl = {}; 2 local this = FirstCtrl; 3 4 local message; 5 local transform; 6 local gameObject; 7 8 --構建函數-- 9 function FirstCtrl.New() 10 logWarn("FirstCtrl.New--->>"); 11 return this; 12 end 13 14 function FirstCtrl.Awake() 15 logWarn("FirstCtrl.Awake--->>"); 16 panelMgr:CreatePanel('First', this.OnCreate); 17 end 18 19 --啟動事件-- 20 function FirstCtrl.OnCreate(obj) 21 gameObject = obj; 22 23 message = gameObject:GetComponent('LuaBehaviour'); 24 25 --這句要注釋掉,因為我們的FirstPanel中沒有按鈕 26 --message:AddClick(MessagePanel.btnClose, this.OnClick); 27 28 logWarn("Start lua--->>"..gameObject.name); 29 end 30 31 --單擊事件-- 32 function FirstCtrl.OnClick(go) 33 destroy(gameObject); 34 end 35 36 --關閉事件-- 37 function FirstCtrl.Close() 38 panelMgr:ClosePanel(CtrlNames.Message); 39 end
4、添加CtrlNames及PanelNames
在Lua/Common找到define.lua,在CtrlNames中添加 First = "FirstCtrl",在PanelNames中添加"FirstPanel",如下:
CtrlNames = { Prompt = "PromptCtrl", Message = "MessageCtrl", First = "FirstCtrl" } PanelNames = { "PromptPanel", "MessagePanel", "FirstPanel" }
5、加載FirstCtrl
在Lua/Logic/Game.lua文件的Game.OnInitOK函數中,將CtrlManager.GetCtrl()的參數修改為我們剛剛添加的CtrlNames.First,如下所示:
CtrlManager.Init(); local ctrl = CtrlManager.GetCtrl(CtrlNames.First); if ctrl ~= nil and AppConst.ExampleMode == 1 then ctrl:Awake(); end
保存代碼並運行
..............
嗯,什么都沒加載出來。
好吧,我得承認,在學習這個框架的過程中,每走一步都是坑。
我就是在艱難的趟過這些坑來之后,才覺得有必要將這個過程記錄下來,才有了這一系列文章,希望對后來人有所幫助。
.............
為什么我們自己的創建的面板沒有加載呢?
查看日志發現,在"LuaFramework InitOK--->>>"日志輸出之前,PromptCtrl.New和MessageCtrl.New都被調用了一次,而我們新加的FirstCtrl卻沒有,見圖3-2。
圖3-2
應該是我們某些地方少加了調用。
查找后發現,確實有這樣一個地方。在Lua/Logic/CtrlManager.lua腳本的Init方法,對所有Ctrl的New方法進行了調用。
我們添加對FirstCtrl.New的調用,如下:
function CtrlManager.Init() logWarn("CtrlManager.Init----->>>"); ctrlList[CtrlNames.Prompt] = PromptCtrl.New(); ctrlList[CtrlNames.Message] = MessageCtrl.New(); ctrlList[CtrlNames.First] = FirstCtrl.New(); return this; end(其實第二節中我們發現了這個地方,本節中忘了將自己的代碼加進去)
然后再運行
.....
報錯了,說我們的FirstCtrl是一個nil value, 見圖3-3
圖3-3
經查,是在CtrlManager中,我們沒有加載對應的腳本,見圖3-4(圖中是已添加之后的)
圖3-4
再次運行
出現了更多的錯誤,見圖3-5
圖3-5
......
有沒有想崩潰的感覺,唉,我當初就是這么一步步過來的。
這次的錯誤是缺少first.unity3d.
這里的原因是,我們之前剛把Lua代碼AssetBundle模式關掉(設置為false),lua代碼不用AssetBundle模式了,但我們的資源(FirstPanel預制體)還 是使用的AssetBundle模式。
並且資源的AssetBundle模式好像無法關閉,因此需要對FirstPanel預制體進行打包操作。
操作如下:
1、找到LuaFramework/Editor/Packager.cs文件中的HandleExampleBundle方法(約160行左右),添加對FirstPanel預制體打包的代碼,包名為"first",如下所示:
/// <summary> /// 處理框架實例包 /// </summary> static void HandleExampleBundle() { string resPath = AppDataPath + "/" + AppConst.AssetDir + "/"; if (!Directory.Exists(resPath)) Directory.CreateDirectory(resPath); AddBuildMap("prompt" + AppConst.ExtName, "*.prefab", "Assets/LuaFramework/Examples/Builds/Prompt"); AddBuildMap("message" + AppConst.ExtName, "*.prefab", "Assets/LuaFramework/Examples/Builds/Message"); //打包我們新加的FirstPanel預制體 AddBuildMap("first" + AppConst.ExtName, "*.prefab", "Assets/LuaFramework/CustomPrj/FirstTest"); AddBuildMap("prompt_asset" + AppConst.ExtName, "*.png", "Assets/LuaFramework/Examples/Textures/Prompt"); AddBuildMap("shared_asset" + AppConst.ExtName, "*.png", "Assets/LuaFramework/Examples/Textures/Shared"); }
2、執行unity編輯器上方LuaFramework菜單中的Build Windows Resources菜單項,進行打包操作。打包完成后,可以在StreamingAssets目錄中看到first.unity3d文件。見圖3-6
圖3-6
再次運行,
這次終於得到了我們想要的結果,我們自己創建的面板FirstPanel,就這么加載出來了。
見圖3-7
圖3-7
真是太不容易了!
現在,將我們改錯的經過都加入到完整的步驟中,那么,加載一個我們自己創建的面板的完整步驟如下:
1、創建一個XxxPanel預制體
2、創建對應的XxxPanel腳本
3、創建對應的XxxCtrl腳本
4、添加CtrlNames及PanelNames
5、在CtrlManager中加入對XxxCtrl.New的調用,並在頭部require "XxxCtrl"
6、在Packager.cs文件中對XxxPanel預制體進行打包
7、在Game.lua加載XxxCtrl
后續寫模塊的時候都會按這個流程來。
后記
在本篇文章的第二節的寫作過程中,為什么我會用推測並驗證的寫法,而不是直接給出一個正確結論?第三節中,我為什么沒有直接給出正確的操作步驟,而是邊走邊改錯?
因為我希望本文能如實還原我學LuaFramework的過程,記錄每一個問題的發生條件,以及我解決問題的思路。
下一篇文章將會介紹如何加載非XXXPanel的預制體以及按鈕事件處理。