Unity開發(fā)時自動熱更新Lua代碼

在游戲開發(fā)階段中提鸟,經(jīng)常需要小修小改,Unity提供了很方便的Scene窗口仅淑,讓開發(fā)者的修改馬上生效称勋,而不需要重新啟動游戲。
那么代碼需要怎么樣才能做到一修改就馬上生效涯竟,并且不需要重啟游戲呢赡鲜?

下面結合Slua來說一下代碼自動更新的方案(我使用的版本是slua-1.3.2空厌,較舊)

思路:

  1. 獲取Lua加載過哪些文件揩瞪,記錄下這些文件的文件名喷屋、全路徑和最后修改時間
  2. 定時檢測文件的最后修改時間密任,如果比記錄的最后修改時間還要大淹遵,則表示在加載后發(fā)生過修改,然后重新加載它
代碼:
using UnityEngine;
#if UNITY_EDITOR    
using UnityEditor;
#endif
using System.Collections;
using System.Collections.Generic;
using System.IO;
using SLua;
using System;

namespace XS
{
    public class ImmediateLuaScript : MonoBehaviour
    {
        public class ScriptInfo
        {
            public string strFileName;
            public string strFileFullPath;
            public System.DateTime dtLastWrite;
        }

        List<ScriptInfo> m_loadedScripts = new List<ScriptInfo>();
        int m_nNextScriptIndex = 0;

        private static ImmediateLuaScript _Instance;
        public static ImmediateLuaScript Instance
        {
            get
            {
                if (_Instance == null)
                {
                    GameObject resMgr = GameObject.Find("_ImmediateLuaScript");
                    if (resMgr == null)
                    {
                        resMgr = new GameObject("_ImmediateLuaScript");
                        GameObject.DontDestroyOnLoad(resMgr);
                    }

                    _Instance = resMgr.AddComponent<ImmediateLuaScript>();
                }
                return _Instance;
            }
        }

#if UNITY_EDITOR
       // 可以通過外部控制ImmediateLuaScriptInEditor老玛,使自動更新暫团澹或恢復
       public static bool ImmediateLuaScriptInEditor = true;
       // 每幀檢查文件數(shù),可根據(jù)文件數(shù)量成正比
       public static int CheckFilesOnce = 5;

       ScriptInfo getScriptInfo(string strFileFullPath)
        {
            foreach (ScriptInfo info in m_loadedScripts)
            {   
                if(info.strFileFullPath == strFileFullPath)
                {
                    return info;
                }
            }
            return null;
        }

        ScriptInfo getOrCreateScriptInfo(string strFileName, string strFileFullPath)
        {
            ScriptInfo info = getScriptInfo(strFileFullPath);
            if (info != null) return info;

            info = new ScriptInfo();
            info.strFileName = strFileName;
            info.strFileFullPath = strFileFullPath;
            m_loadedScripts.Add(info);
            return info;
        }

        ScriptInfo getNextScriptInfo()
        {
            if (m_nNextScriptIndex >= m_loadedScripts.Count)
            {
                m_nNextScriptIndex = 0;
                return null;
            }

            return m_loadedScripts[m_nNextScriptIndex++];
        }

        string getFileFullPath(string strFileName)
        {
            string strFileFullPath;
            do
            {
                // 這里的代碼目錄路徑屬于自定義
                strFileFullPath = UnityEngine.Application.dataPath + "/../luaScript/" + strFileName + ".lua";
                if (System.IO.File.Exists(strFileFullPath))
                    break;
            } while (false);

            return strFileFullPath;
        }

        public void SetImmediateLuaScript(string strFileName)
        {
            if (!ImmediateLuaScriptInEditor) return;

            string strFileFullPath = getFileFullPath(strFileName);
            FileInfo fileInfo = new FileInfo(strFileFullPath);
            if(fileInfo.Exists)
            {
                ScriptInfo info = getOrCreateScriptInfo(strFileName, strFileFullPath);
                info.dtLastWrite = fileInfo.LastWriteTime;
            }
        }

        void checkScriptUpdate()
        {
            ScriptInfo info = getNextScriptInfo();
            if (info == null) return;

            FileInfo fileInfo = new FileInfo(info.strFileFullPath);
            if (info.dtLastWrite < fileInfo.LastWriteTime)
            {
                info.dtLastWrite = fileInfo.LastWriteTime;
                bool result = loadScriptFile(info.strFileName, info.strFileFullPath);
                if(!result)
                {
                    var L = LuaSvr.Instance.luaState.L;
                    string err = LuaDLL.lua_tostring(L, -1);
                    LuaDLL.lua_pop(L, 2);
                    throw new Exception(err);
                }
            }
        }

        void LateUpdate()
        {
            if (!ImmediateLuaScriptInEditor) return;
            for(int i=0;i<CheckFilesOnce; i++)
            {
                checkScriptUpdate();
            }
        }

        public static int newLoader(System.IntPtr L)
        {
            string fileName = LuaDLL.lua_tostring(L, 1);
            Instance.SetImmediateLuaScript(fileName);
            LuaObject.pushValue(L, true);
            LuaDLL.lua_pushnil(L);
            return 2;
        }

        // 注冊Lua腳本loader
        public void AddLuaLoaders(System.IntPtr L)
        {
            LuaState.pushcsfunction(L, newLoader);
            int loaderFunc = LuaDLL.lua_gettop(L);

            LuaDLL.lua_getglobal(L, "package");
#if LUA_5_3
            LuaDLL.lua_getfield(L, -1, "searchers");
#else
            LuaDLL.lua_getfield(L, -1, "loaders");
#endif
            int loaderTable = LuaDLL.lua_gettop(L);

            // Shift table elements right
            for (int e = LuaDLL.lua_rawlen(L, loaderTable) + 1; e > 2; e--)
            {
                LuaDLL.lua_rawgeti(L, loaderTable, e - 1);
                LuaDLL.lua_rawseti(L, loaderTable, e);
            }
            LuaDLL.lua_pushvalue(L, loaderFunc);
            LuaDLL.lua_rawseti(L, loaderTable, 2);
            LuaDLL.lua_settop(L, 0);
        }

        // 加載lua文件
        bool loadScriptFile(string strFileName, string strFileFullPath)
        {
            FileStream fs = new FileStream(strFileFullPath, FileMode.Open);
            long size = fs.Length;
            byte[] bytes = new byte[size];
            fs.Read(bytes, 0, bytes.Length);
            fs.Close();

            bytes = LuaState.CleanUTF8Bom(bytes);

            var L = LuaSvr.Instance.luaState.L;
            if (bytes != null)
            {
                var nOldTop = LuaDLL.lua_gettop(L);
                var err = LuaDLL.luaL_loadbuffer(L, bytes, bytes.Length, "@" + strFileFullPath);
                if (err != 0)
                {
                    string errstr = LuaDLL.lua_tostring(L, -1);
                    LuaDLL.lua_pop(L, 1);
                    LuaObject.error(L, errstr);
                    return false;
                }
                err = LuaDLL.lua_pcall(L, 0, 0, 0);
                if (err != 0)
                {
                    string errstr = LuaDLL.lua_tostring(L, -1);
                    LuaDLL.lua_pop(L, 1);
                    LuaObject.error(L, errstr);
                    return false;
                }
                LuaDLL.lua_settop(L, nOldTop);
                Debug.Log(" *** Reload Lua File Successful : " + strFileName);
                return true;
            }
            LuaObject.error(L, "Can't find {0}", strFileFullPath);
            return false;
        }

#endif

    }
}

使用:

在Slua啟動時薪前,在任何未加載Lua腳本前調(diào)用

    ImmediateLuaScript.Instance.AddLuaLoaders(luaState.L);

來完成注冊

注意事項:

  1. 熱更新Lua代碼只是針對具體文件依次執(zhí)行l(wèi)uaL_loadbuffer和lua_pcall鳍侣,并不能更新代碼中的閉包
  2. 代碼中的局部變量會丟失,所以需要在代碼書寫時注意局部變量的保存(這里涉及Lua框架設計敛助,如setfenv测砂、module加匈、_ENV存璃,甚至面向對象)
  3. 可以通過增大CheckFilesOnce來加快檢測速度,但是這樣會影響到游戲運行時的性能
  4. 每次更新代碼后會在后臺輸出 “ *** Reload Lua File Successful : FileName ”
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偎球,隨后出現(xiàn)的幾起案子淌友,更是在濱河造成了極大的恐慌,老刑警劉巖物独,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帚称,居然都是意外死亡官研,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門闯睹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戏羽,“玉大人,你說我怎么就攤上這事楼吃∈蓟ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵所刀,是天一觀的道長衙荐。 經(jīng)常有香客問我,道長浮创,這世上最難降的妖魔是什么忧吟? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮斩披,結果婚禮上溜族,老公的妹妹穿的比我還像新娘讹俊。我一直安慰自己,他們只是感情好煌抒,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布仍劈。 她就那樣靜靜地躺著,像睡著了一般寡壮。 火紅的嫁衣襯著肌膚如雪贩疙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天况既,我揣著相機與錄音这溅,去河邊找鬼。 笑死棒仍,一個胖子當著我的面吹牛悲靴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播莫其,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼癞尚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乱陡?” 一聲冷哼從身側響起浇揩,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛋褥,沒想到半個月后临燃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡烙心,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年膜廊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淫茵。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡爪瓜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匙瘪,到底是詐尸還是另有隱情铆铆,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布丹喻,位于F島的核電站薄货,受9級特大地震影響,放射性物質發(fā)生泄漏碍论。R本人自食惡果不足惜谅猾,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧税娜,春花似錦坐搔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弧岳,卻和暖如春凳忙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背禽炬。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工消略, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞎抛。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像却紧,于是被迫代替她去往敵國和親桐臊。 傳聞我的和親對象是個殘疾皇子澈驼,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355