1. 程式人生 > >Unity 熱更新之ULua 踩坑篇

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<stringLuaBase> 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~~~~~