在游戲開發(fā)階段中提鸟,經(jīng)常需要小修小改,Unity提供了很方便的Scene窗口仅淑,讓開發(fā)者的修改馬上生效称勋,而不需要重新啟動游戲。
那么代碼需要怎么樣才能做到一修改就馬上生效涯竟,并且不需要重啟游戲呢赡鲜?
下面結合Slua來說一下代碼自動更新的方案(我使用的版本是slua-1.3.2空厌,較舊)
思路:
- 獲取Lua加載過哪些文件揩瞪,記錄下這些文件的文件名喷屋、全路徑和最后修改時間
- 定時檢測文件的最后修改時間密任,如果比記錄的最后修改時間還要大淹遵,則表示在加載后發(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);
來完成注冊
注意事項:
- 熱更新Lua代碼只是針對具體文件依次執(zhí)行l(wèi)uaL_loadbuffer和lua_pcall鳍侣,并不能更新代碼中的閉包
- 代碼中的局部變量會丟失,所以需要在代碼書寫時注意局部變量的保存(這里涉及Lua框架設計敛助,如setfenv测砂、module加匈、_ENV存璃,甚至面向對象)
- 可以通過增大CheckFilesOnce來加快檢測速度,但是這樣會影響到游戲運行時的性能
- 每次更新代碼后會在后臺輸出 “ *** Reload Lua File Successful : FileName ”