热更新原理:
AssetBundle包可以从服务器上下载然后加载进游戏,所以我们需要热更的资源只要丢在服务器上,等进入游戏时再进行下载就可以了。但AssetBundle只能打包资源,不能打包C#代码(可能出于安全和方便管理),因此我们需要考虑代码热更的方式。
现在主流的一些热更方式:
ToLua/XLua
ILRuntime
HybridCLR
资源管理 AssetBundle 1 打包面板信息 1.1 AB包的压缩格式
LZMA
默认压缩方式,能使压缩文件达到最小。但由于其压缩方式是基于整个AssetBundle的数据流实现的,因此在对其解压时需将整个流进行解压。
LZ4
一种基于块的压缩算法,压缩文件比LZMA大,但在解压时只需要解压包含所请求资源的字节的块。
No Compression
1.2 AB包生成的文件
对应资源的文件
资源AB包
manifest文件
记录了AB包的文件信息
加载时可以用来提供资源信息、资源依赖关系等等
关键文件(和定义的output文件同名)
主包
manifest文件:记录AB包之间的依赖信息
2 加载AB包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 using System.Collections;using System.Collections.Generic;using TMPro.EditorUtilities;using Unity.VisualScripting;using UnityEngine;using UnityEngine.Events;public class ABLoadManager : SingletonMono <ABLoadManager >{ private string PathUrl { get { return Application.streamingAssetsPath + "/" ; } } private string MainABName { get { #if UNITY_IOS return "IOS" ; #elif UNITY_ANDROID return "Android" ; #else return "PC" ; #endif } } private AssetBundle mainAB = null ; private AssetBundleManifest mainABManifest = null ; private Dictionary<string , AssetBundle> abDict = new Dictionary<string , AssetBundle>(); public T LoadRes <T >(string abName,string resName ) where T : Object { if (!abDict.ContainsKey(abName)) { AddToABDict(abName); } return abDict[abName].LoadAsset<T>(resName); } public void LoadResAsync <T >(string abName,string resName,UnityAction<T> action ) where T : Object { if (!abDict.ContainsKey(abName)) { AddToABDict(abName); } StartCoroutine(WaitForLoadResAsync(abName, resName, action)); } private IEnumerator WaitForLoadResAsync <T >(string abName, string resName, UnityAction<T> action ) where T :Object { AssetBundleRequest abr = abDict[abName].LoadAssetAsync<T>(resName); yield return abr; action?.Invoke(abr.asset as T); } private void AddToABDict (string abName ) { if (mainAB == null ) { mainAB = AssetBundle.LoadFromFile(PathUrl + MainABName); mainABManifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest" ); } var strs = mainABManifest.GetAllDependencies(abName); foreach (var str in strs) { if (!abDict.ContainsKey(str)) { abDict[str] = AssetBundle.LoadFromFile(PathUrl + str); } } abDict[abName] = AssetBundle.LoadFromFile(PathUrl+abName); } public void UnLoadAB (string abName ) { if (abDict.ContainsKey(abName)) { abDict[abName].Unload(false ); abDict.Remove(abName); } } public void UnLoadAllAB () { AssetBundle.UnloadAllAssetBundles(false ); abDict.Clear(); mainAB = null ; mainABManifest = null ; } }
Addressable Addressable是在AssetBundle基础上实现的,详细用法:参考
XLua 1 CSharp调用Lua 1.1 Lua解析器 1 2 3 4 5 6 7 8 9 10 11 12 var luaEnv = new LuaEnv();luaEnv.DoString("print('Hello,World!')" ); luaEnv.DoString("require('LuaTest')" ); luaEnv.Tick(); luaEnv.Dispose();
1.2 文件加载重定向 解决Lua文件默认从Resources文件执行不方便的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void Start () { var luaEnv = new LuaEnv(); luaEnv.AddLoader(MyCustomABLoader); } public byte [] MyCustomABLoader (ref string filepath ) { var path = Application.streamingAssetsPath + "/lua" ; var ab = AssetBundle.LoadFromFile(path); var tx = ab.LoadAsset<TextAsset>(filePath + ".lua" ); return tx.bytes; }
Lua会在调用require("Lua文件名")
时依次访问目前所有的文件加载规则,直到找到对应的文件。
访问 自定义加载规则1->自定义加载规则2->…->默认加载规则。
1.3 luaEnv.Global 1.3.1 获取全局变量 1 2 3 4 5 6 7 8 9 10 11 12 public LuaTable Global{ get { return luaEnv.Global; } } private void Start () { var i = Global.Get<int >("testNumber" ); Global.Set("testNumber" ,55 ); }
1.3.2 获取全局函数 lua文件
1 2 3 testFun = function () print ("Hello,World!" ) end
C#文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public LuaTable Global{ get { return luaEnv.Global; } } public delegate void CustomCall () ;private void Start () { var call = Global.Get<CustomCall>("testFun" ); call(); var lf = Global.Get<LuaFunction>("testFun" ); lf.Call(); lf.Dispose(); }
另外,对于自定义委托,无参无返回值的XLua已经帮助处理了,所以不需要特殊声明,但如果是有参有返回值的委托,需要在前面加上[CSharpCallLua]
。
1 2 3 [CSharpCallLua ] public delegate int CustomCall (int num ) ;
1.3.3 获取List/Dictionary lua文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 testList1 = {1 ,2 ,3 ,4 ,5 } testList2 = {1 ,"123" ,true } testDict1 = { ["1" ] = 1 , ["2" ] = 2 , ["3" ] = 3 , ["4" ] = 4 , } testDict2 = { ["1" ] = 1 , [true ] = 1 , [false ] = true , ["123" ] = false , }
C#文件
1 2 3 4 5 6 7 8 9 10 11 public LuaTable Global{ get { return luaEnv.Global; } } var list1 = Global.Get<List<int >>("testList1" );var list2 = Global.Get<List<object >>("testList2" );var dict1 = Global.Get<Dictionary<string ,int >>("testDict1" );var dict2 = Global.Get<Dictionary<object ,object >>("testDict2" );
1.3.4 获取类 lua文件
1 2 3 4 5 6 7 8 testClass = { testInt = 2 , testBool = true , testString = "123" , testFun = function () print ("Hello,World!" ) end }
C#文件
1 2 3 4 5 6 7 8 9 10 11 12 13 public class CallLuaClass { public int testInt; public bool testBool; public string testString; public UnityAction testFun; } public class Program { var obj = Global.Get<CallLuaClass>("testClass" ); }
1.3.5 获取接口 lua文件
1 2 3 4 5 6 7 8 testInterface = { testInt = 2 , testBool = true , testString = "123" , testFun = function () print ("Hello,World!" ) end }
C#文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [CSharpCallLua ] public interface ICallLuaInterface { int testInt{ get ; set ; } bool testBool{ get ; set ; } string testString{ get ; set ; } UnityAction testFun{ get ; set ; } } public class Program { var obj = Global.Get<ICallLuaInterface>("testInterface" ); }
1.3.5 获取LuaTable lua文件
1 2 3 4 5 6 7 8 test = { testInt = 2 , testBool = true , testString = "123" , testFun = function () print ("Hello,World!" ) end }
C#文件
1 2 3 4 5 6 var luaTable = Global.Get<LuaTable>("test" );Debug.Log(luaTable.Get<int >("testInt" )); luaTable.Dispose();
2 Lua调用CSharp Lua没有办法直接访问C#,所以一定是先从C#调用Lua后,才把核心逻辑交给Lua来编写。
1 2 3 4 5 6 7 public class Main : MonoBehaviour { private void Start () { LuaManager.GetInstance().Init(); LuaManager.GetInstance().DoLuaFile("Main" ); } }
2.1 Lua调用C#类
Lua调用C#类
Unity自带类&C#自定义类
CS.命名空间.类
CS.UnityEngine.GameObject
实例化类对象:local obj = CS.UnityEngine.GameObject("名字")
为了节约性能&简便,可以在Lua里使用全局变量对一些C#类进行声明,如GameObject = CS.UnityEngine.GameObject
Mono类
不能用new实例化,需要用组件挂载的方式,但xLua不支持AddComponent<泛型>()这种无参泛型模式,因此使用另一个重载:AddComponent(Type t)
obj.AddComponent(typeof(Mono类名))
Lua调用类对象
静态方法和变量
使用 . 进行调用
local obj = GameObject.Find("名字")
成员变量
实例化的对象.变量名
obj.transform.location
成员方法
2.2 Lua调用C#枚举 和类的调用规则一样:CS.命名空间.枚举名.枚举成员
其他需要注意的:
1 2 3 4 5 6 7 8 9 E_MyEnum = CS.E_MyEnum local c = E_MyEnum.Idlelocal a = E_MyEnum.__CastFrom(1 )local b = E_MyEnum.__CastFrom("Idle"
2.3 Lua调用C# Array/list/Dictionary C#
1 2 3 4 5 public class Test { public int [] array = new int [5 ]{1 ,2 ,3 ,4 ,5 }; public List<int > list = new List<int >(); public Dictionary<int ,string > dict = new Dictionary<int ,string >(); }
Lua
Array
1 2 3 4 5 6 7 8 9 10 11 12 13 local obj = CS.Test()print (obj.array.Length)print (obj.array[0 ])for i=0 ,obj.array.Length-1 do print (obj.array[i]) end local array = CS.System.Array.CreateInstance(typeof(CS.System.Int32),10 )
List
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 local obj = CS.Test()obj.list:Add(1 ) obj.list:Add(2 ) obj.list:Add(3 ) for i=0 ,obj.list.Count-1 do print (i) end local List_String = CS.System.Collections.Generic.List(CS.System.String)local list = List_String()list.Add(111 )
Dictionary
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 local obj = CS.Test()obj.dict:Add(1 ,"123" ) for k,v in pairs (obj.dict) do print (k,v) end local Dic_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)local dict = Dic_String_Vector3()dict:Add("123" ,CS.UnityEngine.Vector3.right) print (dict:get_Item("123" ))dict.set_Item("123" ,nil )
2.4 Lua使用C#拓展方法 C#拓展方法是一种允许你为现有的类型添加新方法,而无需修改原类型代码或创建子类的功能。扩展方法看起来就像是目标类型的实例方法一样调用,但实际上它是一个静态方法。它的核心目的是为已定义的类(包括.NET框架的内置类型或第三方库中的类)增加功能,而无需修改或继承该类。
特点:
静态方法 :扩展方法实际上是定义在静态类中的静态方法。
this
关键字 :扩展方法的第一个参数前加上 this
关键字,表示该方法是扩展到这个类型的。
定义:
1 2 3 4 5 6 7 8 9 10 public static class StringExtensions { public static string Reverse (this string str ) { char [] charArray = str.ToCharArray(); Array.Reverse(charArray); return new string (charArray); } }
使用:
1 2 3 string example = "Hello" ;string reversed = example.Reverse(); Console.WriteLine(reversed);
要在lua中调用C#的拓展方法,要在声明拓展方法的静态类前面加上:[LuaCallCSharp]
,调用方法则和普通调用实例方法一致。
另外,该特性可以提高lua访问C#的性能。