kizumi_header_banner_img

Hello! Beautiful Kizumi!

加载中

文章导读

Unity中Lua热更新解决方案


avatar
Yuyas 2025年12月19日 19

AB包

作用:

  • 比Resources更好管理资源

    • Resources:打包时定死只读,无法修改
    • AB包:存储位置自定,压缩方式自定,后期可以动态更新

  • 减少包体大小

    • 压缩资源
    • 减少初始包体大小
    • 热更新

生成的文件:

  • AB包文件

    • 资源文件

  • manifest

    • AB包文件信息
    • 加载时,提供了关键信息,资源信息,以来关系,版本信息等

  • 关键AB包(主包,和目录名一样的包)

Compression压缩方式:

  • Nocompression

    • 不压缩,解压快,包大

  • LZMA

    • 压缩最小,解压慢
    • 用一个资源要解压所有

  • LZ4

    • 压缩相对LZMA大一点点
    • 建议使用,用什么解压什么,内存占用低

加载资源:(不可重复加载包体)

先加载AB包本体,再加载资源

  • 同步加载

    • 加载AB包:AssetBundle.LoadFromFile
    • 从AB包加载资源:ab.LoadAsset

  • 异步加载(返回的是Request)

    • 加载AB包:AssetBundle.LoadFromFileAsync(abName)
    • 从AB包加载资源:ab.LoadAssetAsync

卸载AB包:(传参若为ture,则会同时卸载通过此包加载的资源)

  • AssetBundle.UnloadAllAssetBundles(false);
  • ab.Unload(false);

依赖:

AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/cube");
GameObject obj = Instantiate(ab.LoadAsset<GameObject>("Cube"));
// 加载了一个Cube,但是其材质球在别的AB包中,不加载对应包会导致材质 异常

//得到所有依赖Cube的AB包并加载,正常加载Cube材质
AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/PC");
AssetBundleManifest manifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] str = manifest.GetAllDependencies("cube");
foreach (string i in str)
{
   AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + i);
}

Lua

  • 4种简单的变量类型

    • nil
    • number
    • string
    • boolean

  • 4种复杂的变量类型

    • function 函数
    • table 表
    • userdata 数据结构
    • thread 协同程序

  • 字符串操作

    • 获取字符串长度 #s

      • 一个汉字占3个长度

    • 字符串拼接 s1 .. s2
    • 格式化占位符 string.format(“%d,123”)
    • 转字符串 tostring(a)
    • 大小写转换 string.upper/lower
    • 翻转 string.reverse
    • 字符串索引查找 string.find(str,”abc”)

      • 可以查找字符串

    • 截取 string.sub(str,3,4)
    • 修改 string.gsub(str,”before”,”after”)

  • 运算符

    • 算数运算符

      • 不支持 += ++等
      • 支持幂运算符 ^

    • 条件运算符

      • 不等于 ~=

    • 逻辑运算符

      • and
      • or
      • not

    • 位运算符,三目运算符

      • 不支持
      • 但是三目运算符可以用另一种方式实现

        • ans = ( x > y ) and x or y

  • 条件分支语句

    • 单分支:if 条件 then …… end
    • 双分支:if 条件 then …. else ….. end
    • 多分支:if 条件 then …. elseif 条件 then ….. end

  • 循环语句

    • while 条件 do …… end
    • repeat ….. until
    • for i = 1,5 do …. end

      • 默认自增1,想要自定可以在5后传入第三个参数

  • 函数

    • 两种声明方式

      • 直接声明 funtion func()…. end
      • 可以作为变量 func = function()….. end

    • 对于有参函数,传入的参数与要求不匹配,不会报错,只会补空nil或者丢弃
    • 可以返回多个参数,用多个变量接收(直接用逗号隔开)
    • lua不支持重载,默认调用最后一个声明的函数
    • 变长参数 function func( … ) arg = { … }

      • 参数为…,需要声明一个表接收

    • 嵌套函数
    • 闭包

      • function f1(x)
           return function (y)
               return x + y
           end
        end
        f2 = f1(1);
        print(f2(2));

  • table表

    • 数组

      • 当数组中有nil时,会影响#获取数组长度(只能获取nil前面元素的个数)

    • 迭代器ipairs和pairs

      • ipairs只能遍历从1开始连续的元素
      • pairs可以遍历数组中所有的元素

        • for i,k in pairs(a) do
             print(i .. ” ” .. k)  

    • 字典

      • 添加

        • 直接在字典里声明 a = {[“name”] = “Shen” , [“age”] = 19}
        • 通过键新增 a[“sex”] = true

      • 删除

        • a[“name”] = nil

      • 访问

        • a.name
        • a[“name”]

      • 如果要遍历必须用pairs不能用ipairs

      • Student =
        {
           name = “shen”,
           Func = function (t)
          print(t.name)
           end
        }
      • 其中在表的内部函数中调用表中属性,也要加上表名
      • lua中点和冒号的区别

        • Student.Func(Student)
        • Student:Func(),冒号会默认把调用者作为第一参数传入方法

          • 冒号除了调用,还可以配合self声明

            • Student:Func()
              print(self.name)
              end

    • table表的公共操作

      • table.insert( t1, t2 ) 将t2插入t1
      • table.remove( t1 ) 移除t1中的尾部元素,也可以传入第二参数指定下标
      • table.sort( t1 ) 升序排序

        • 降序排序table.sort(t1,function(a,b)
          if a > b then
          return true
             end
          end)

  • Local 本地变量
  • require(”脚本名”) 多脚本执行,可以用变量接收,接收到的值为脚本中return的值
  • package.loaded[“脚本名”] 返回true则该脚本已加载,若要卸载可以手动置false
  • 大G表 _G 是一个总表(table),会将所有全局变量存入,local本地变量不会存入
  • lua中只有nil和false才是假,0不是
  • 协程

    • 两种创建方式

      • coroutine.create ( func ) type : thread

        • 若yield中有返回值,可以用两个变量接收,第一变量为bool

      • coroutine.warp ( func ) type : function

        • 若yield中有返回值,用一个变量接收,原来的bool没了

    • 两种运行方式(对应上方两种创建方式)

      • coroutine.resume ( myCoroutine )
      • myCoroutine ( )

    • 挂起

      • coroutine.yield ( ) 可以传参作为返回值

    • 状态

      • coroutine.status ( 协程对象 )

        • dead 结束
        • suspended 暂停
        • running 进行中

    • 得到正在运行的协程号

      • coroutine.running ( )

  • 元表

    • 设置元表函数

      • setmetatable(子表,元表(父表))

    • __tostring

      • 当子表被作为字符串使用时,默认调用元表的__tostring方法

    • __call

      • 子表被作为方法使用时,默认调用元表的__call方法
      • 元表中方法的第一参数为子表本身,第二参数才是传入参数

    • __add,__sub,__mul,__div,__mod…….

      • 运算符重载,对应 + – * / %

    • __index

      • 当你尝试访问一个表中不存在的键(例如 t.key)时,Lua 会做以下操作:

        • 检查该表是否有元表(metatable)
        • 如果有元表,检查元表中是否有 __index 字段
        • 根据__index的类型,执行不同逻辑:

          • __index 是一个,Lua 会去这个表中查找该键
          • __index 是一个函数,Lua 会调用该函数,传入原表和要访问的键

      • 例:meta.__index = meta (写在表外部初始化)

    • __newindex(与__index相似,由访问变为了赋值)

      • 当你尝试给表中一个不存在的字段赋值时,Lua 会先检查该表是否有元表(metatable),并且元表中是否定义了 __newindex 方法:

        • 如果存在 __newindex 方法,Lua 会调用这个方法,而不是直接在原表中添加新字段
        • 如果不存在 __newindex 方法,Lua 会直接在原表中添加这个新字段

    • getmetatable ( 子表 )

      • 得到元表的方法

    • rawget ( 表, “属性” )

      • 忽略元表 __index 的影响,获取此表中属性

    • rawset ( 表, “属性”, 值 )

      • 忽略元表 __newindex 的影响,赋值此表中属性

  • 面向对象

    • 封装local Object = {}
      Object.id = 1

      function Object:new()
         local obj = {}
         self.__index = self;
         setmetatable(obj,self)
         return obj
      end
    • 继承function Object:subClass(className)
         _G[className] = {}
         local obj = _G[className]
         self.__index = self
         setmetatable(obj, self)
      end
    • 多态– 在继承方法中加入base属性为自己的父表
      function Object:subClass(className)
      _G[className] = {}
      local obj = _G[className]
      obj.base = self
      self.__index = self
      setmetatable(obj, self)
      end

      — 声明子类
      Object:subClass(“GameObject”)
      GameObject:subClass(“Player”)
      Player.posX = 0
      Player.posY = 0

      p1 = Player:new();
      p2 = Player:new();

      — 多态实现
      function GameObject:Move()
      self.posX = self.posX + 1
      self.posY = self.posY + 1
      print(self.posX)
      print(self.posY)
      end

      function Player:Move()
      self.base.Move(self)
      end

      p1:Move()
      p2:Move()

  • Lua元表深拷贝的实现function clone(object)
    local table_dic = {}

    local function _copy(object)
    if type(object) ~= “table” then
    return object
    end

    if(table_dic[object] ~= nil) then
    return table_dic[object]
    end

    local new_table = {}
    table_dic[new_table] = new_table
    for key, value in pairs(object) do
    new_table[_copy(key)] = _copy(value)
    end
    return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
    end

  • 自带库

    • math.abs
    • math.deg
    • math.cos
    • math.floor / ceil
    • math.max / min
    • math.modf ( 1.2 )

      • 两个变量接收 将数分为小数和整数部分

    • math.sqrt
    • 随机数

      • math.randomseed( os.time() )
      • math.random ( 100 )

  • 垃圾回收

    • collectgarbage ( “count” )

      • 返回当前lua占用内存数,单位为K字节

    • collectgarbage ( “collect” )

      • 手动垃圾回收

    • Tip:Unity热更新开发中尽量不要用自动垃圾回收,最好手动回收

C#调用Lua

  • 调用前在编辑器中 Generate Code
  • AB包打包前 Clear Generate Code
  • Lua解析器

    • Lua env = new LuaEvn( )

      • 通常保持唯一性

    • env.DoString( “print(‘Hello World’)” )

      • 执行Lua语言
      • 通常是env.DoString( “require( ‘ScriptName’ )” )

        • 其中寻找脚本路径默认是在Resources下,并且要在Lua脚本后缀加上txt

    • env.Tick( )

      • 垃圾回收,通常在帧更新定时或者切换场景时执行

    • env.Dispose( )

      • 销毁解析器

    • env.AddLoader( MyCustomLoader )

      • private bite[ ] MyCustomLoader( ref string fileName )

        • 由此函数通过File.ReadAllBytes(path)返回想要读取lua文件的地址
        • 若地址无效则会从默认Resources中寻找lua资源
        • 其中fileName为lua的文件名

      • 可以添加多个,执行require时依次寻找

    • AB包加载lua文件

      • lua文件必须加上.txt,在ab打包时不支持lua类型文件
      • LoadAsset时文件名必须带上.lua因为.lua是文件的一部分不是后缀,不能省略

    • 全局变量的获取

      • 大G表(C#中的env.Global)获取lua中的变量
      • LuaMgr.Instance.Global.Get<int>(“Name”) 获取变量
      • LuaMgr.Instance.Global.Set(“Name”,10) 设置变量,改变引用

    • 全局函数的获取

      • 无参无返回

        • 自定义委托 public delegate void CustomCall( )

          • 不需要加特性,xLua已经处理过

        • UnityAction
        • Action

      • 有参有返回

        • 自定义委托 public delegate int CustomCall(int a)

          • 需要加上特性[CSharpCallLua]

        • C#自带,类型 Func<int,int>(参数为一个int,返回值int)

      • 多返回值

        • 自定义委托 public delegate int CustomCall(int a , out b , out c , out d)

          • 需要加上特性[CSharpCallLua]
          • 用out或者ref皆可
          • 其中委托类型为第一个返回值

      • 变长参数

        • 自定义委托 public delegate void CustomCall(string a, params object[ ] args)

          • 需要加上特性[CSharpCallLua]
          • 其中object也可以改为指定类型,提高性能

    • List和Dictionary映射table

      • 与上方一样,泛型类型为List<T>和Dictionary<T,T>
      • 当有多种类型时就用object

    • Class类映射table

      • 自定义一个类去接收table类,同样使用Get<T>,类中属性名字要和lua中保持一致
      • 浅拷贝

    • Interface接口映射table

      • 需要加上特性[CSharpCallLua]
      • 接口只允许存在成员属性,不允许存在成员变量
      • 深拷贝

    • LuaTable映射table

      • xLua自带的LuaTable类
      • 获取后相当于一个小的大G表,继续使用Get<T>获取table里的变量
      • Set为改引用
      • 用完一定要table.Dispose( )

    • 总结CSharpCallLua特性使用场景

      • 自定义委托
      • 接口

Lua调用C#

  • 尽量在所有要调用的类加上[LuaCallCSharp]提升访问性能(不加也不会报错)
  • 调用C#类

    • CS.命名空间.类名
    • 例 local obj = CS.UnityEngine.GameObject( )

      • 因为Lua没有new,所以直接加上括号就是新建对象

    • 为了方便使用并且节约性能,定义全局变量存储C#中的类,相当于别名using

      • 例 GameObject = CS.UnityEngine.GameObject

  • 调用成员方法

    • 静态方法不用使用:
    • 其余必须使用冒号:
    • obj:AddComponent( typeof( CS.MyClass ) )

      • 因为lua不支持泛型,所以使用AddComponent的重载传入Type

  • 调用枚举Enum

    • 使用起来跟类一样,只不过不用new
    • 也可以使用下标或者字符串转换

      • 枚举.__CastFrom( 下标/字符串 )

  • 调用数组,List,Dictionary

    • 数组

      • 只能用Length获取长度,不能用#
      • 下标规则为C#从0开始
      • 在lua中创建数组

        • array = CS.System.Array.CreateInstance(typeof(CS.System.Int32), 10)

    • List

      • 用Count获取长度
      • 在lua中创建List

        • List_String = CS.System.Collections.Genetic.List(CS.System.String)MyList = List_String( )
        • 此时的List是需要new的

    • Dictionary

      • 在lua中创建Dictionary

        • Dictionary_String_Vector3 = CS.System.Collections.Dictionary(CS.System.String, CS.UnityEngine.Vector3)MyDic = Dictionary_String_Vector3( )

      • 在lua中的字典,当value为结构体类型时,不能直接使用dic[key],

        • dic:get_Item(key)
        • dic:set_Item(key)

  • 调用拓展方法

    • 需要给静态工具类加上特性[LuaCallCSharp]

  • ref,out

    • ref

      • 以多返回值形式返回给lua,需要多个变量接收
      • 如果存在返回值,则是第一个值
      • 需要填入参数占位

    • out

      • 以多返回值形式返回给lua,需要多个变量接收
      • 如果存在返回值,则是第一个值
      • 不需要填入参数占位

  • 调用重载函数

    • Lua虽然支持调用C#中的重载函数,但是本身不支持重载函数
    • 因为Lua中只有Number类型,所以尽量避免使用精度重载的函数,会发生错误
    • 如果非要使用精度重载函数,可以使用xLua提供的反射

  • 调用委托和事件

    • 委托

      • 第一次往委托里添加函数,不能直接+,因为是nil,所以第一次要先=
      • del = nil 清空
      • del()调用

    • 事件

      • 事件的订阅有点像成员方法,obj:eventAction(“+”, func)
      • 清空和调用都要在C#中封装方法调用

  • 使用二维数组

    • Lua不支持直接array[1][1]访问数组
    • 要使用方法array:GetValue(1, 1)

  • nil和null区别

    • unity中的判空不能直接 if obj == nil,而是obj:Equals(nil)
    • 也可以在Unity中封装一个静态方法IsNull( this Object obj )
    • 也可以在Lua中封装一个方法funtion IsNull( obj )

  • 解决[LuaCallCSharp]和[CSharpCallLua]无法设置在系统类的问题

    • public static class Tool // 静态类
      {
      [CSharpCallLua] // 特性
      public static List<Type> list = new List<Type>() // 静态list
      {
      typeof(UnityAction<float>)
      };
      }
    • local slider = sliderObj:GetComponent(“UITest”).slider

      slider.onValueChanged:AddListener(function (value) // 这里是把lua的方法融合到了C#方法里,所以需要CSharpCallLua,不需要LuaCallCSharp是因为已经内置了
      print(value)
      end)

  • lua中调用C#协程

    • GameObject = CS.UnityEngine.GameObject
      WaitForSeconds = CS.UnityEngine.WaitForSeconds
      local util = require(“xlua/util”) // 注意这里要手动把util放在Resources并删除txt后缀
      print(util)

      local obj = GameObject(“Coroutine”)
      mono = obj:AddComponent(typeof(CS.LuaCallCSharp))

      function Func()
      i = 0
      while true do
      i = i + 1
      print(i)
      coroutine.yield(WaitForSeconds(1))
      end
      end

      mono:StartCoroutine(util.cs_generator(Func)) // 使用util工具进行协程调用

  • 调用泛型函数

    • lua只支持有参数,有约束且约束只能继承类的泛型函数
    • 剩下的只能得到通用函数使用xlua.get_generic_method(根据打包方式不同会有所限制)



评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码

个人信息

avatar

24
文章
1
评论
1
用户

近期文章