Unity 熱更新之ULua 踩坑篇
Unity 的原生c#是無法在移動端上進行熱更新的,那麼如果線上釋出遇到重大閃退事故的話,那麼就不可以通過遊戲內的熱更新進行bug修復,只能重新提交版本,而往往在提交版本到釋出的時間內,必然有玩家遇到這種問題,導致流失的,對於團隊來說,這個可是很嚴重的。
所以我google了下,目前已經有開發者實現了這一功能,有c#Light ,ULua,Nlua等,lua在cocos上可以說是非常成功的,與C/C++強大的互動能力,小巧高速的能力,在C/C++上體現的非常好。
最近開始搞Unity,學習了下ULua(ULua,資料),撇開Luajit
64位的坑。首先,開啟ulua_v1.08.unitypackage,匯入Ulua,如圖:
uLua資料夾中 包含一些例子,還有LuaInterface的文件,可以學習學習。
匯入成功之後,我們新建一個資料夾Scripts,新建一個Lua檔案
,然後在子資料夾global新建一個c#檔案,之後為了在c#中呼叫LuaEnterance.lua這個檔案,得在c#中加入程式碼,同時引入名稱空間usingLuaInterface;這樣才能呼叫LuaScriptMgr,這裡建議將LuaScriptMgr物件宣告位一個類成員。然後編譯一下,沒有問題!然後執行程式,就發現了第一個坑。
void Start() { mgr = new LuaScriptMgr (); mgr.Start (); mgr.DoFile ("LuaEnterance.lua"); }
執行doFile函式的時候獲取檔案的路徑通過
LuaDLL.lua_pushstdcallcfunction(L,tracebackFunction);
int oldTop=LuaDLL.lua_gettop(L);
// Load with Unity3D resources
byte[] text = LuaStatic.Load(fileName);
用的是deletage,類似於c++的函式指標
public delegate byte[] ReadLuaFile(string name); public static class LuaStatic { public static ReadLuaFile Load = null; //private static int trace = 0; static LuaStatic() { Load = DefaultLoader; } static byte[] DefaultLoader(string name) { byte[] str = null; string path = Util.LuaPath(name); using (FileStream file = new FileStream(path, FileMode.Open)) { str = new byte[(int)file.Length]; file.Read(str, 0, str.Length); file.Close(); } return str; }
然後為了獲取準確路徑,呼叫Util.LuaPath,
/// <summary>
/// 取得Lua路徑
/// </summary>
public static string LuaPath(string name) {
string path = Application.dataPath + "/";
string lowerName = name.ToLower();
if (lowerName.EndsWith(".lua")) {
return path + "lua/" + name;
}
return path + "lua/" + name + ".lua";
}
最後我發現doFile總是會到lua資料夾去找lua檔案,這也太不自由了。當然我想到了其他可能性,或許作者為ulua打包做了處理,lua資料夾的檔案有特別的好處?或者跟unity的機制有些關係?目前尚不清楚。
不考慮這些情況,我們可以簡單做個處理。
void Start()
{
mgr = new LuaScriptMgr ();
mgr.Start ();
mgr.DoFile (ReviseLuaPath("LuaEnterance.lua"));
}
public void DoLuaFile(string filepath)
{
if (mgr != null)
mgr.DoFile (ReviseLuaPath (filepath));
else
Debug.Log ("DoLuaFile ERROR! Plz create LuaScriptMgr First");
}
public string ReviseLuaPath(string path)
{
return "../Scripts/" + path;
}
然後執行,便發現成功了。。。
之後進行進行下一步,在c#中執行lua中函式,首先在lua檔案中定義一個函式
然後在c#中獲取它
private LuaFunction funcUpdate ;
void Start()
{
mgr = new LuaScriptMgr ();
mgr.Start ();
mgr.DoFile (ReviseLuaPath("LuaEnterance.lua"));
funcUpdate = mgr.GetLuaFunction ("Update");
}
然後在update中執行 void Update()
{
if (mgr != null)
{
funcUpdate.Call (Time.deltaTime);
}
}
結果發現什麼都沒有輸出。。。醉了醉了。遇到問題那就查!
推測Update這個函式沒有獲取到。看一下GetLuaFunction
//會快取LuaFunction
public LuaFunction GetLuaFunction(string name)
{
LuaBase func = null;
if (!dict.TryGetValue(name, out func))
{
IntPtr L = lua.L;
int oldTop = LuaDLL.lua_gettop(L);
if (PushLuaFunction(L, name))
{
int reference = LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);
func = new LuaFunction(reference, lua);
func.name = name;
dict.Add(name, func);
}
else
{
Debuger.LogWarning("Lua function {0} not exists", name);
}
LuaDLL.lua_settop(L, oldTop);
}
else
{
func.AddRef();
}
return func as LuaFunction;
}
每次獲取快取的lua函式,會嘗試從Dictionary<string, LuaBase> dict中獲取,然後這個key則是根據你傳入的name而定的,那麼問題就來了,不同檔案下的同名函式怎麼辦?因為有些函式是Ulua內建的,都是在_G下的,有時候一不小心就可能命名一致,這裡的問題也是因為這個導致的,ulua中自帶一個Main.lua的檔案,這個lua檔案中有一個同名函式Update!!所以我們GetLuaFunction實際獲得是這個函式!,因為其先被呼叫,並存在了dict中!LOOK! LuaScriptMgr中有這麼一段
public void Start()
{
OnBundleLoaded();
}
void OnBundleLoaded()
{
DoFile("Golbal.lua");
unpackVec3 = GetLuaFunction("Vector3.Get");
unpackVec2 = GetLuaFunction("Vector2.Get");
unpackVec4 = GetLuaFunction("Vector4.Get");
unpackQuat = GetLuaFunction("Quaternion.Get");
unpackColor = GetLuaFunction("Color.Get");
unpackRay = GetLuaFunction("Ray.Get");
packVec3 = GetLuaFunction("Vector3.New");
packVec2 = GetLuaFunction("Vector2.New");
packVec4 = GetLuaFunction("Vector4.New");
packQuat = GetLuaFunction("Quaternion.New");
packRaycastHit = GetLuaFunction("Raycast.New");
packColor = GetLuaFunction("Color.New");
packRay = GetLuaFunction("Ray.New");
packTouch = GetLuaFunction("Touch.New");
#if !MULTI_STATE
traceback = GetLuaFunction("traceback");
#endif
DoFile("Main.lua");
CallLuaFunction("Main");
updateFunc = GetLuaFunction("Update");
lateUpdateFunc = GetLuaFunction("LateUpdate");
fixedUpdateFunc = GetLuaFunction("FixedUpdate");
levelLoaded = GetLuaFunction("OnLevelWasLoaded");
}
它將一些基本的ulua庫檔案載入了,同時也運行了Main.lua,所以導致了這個錯誤。
如何解決?可以通過統一的命名規範避免這個問題。曾經還想過根據dofile的name來為getluafunction開航,不過也是有問題的,就是require ,因為lua的require做的差不多也是dofile乾的事情,這樣就定位不準確了,容易出問題,所以放棄了,感覺還是規範更好些。
後來本人測試了下ulua協程,結果發現也是有問題,報錯。紅叉叉的看著真是不舒服。
先寫一段coroutine的測試程式碼。發現在wait這裡斷掉了。 = =。
改改改!修修修!
coroutine.start 會自動將傳入的函式,作為coroutine.create的引數建立一個新的協程,並立刻resume執行,進入到Test函式,輸出 "a simple coroutine test",然後wait ,依靠CoTimer,計算時間,這裡需要做一個處理:在c#中執行
void Update()
{
if (mgr != null)
{
mgr.Update();
}
}
void LateUpdate()
{
if (mgr != null)
{
mgr.LateUpate();
}
}
LateUpdate目的為的是執行CoUpdateBeat() ,這個函式是放在Main中的,不然cotimer不會更新,
Update目的是為了設定deltatime,不然lua中的deltatime會一直是0,影響定時器。
然後修改幾處地方,在functions.lua中加入一個函式
看一下CoTimer
start的時候依賴的是CoUpdateBeat,而CoUpdateBeat是個Event,而Add正是在Event中宣告的,並呼叫了functor,然後修改
中的44行,利用剛才定義的handler讓xpacall,這樣就將所有的有參函式,都統一變為了無形參(忽略隱藏引數self)函式,更重要的是解決了cfunc與luafunc的呼叫問題,可以參考quick-cocos2dx的做法。
再次修改
同樣用handler去構造coTimer,以上為的是解決CoTimer:Update的self丟失問題。
最後執行!!!
這裡的時間誤差是因為lua中Time用的是os.clock,而wait則是根據unity來的,準確的說應該是根據幀率來的,1s在unity中已經走完,所以根據unity,這樣是麼問題的。
OVER~~~~~