创建一个事件监听类,并把它添加到wrap中,让lua可以访问到它。
using UnityEngine; using System; using System.Collections; using LuaInterface; public sealed class TestEventListener : MonoBehaviour { public delegate void VoidDelegate(GameObject go); public delegate void OnClick(GameObject go); public OnClick onClick = delegate { }; public event OnClick onClickEvent = delegate { }; [NoToLuaAttribute] public void OnClickEvent(GameObject go) { onClickEvent(go); } }
在lua中对onClick和onClickEvent进行添加和删除事件
--listener为上面的那个类 function DoClick1(go) print('click1 gameObject is '..go.name) end function AddClick1(listener) --给委托添加事件 if listener.onClick then listener.onClick = listener.onClick + DoClick1 else listener.onClick = DoClick1 end end function RemoveClick1(listener) --给委托移除事件 if listener.onClick then listener.onClick = listener.onClick - DoClick1 else print('empty delegate') end end function TestEvent() print('this is a event') end function AddEvent(listener)--事件添加事件 listener.onClickEvent = listener.onClickEvent + TestEvent end function RemoveEvent(listener)--事件移除事件 listener.onClickEvent = listener.onClickEvent - TestEvent end local t = {name = 'byself'} function t:TestSelffunc() print('callback with self: '..self.name) end function AddSelfClick(listener) if listener.onClick then listener.onClick = listener.onClick + TestEventListener.OnClick(t.TestSelffunc, t) else listener.onClick = TestEventListener.OnClick(t.TestSelffunc, t) end end function RemoveSelfClick(listener) if listener.onClick then listener.onClick = listener.onClick - TestEventListener.OnClick(t.TestSelffunc, t) else print('empty delegate') end end
c#中获取lua方法为委托添加或删除事件
LuaFunction func = state.GetFunction("DoClick1"); TestEventListener.OnClick onClick = (TestEventListener.OnClick)DelegateTraits<TestEventListener.OnClick>.Create(func); listener.onClick += onClick; LuaFunction func = state.GetFunction("DoClick1"); listener.onClick = (TestEventListener.OnClick)DelegateFactory.RemoveDelegate(listener.onClick, func); func.Dispose(); func = null;
在c#中可以通过强转,将枚举和整形之间互相转换。那么在Lua中怎么转换呢?
space = UnityEngine.Space.World --Space是一个枚举 space:ToInt() --转换为数值 UnityEngine.Space.IntToEnum(0) --数值转换为枚举
using UnityEngine; using System.Collections.Generic; using LuaInterface; public class AccessingLuaVariables : MonoBehaviour { private string luaScript = @" tab = {1,2,3,4,5} tab.name = 'tab' tab.map = {} tab.map.name = 'map' meta = {name = 'meta'} setmetatable(tab,meta) "; void Start () { new LuaResLoader(); LuaState lua = new LuaState(); lua.Start(); lua.DoString(luaScript); LuaTable tab = lua.GetTable("tab"); Debug.LogError(tab[1]); //1 Debug.LogError(tab["name"]); //tab LuaTable map = tab["map"] as LuaTable; Debug.LogError(map["name"]); //map LuaTable meta = tab.GetMetaTable(); Debug.LogError(meta["name"]); //meta table.Dispose(); lua.CheckTop(); lua.Dispose(); } }
在编辑器中,实例化LuaState的时候就会将LuaConst.cs中定义的luaDir和toluaDir添加到lua脚本搜索目录中,所以这两个脚本一定得正确,在编辑器中,运行lua脚本就会使用这两个目录下的,也方便修改后立即看到效果。
void InitLuaPath()//LuaState构造方法中调用的其中一个方法 { InitPackagePath(); if (!LuaFileUtils.Instance.beZip) { #if UNITY_EDITOR if (!Directory.Exists(LuaConst.luaDir)) { string msg = string.Format("luaDir path not exists: {0}, configer it in LuaConst.cs", LuaConst.luaDir); throw new LuaException(msg); } if (!Directory.Exists(LuaConst.toluaDir)) { string msg = string.Format("toluaDir path not exists: {0}, configer it in LuaConst.cs", LuaConst.toluaDir); throw new LuaException(msg); } AddSearchPath(LuaConst.toluaDir); AddSearchPath(LuaConst.luaDir); #endif if (LuaFileUtils.Instance.GetType() == typeof(LuaFileUtils)) { AddSearchPath(LuaConst.luaResDir); } } }
当在手机上时,lua后缀极其的被排斥,放在哪里都不太行,所以需要打包成assetbundle,从而能够真正的热更新。由于unity中不认.lua文件,所以在打包的时候复制lua脚本到一个临时目录并添加后缀.bytes,然后根据文件夹名字标志assetbundle的名字,tolua中已经有了实现,我删除了打包的步骤,只用来标志名字了,因为我项目已经有了打包的方法了。
//ToLuaMenu.cs [MenuItem("Lua/Build bundle files not jit", false, 55)] public static void BuildNotJitBundles() { ClearAllLuaFiles(); CreateStreamDir(GetOS()); #if UNITY_4_6 || UNITY_4_7 string tempDir = CreateStreamDir("Lua"); #else string tempDir = Application.dataPath + "/temp/Lua"; if (!File.Exists(tempDir)) { Directory.CreateDirectory(tempDir); } #endif CopyLuaBytesFiles(LuaConst.luaDir, tempDir); CopyLuaBytesFiles(LuaConst.toluaDir, tempDir); AssetDatabase.Refresh(); List<string> dirs = new List<string>(); GetAllDirs(tempDir, dirs); for (int i = 0; i < dirs.Count; i++) { string str = dirs[i].Remove(0, tempDir.Length); BuildLuaBundle(str.Replace('\\', '/'), "Assets/temp/Lua"); } BuildLuaBundle(null, "Assets/temp/Lua"); AssetDatabase.SaveAssets(); //只用来标志BUndle的名字就行了 //string output = string.Format("{0}/{1}", Application.streamingAssetsPath, GetOS()); //BuildPipeline.BuildAssetBundles(output, BuildAssetBundleOptions.DeterministicAssetBundle, EditorUserBuildSettings.activeBuildTarget); //Directory.Delete(Application.dataPath + "/temp/", true); AssetDatabase.Refresh(); } static void CopyLuaBytesFiles(string sourceDir, string destDir, bool appendext = true, string searchPattern = "*.lua", SearchOption option = SearchOption.AllDirectories) { if (!Directory.Exists(sourceDir)) { return; } string[] files = Directory.GetFiles(sourceDir, searchPattern, option); int len = sourceDir.Length; if (sourceDir[len - 1] == '/' || sourceDir[len - 1] == '\\') { --len; } for (int i = 0; i < files.Length; i++) { string str = files[i].Remove(0, len); string dest = destDir + "/" + str; if (appendext) dest += ".bytes"; string dir = Path.GetDirectoryName(dest); Directory.CreateDirectory(dir); File.Copy(files[i], dest, true); } } static void GetAllDirs(string dir, List<string> list) { string[] dirs = Directory.GetDirectories(dir); list.AddRange(dirs); for (int i = 0; i < dirs.Length; i++) { GetAllDirs(dirs[i], list); } } static void BuildLuaBundle(string subDir, string sourceDir) { string[] files = Directory.GetFiles(sourceDir + subDir, "*.bytes"); string bundleName = subDir == null ? "lua": "lua" + subDir.Replace('/', '_') ; bundleName = "scripts/" + bundleName.ToLower(); #if UNITY_4_6 || UNITY_4_7 List<Object> list = new List<Object>(); for (int i = 0; i < files.Length; i++) { Object obj = AssetDatabase.LoadMainAssetAtPath(files[i]); list.Add(obj); } BuildAssetBundleOptions options = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets | BuildAssetBundleOptions.DeterministicAssetBundle; if (files.Length > 0) { string output = string.Format("{0}/{1}/" + bundleName, Application.streamingAssetsPath, GetOS()); File.Delete(output); BuildPipeline.BuildAssetBundle(null, list.ToArray(), output, options, EditorUserBuildSettings.activeBuildTarget); } #else string bundleVariant = com.Bowen.Game.Lobby.GlobalConst.Res.Phone_BundleFileExt.Replace(".", ""); for (int i = 0; i < files.Length; i++) { AssetImporter importer = AssetImporter.GetAtPath(files[i]); if (importer) { importer.assetBundleName = bundleName; importer.assetBundleVariant = bundleVariant; } } #endif }
好了,现在lua脚本都复制到了temp/Lua下面了,并且lua脚本根据所在的子文件夹标志了一个包名字,只需要调用Unity提供的打包方法就可以了,写入自己框架中的资源文件中。
public static bool BuildAssetBundle(string path, BuildTarget target, List<GameConfig> games, ref List<AssetBundleInfo> abList) { try { var manifest = BuildAssetBundle(path, target, games); if (manifest == null) { Debug.Log("null manifest"); return true; } string[] assetBundles = manifest.GetAllAssetBundles(); foreach (var ab in assetBundles) { string name = ab.Replace(Path.GetExtension(ab), ""); var data_type = new AssetBundleInfo(); //data_type.ID = ""; data_type.Name = name; data_type.FileName = ab; data_type.Hash = MD5Util.GetFileMD5(path + ab); data_type.Version = "0"; var oldItem = abList.Find(item => { return string.Compare(item.Name, name, true) == 0; }); if (oldItem != null) { abList.Remove(oldItem); if (string.Compare(oldItem.Hash, data_type.Hash, true) != 0) { data_type.UpdateVersion(); } } Debug.Log("add builded ab:" + data_type + " name:" + name); abList.Add(data_type); } Debug.Log("assetbundle build finish"); return true; } catch (Exception e) { Debug.LogException(e); return false; } } private static AssetBundleManifest BuildAssetBundle(string path, BuildTarget target, List<GameConfig> games) { var builds = new List<AssetBundleBuild>(); var abs = AssetDatabase.GetAllAssetBundleNames(); foreach (var ab in abs) { var build = new AssetBundleBuild(); build.assetBundleName = Path.GetDirectoryName(ab) + "/" + Path.GetFileNameWithoutExtension(ab); build.assetNames = AssetDatabase.GetAssetPathsFromAssetBundle(ab); build.assetBundleVariant = Path.GetExtension(ab).Replace(".", ""); builds.Add(build); Debug.Log("name:" + build.assetBundleName + " variant:" + build.assetBundleVariant + " ab:" + ab); } Debug.Log("Build scene for target:" + target + " by path:" + path); if (EditorUserBuildSettings.activeBuildTarget != target) { EditorUserBuildSettings.SwitchActiveBuildTarget(target); } return BuildPipeline.BuildAssetBundles( path, builds.ToArray(), BuildAssetBundleOptions.DeterministicAssetBundle/* | BuildAssetBundleOptions.ForceRebuildAssetBundle*/, target ); }
在框架下载后,读取assetbundle包中的lua脚本
//LuaManager.cs 在资源下载完成后,调用初始化方法 public void InitStart() { InitLuaBundle(); this.luaState.Start(); //启动LUAVM //this.StartMain(); this.StartLooper(); } /// <summary> /// 初始化LuaBundle,加载下载的Bundle到一个字典中缓存起来 /// </summary> void InitLuaBundle() { if (loader.beZip) { loader.AddBundle("lua.mp"); loader.AddBundle("lua_math.mp"); loader.AddBundle("lua_system.mp"); loader.AddBundle("lua_system_reflection.mp"); loader.AddBundle("lua_unityengine.mp"); loader.AddBundle("lua_common.mp"); loader.AddBundle("lua_logic.mp"); loader.AddBundle("lua_view.mp"); loader.AddBundle("lua_controller.mp"); loader.AddBundle("lua_misc.mp"); loader.AddBundle("lua_protobuf.mp"); loader.AddBundle("lua_3rd_cjson.mp"); loader.AddBundle("lua_3rd_luabitop.mp"); loader.AddBundle("lua_3rd_pbc.mp"); loader.AddBundle("lua_3rd_pblua.mp"); loader.AddBundle("lua_3rd_sproto.mp"); } } //这里是loader的类 LuaLoader.cs using UnityEngine; using System.Collections; using System.IO; using LuaInterface; /// <summary> /// 集成自LuaFileUtils,重写里面的ReadFile,继承LuaFileUtils /// </summary> public class LuaLoader : LuaFileUtils { // Use this for initialization public LuaLoader(bool isZip = false) { instance = this; beZip = isZip; } /// <summary> /// 添加打入Lua代码的AssetBundle /// </summary> /// <param name="bundle"></param> public void AddBundle(string bundleName) { string url = ""; #if !UNITY_EDITOR //lua bundle资源下载的目录 url = LuaConst.luaResDir + "/" + bundleName.ToLower(); #else url = LuaConst.luaWinTestResDir + "/" + bundleName.ToLower(); #endif if (File.Exists(url)) { AssetBundle bundle = AssetBundle.LoadFromFile(url); if (bundle != null) { bundleName = bundleName.Replace("lua/", "").Replace(".mp", ""); base.AddSearchBundle(bundleName.ToLower(), bundle); } } } /// <summary> /// 当LuaVM加载Lua文件的时候,这里就会被调用, /// 用户可以自定义加载行为,只要返回byte[]即可。 /// </summary> /// <param name="fileName"></param> /// <returns></returns> public override byte[] ReadFile(string fileName) { return base.ReadFile(fileName); } }
好了LuaFileUtils里面有个zipMao字典有了所有的lua的AssetBundle包,当我们想调用lua脚本的时候,我们可以自由的去读取了,因为我们有做映射,就是不知道哪个lua脚本在哪个包中,所以我就直接遍历所有的包了,先实现再优化了。
byte[] ReadZipFile(string fileName) { byte[] buffer = null; string zipName = null; using (CString.Block()) { CString sb = CString.Alloc(256); sb.Append("lua"); int pos = fileName.LastIndexOf('/'); if (pos > 0) { sb.Append("_"); sb.Append(fileName, 0, pos).ToLower().Replace('/', '_'); fileName = fileName.Substring(pos + 1); } if (!fileName.EndsWith(".lua")) { fileName += ".lua"; } #if UNITY_5 || UNITY_5_3_OR_NEWER fileName += ".bytes"; #endif zipName = sb.ToString(); //这里要优化的 现在遍历查找 foreach (AssetBundle bundle in zipMap.Values) { #if UNITY_4_6 || UNITY_4_7 TextAsset luaCode = bundle.Load(fileName, typeof(TextAsset)) as TextAsset; #else TextAsset luaCode = bundle.LoadAsset<TextAsset>(fileName); #endif if (luaCode != null) { buffer = luaCode.bytes; Resources.UnloadAsset(luaCode); break; } } } return buffer; }
区分是否读取包的lua还是LuaConst.cs中那两个目录的lua,是LuaFileUtils.cs中一个叫beZip的参数,设置为true表示加载的是资源包的。
UNity5.6+
官方的,在这里下载https://github.com/Unity-Technologies/AssetBundles-Browser
导入到Editor文件夹就行了,使用方法:Window->AssetBundle Browser,其他摸索
IEnumerator Start () { UnityWebRequest request = UnityWebRequest.GetAssetBundle (baseUrl + "AssetBundle",0); yield return request.SendWebRequest (); AssetBundle ab = DownloadHandlerAssetBundle.GetContent (request); AssetBundleManifest abm = ab.LoadAsset<AssetBundleManifest> ("AssetBundleManifest"); foreach (var a in abm.GetAllDependencies("cub")) { using (UnityWebRequest r = UnityWebRequest.GetAssetBundle (baseUrl + a, 0)) { yield return r.SendWebRequest (); DownloadHandlerAssetBundle.GetContent (r);//加载到依赖包到内存中 } } //依赖包加载完成加载完成后,实例化Cube才不会丢失材质和贴图 using (UnityWebRequest r = UnityWebRequest.GetAssetBundle (baseUrl + "cub")) { yield return r.SendWebRequest (); AssetBundle assb = DownloadHandlerAssetBundle.GetContent (r); GameObject go = assb.LoadAsset<GameObject> ("Cube"); Instantiate (go); } }
还记得那个怎么都有的AssetBundle文件吗,没错,这里记载了所有你打包出来的AssetBundle的依赖
加载名字叫做那个就是那个AssetBundle的文件,获取Manifest,通过GetAllDependencies()获取cub的依赖(字符串,包名),因为等下我要加载cub里面的Cube
在使用BuildPipeline.BuildAssetBundles()的第二个参数可以指定构建的压缩选项
BuildAssetBundleOptions.None ->使用的是LZMA算法,压缩包更小,但是加载时需要整体解压。下载后,就会使用LZ4重新压缩。
BuildAssetBundleOptions.ChunkBasedCompression ->使用的是LZ4的压缩方法,包较小且有较快的加载速度
BuildAssetBundleOptions.UncompressedAssetBundle ->不压缩,包大,加载速度最快
AssetBundle的打包会自动识别依赖,所以可以将一些公用的资源(贴图,材质等)打包到一个包上(共享包),其他的资源在分类打包,避免贴图等共享(多次使用的资源)被打包了多次,增加总体包的大小
在指定资源的assetbundle的名字的时候,可以进行目录分类,只需要使用路径的形式来命名,例如"scene/texture",这样texture这个AssetBundle就在scene的目录下
在lua代码中,肯定要使用到Unity中内置的类和自己自定义的类,在Assets/Editor/Custom目录下有一个CustomSettings.cs这里配置了lua中要使用到的类,也只有在这里配置了之后,ToLua才会把这些类映射到lua中,这样才可以在lua中使用这些类。
例如:
_GT(typeof(Text)), //UGUI的Text
TextUI = TextUI:GetComponent(typeof(UnityEngine.UI.Text)) --加上命名空间