zoukankan      html  css  js  c++  java
  • Siki_Unity_3_xLua游戏中的热更新

    xLua游戏中的热更新

    课时3~5:热更新方案

    开发者不用重新打包上传;
    用户不用重新下载安装包;
    安装包不用通过商店审核

    现有的成熟热更新插件:
      toLua/ xLua -- 运行lua代码,实现了lua和c# 的代码交互

    xLua(腾讯开源)介绍:https://gameinstitute.qq.com/community/detail/111400

    市面上用的多的的热更新方案:

    uLua(http://ulua.org/index.html):基于toLua(插件)扩展出的热更新方案,但是不再维护,作者去维护toLua了
    toLua:uLua的作者基于toLua插件开发出了名为LuaFramework的热更新方案
    sLua:代码质量好,性能比toLua低

    Lua为解释性语言,因此无需进行编译过程,用户在下载热更新代码后即可直接生效,不需进行打包

    课时6~8:xLua插件的导入及简单使用

    github找到xLua,进行clone,将下载好的代码压缩包导入unity工程即可
      这里面也包含了官方使用demo及教程

    C#与Lua的代码交互

    // c#中调用lua代码
    
    private LuaEnv luaEnv;
    
    void Start(){
        luaEnv = new LuaEnv();
        luaEnv.DoString("print('Hello world!')");
    
    }
    
    void OnDestroy(){
        luaEnv.Dispose();
    }

    一个LuaEnv实例对应一个Lua虚拟机,处于开销的考虑,建议全局唯一

    在Lua中调用C#方法:

    luaEnv.DoString( "CS.UnityEngine.Debug.Log("HelloWorld")" );即可
    1. C#方法名要写包含命名空间的全称

    任务9~12:通过Lua文件进行调用

    原理为通过Resources.Load进行Lua文件的读取,并将读取的代码放入luaEnv.DoString()执行

    TextAsset ta = Resources.Load<TextAsset>("helloworld.lua");
    luaEnv.DoString(ta.text);

    正常的加载方法为:通过lua的内置loader进行加载

    使用:

    将HelloWorld.lua.txt文件放入Resources文件夹下

    luaEnv.DoString( "require 'HelloWorld' ");

    此时将HelloWorld.lua中的代码全部运行了一遍

    原理:

    require实际上会调用一个个的loader尝试加载,有一个加载成功则返回,若全部失败则报文件找不到

    xLua除了原生loader之外,还扩展了从Resource文件夹中加载的loader,前提条件是这些lua文件需要加上.txt后缀

    官方建议

    建议的加载Lua的脚本方式:
    整个程序只有一个DoString()进行DoString("require 'main'"),然后在main.lua中加载其他lua脚本

    通过自定义的Loader进行加载

    使用自定义Loader的好处:可以将lua文件放在自定义目录下,也可以放在服务器端进行下载动态取得

    void Start(){
        LuaEnv env = new LuaEnv();
        env.AddLoader(MyLoader);    // 这边手动添加了一个自定义loader的回调
        env.DoString("require 'xxx'");    
        // 不仅会去Resource中找,也会通过自定义loader的回调去查找。
        // 注意:找到一个loader后就不会继续查找了
        env.Dispose();
    }
    
    // 这里即为自定义的loader,当上面执行env.DoString("require")时,
    // 会将该自定义loader也加入查找。
    // 返回值不为空时,即表示找到lua代码并返回,为空时会继续遍历查找其他loader
    private byte[] MyLoad(ref string path){
        if(path=="xxx")
        return System.Text.Encoding.UTF8.GetBytes("print 1");
    }

    使用自定义loader进行加载的实例:

    // 将lua文件放入streamingAssetsPath
    private byte[] MyLoader(ref string filePath){
        string path = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";
        return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(path));
    }

    任务13~17:C#与lua之间的变量访问

    简单类型

    在lua文件中定义a=100; b="str"; c=false变量
    LuaEnv.DoString进行require
      此时变量已经存储在了LuaEnv的虚拟机中
    luaEnv.Global.Get<int>("a")
    luaEnv.Global.Get<string>("b")
    luaEnv.Global.Get<bool>("c")

    元组 (class/struct/interface):

    lua中定义元组

    person = {
      name = "",
      age = 1
    }

    方法1:在c#中映射到class/ struct -- 值拷贝

    class Person{
        public string name;
        public int age;
    }
    
    Person p = luaEnv.Global.Get<Person>("person");
    
    // 这是一个值拷贝的过程 -- 因此获得以后,lua里和c#里的两者就没有关系了
    // xLua会先new一个对象实例,并把对应的字段复制过去。
    // 如果class比较复杂,则代价会比较大

    名字相同的字段会进行映射,名字没有匹配的字段(不管是lua中的还是c#中的,没有关系,会被忽略不被赋值)

    这种方式的开销大,可以通过把类型加到GCOptimize生成降低开销

    方法2:在c#中映射到interface -- 引用(推荐

    需要加特性 [CSharpCallLua],此时xLua会自动为该接口生成一些需要的代码

    [CSharpCallLua]
    interface IPerson {
       // 接口不能定义字段
    string name{get;set;} int age{get;set;} void eat(int a, int b) } IPerson p = luaEnv.Global.Get<IPerson>("person"); // 注意:这是引用 -- 在c#中改变person的属性,也会改变lua的中的元组 p.age = 15; luaEnv.DoString("print(persion.age)"); // 此时输出的为Lua: 15 // 映射为接口可进行函数映射 p.eat(1,2) // 对应lua的person元组的定义一个对应的匿名函数 // 这样定义lua函数时,有三个参数,第一个为函数本身,对应c#里的this,这个必须写。可以通过self进行元组的访问,比如self.age person = { ... eat = function (self, a, b) print(a+b) end } // 输出3 // 不声明第一个self参数的写法:这么写会默认带一个self参数 function person:eat(a, b) end // 上面的写法等价于: function person.eat(self, a, b) end

    方法3:映射到Dictionary<>/ List<>上 -- by value

    不需要定义class或interface,直接映射成Dictionary或List集合中
    映射成Dictionary或List中,各有缺点

    // Dictionary
    // 局限性:元组中没有对应key的元素,不会进行映射
    Dictionary<string, object> dict = luaEnv.Global.Get<Dictionary<string, object>>("person");
    foreach(string key in dict.keys) {
        // 遍历
    }
    
    // List
    // 局限性:为键值对会被忽略,只会映射单纯的值
    List<object> list = luaEnv.Global.Get<List<object>>("person");
    foreach(object item in list) {
        // 遍历
    }
    
    luaEnv.Global.Get<List<int>>("person")
    // 只会映射元组中为number的值,若为小数则映射为0

    方法4:映射到LuaTable类中 -- by ref -- 不推荐

    xLua提供的一个c#类
    优势:无需生成代码
    缺点:比较慢,比方法2慢了一个数量级
      没有类型检查

    LuaTable tab = luaEnv.Global.Get<LuaTable>("person");
    print(tab.Get<string>("name"));    // 输出key为name对应的值
    print(tab.Get<int>("age"));
    
    // 还有其他一些方法可以尝试,比如Length/ Get/ Set等

    任务18~19:访问Lua中的全局function

    方法1:将lua中的全局function映射到C#的delegate委托 -- 推荐

    优点:性能好;类型安全
    缺点:需要生成代码

    delegate

    // 定义委托 -- 需要加上CSharpCallLua特性
    [CSharpCallLua]
    delegate void Func(int a, string b); // 映射 Func func = luaEnv.Global.Get<Func>("funcName"); // 调用 func(1,"aa"); // 释放 func = null; // 如果没释放就进行luaEnv.Dispose(),会报错

    没有参数时可以简化成Action,不带参数的Action是可以不加 [CSharpCallLua] 特性的

    Action func = luaEnv.Global.Get<Action>("funcName");
    func();
    func = null;

    返回值的处理

    // 单返回值
    [CSharpCallLua]
    delegate int Add(int a, int b);
    
    Add add = luaEnv.Global.Get<Add>("add");
    int res = add(1,2);
    add = null;
    
    // 多返回值 -- 通过out/ ref的方式
    [CSharpCallLua]
    delegate int Add(int a, int b, out int res2, ref int res3);
    // 可用out或ref都行
    
    Add add = luaEnv.Global.Get<Add>("add")
    int res1 = add(1, 2, out int res2, ref int res3);
    add = null;

    方法2:映射到LuaFunction

    优缺点与方法1相反 -- 速度慢,类型不安全 -- 不推荐

    LuaFunction func = luaEnv.Global.Get<LuaFunction>("add");
    // 返回类型为object[]
    object[] results = func.Call(1, 2); 

    总结:在C#中访问Lua的全局数据时
      推荐通过delegate访问函数
      通过interface访问元组,
      这样做可以使c#使用方和xLua解耦,由一个专门的模块负责xlua的初始化以及delegate/ interface的映射。

    任务20~21:在Lua中访问静态属性与方法

    类名需要写全,要带上命名空间

    Lua中没有new关键字;
    所有c#相关代码都要放到CS下,包括构造函数、静态成员属性、方法等

    创建对象:

    local newGo = CS.UnityEngine.GameObject();

    local newGo = CS.UnityEngine.GameObject('name')

    访问静态属性:

    CS.UnityEngine.Time.deltaTime

    CS.UnityEngine.Time.timeScale = 0.5

    访问静态方法:

    CS.UnityEngine.GameObject.Find('name')

    小技巧:
      如果有一些需要经常访问的类,可以用局部变量引用后,通过局部变量进行访问,可以提高性能
      local GameObject = CS.UnityEngine.GameObject
      GameObject.Find('name')

    任务22:Lua访问C#成员属性和方法

    访问成员属性:. 运算符

    local camera = CS.UnityEngine.GameObject.Find("Main Camera")
    camera.name = "newName";

    访问成员方法:. 运算符 或 : 语法糖

    注意:在Lua中没有this关键字
      所以如果使用 . ,则需要在方法中手动传递对象本身
      如果使用 : 则不需要

    local cameraComponent = camera.GetComponent(camera, "Camera")

    local cameraComponent = camera:GetComponent("Camera")

    任务23:Lua访问C#的注意事项

     

     

     

     

     

     

     

  • 相关阅读:
    kube-apiserver
    深度学习三:卷积神经网络
    深度学习二:概率和反向传播的变种
    深度学习一:深度前馈网络和反向传播
    Knowledge 1:Propositional Logic 命题逻辑基础及符号
    评估方法:留出法、交叉验证法、自助法、调参与最终模型
    你曾这样问过
    套路总结
    NOI2020游记
    curl不是内部或外部命令,也不是可运行的程序或批处理文件
  • 原文地址:https://www.cnblogs.com/FudgeBear/p/14077325.html
Copyright © 2011-2022 走看看