首页 > 代码库 > xLua热更方案

xLua热更方案

前言:

一直没有做过Lua相关的商业项目,这次总算有机会得到这个机会,并且对游戏前端用xLua进行了实现。

之前在业余的时间里自己经常练手写一些关于uLua的东西,但真正工作用起来,发现业余玩一玩的练手方式其实还是不够的,需要多实践。

之前所了解的一些热更的方案有LuaJit、uLua、sLua、xLua、L#(C# Light)等,最终我们选了xLua的方案。

原因:

1、据我所知,LuaJit和Lua并不是同一门语言。

2、uLua:据我所知,uLua是当前速度最快的Lua,但它所存在的缺陷就是uLua的分支版本多,造成了不统一的现象。

3、sLua:不熟悉。

4、xLua:作者和云风撕了一场大逼,性能上可能并不如uLua,也可能会让大家产生撞大运编程的即视感,不过由企鹅主导维护,并且只有一个版本,对开发者还算比较友好。

5、L#:李中二是个懒虫,踏马赛克的都不维护了。

每种语言或者插件,都有各自的优势,所以它才会有人用。

xLua的优势:http://www.gad.qq.com/article/detail/24967

GitHub地址:https://github.com/Tencent/xLua

正题:

重点强调:不要在意我的实现方式,因为我还没去做优化,暂时只是简单的做出来,我也是完全靠个人领悟,目前我也没弄到可直接运行供我参考的可热更的Lua资源,我正在努力的做我的第一个关于Lua的商业作品,我也很纠结,说多了都是泪啊。

1、用法和uLua相似,导进来就行,具体用法大家看官方教程吧,xLua下载下来后里面有文档。

2、同样不支持JIT,今天就在通讯的泛型上面有一些困惑,最终问了作者,不支持JIT,原因:为了支持IOS而不能使用JIT

3、Lua调用C#的类型映射类似下图的方式,需要什么加什么(例子在官方工程的ExampleGenConfig.cs里面)。

技术分享

 

4、C#调用xLua,观察者模式下传递委托会出现异常,有可能是因为这个委托的类型没有配置(例子在官方工程的ExampleGenConfig.cs里面)。

技术分享

这个委托类型是我自己加上去的,我在观察者模式上用到这个委托的回调。

技术分享

 

5、挂载xLua脚本。

xLua的方案中,unity挂脚本是通过一个LuaBehaviour.cs来作为主控挂载xLua的脚本,按照unity的思维,应该是可以同时挂载多个脚本,所以我对xLua的LuaBehaiour.cs做了一点点的调整。不要在意我的实现方式,因为我还没去做优化,暂时只是简单的做。

    void Awake()
    {
        LuaEnv luaenvScriptCtrl = new LuaEnv();
        if (luaAssetRoot == null)
        {
            luaAssetRoot = Assets.InstantiateTextAsset(GameData.gameInfos.selectGameInfo.resPathOf("LuaScriptList.lua"));
        }//获取一个Lua脚本LuaScriptList.lua,这个脚本里存放的是prefab的名字和脚本名字的键值对,一键可对多值,因为一个GameObject可以挂多个脚本。
        luaenvScriptCtrl.DoString(luaAssetRoot.text);
        string thisName = this.gameObject.name.Replace("(Clone)", "");
        thisName = thisName.Replace("(Clone)", "");//去掉克隆字样,括号可能是半角或者全角都作处理
        string scriptStrs = luaenvScriptCtrl.Global.Get<LuaTable>("LuaScriptTable").Get<string>(thisName);//从LuaScriptList.lua中取出自己的配置表
        string[] scriptArray = scriptStrs.Split(|);//解析我这个Prefab用哪些lua脚本。
        foreach (var s in scriptArray)
        {
            if (luaScripts == null)
            {
                luaScripts = new List<TextAsset>();
            }
            luaScripts.Add(Assets.InstantiateTextAsset(GameData.gameInfos.selectGameInfo.resPathOf(s)));
        } //将解析出来需要加上去的脚本统一加上去。
        #region 官方例子里的内容,我先括起来。
        scriptEnv = luaEnv.NewTable();

        LuaTable meta = luaEnv.NewTable();
        meta.Set("__index", luaEnv.Global);
        scriptEnv.SetMetaTable(meta);
        meta.Dispose();

        scriptEnv.Set("self", this);
        if (injections != null)
        {
            foreach (var injection in injections)
            {
                scriptEnv.Set(injection.name, injection.value);
            }
        }
        #endregion

        if (luaScripts != null)
        {
            foreach (var item in luaScripts)
            {
                luaEnv.DoString(item.text, "LuaBehaviour", scriptEnv);
                luaAwake += scriptEnv.Get<Action>("awake");
                luaStart += scriptEnv.Get<Action>("start");
                luaUpdate += scriptEnv.Get<Action>("update");
                luaOnDestroy += scriptEnv.Get<Action>("ondestroy");
                luaOnEnable += scriptEnv.Get<Action>("OnEnable");
                luaOnDisable += scriptEnv.Get<Action>("OnDisable");
            }//将xLua的运作方式改成和unity monobehaviour一致。
        }
        if (luaAwake != null)
        {
            luaAwake();
        }
      
    }

 

剩下的start、update等等函数也需要做一点处理。

   // Use this for initialization
    void Start ()
    {
        if (luaStart != null)
        {
            luaStart();
        }
    }
    
    // Update is called once per frame
    void Update ()
    {
        if (luaUpdate != null)
        {
            luaUpdate();
        }
        if (Time.time - LuaBehaviour.lastGCTime > GCInterval)
        {
            luaEnv.Tick();
            LuaBehaviour.lastGCTime = Time.time;
        }
    }

    void OnDestroy()
    {
        if (luaOnDestroy != null)
        {
            luaOnDestroy();
        }
        luaOnDestroy = null;
        luaUpdate = null;
        luaStart = null;
        if (scriptEnv != null)
        {
            scriptEnv.Dispose();
        }

        injections = null;
    }

    private void OnEnable()
    {
        if (luaOnEnable != null)
        {
            luaOnEnable();
        }
    }

    private void OnDisable()
    {
        //this.gameObject.SetActive(false);
        if (luaOnDisable != null)
        {
            luaOnDisable();
        }
    }

 

6、跑起来之前先要做一个wrap操作。

技术分享

选项1、生成wrap等。

项选2、清除wrap等。

选项3、使用类或者方法的热更:该项功能主要是打补丁,比如替换C#的某个方法或者某个类。

 

7、跑起来时LuaBehaviour的效果。

使用热更的那个游戏服务器关了,游戏进不去了,下回再补上。

6点30下班都走了,服务端的走了没法开了,真的没加班。

另外公司招会unity+lua的熟手,有大神愿意来吗,薪资保证你满意,虽然薪资不是我说了算。

 

8、Lua代码

这种代码的写法我真的很难受GameLogic2007.lua.txt。

Singleton = {}
function Singleton:new(o)
    o = o or {}
    setmetatable(o,self)
    self.__index = self
    return o
end

function Singleton:Instance()
    if self.instance == nil then
        self.instance = self:new()
    end
    return self.instance
end

GameLogic2007 = Singleton:Instance()
-----以上代码表示我是个单例。
function awake()
    GameLogic2007.s = HandleGetClientSetting2007  --HandleGetClientSetting2007这里赋值的话也就是可以用GameLogic2007.s来调HandleGetClientSetting2007,
end--相当于unity的Awake

function start()
    print("startStart!")
    local nullBodyContract = CS.NullBodyContract
    CS.Game.Game2007.BJL.Logic.GameProtocolMgr.instance:SendClientLoadComplete(nullBodyContract)
    print("startOver!")
end--相当于unity的Start

function update()

end--相当于unity的update

function ondestroy()

end--相当于unity的ondestroy

function OnEnable()
    print("OnEnableStart!")
    self.LuaCSAddListener(self,"GameState2007", HandleGameState2007)
    self.LuaCSAddListener(self,"GetClientSetting2007",GameLogic2007.s)
    self.LuaCSAddListener(self,"SetGameResult2007", HandleSetGameResult2007)
    print("OnEnableOver!")
end--相当于unity的 OnEnable,观察者模式下我的监听放于此。

function OnDisable()
    print("OnDisableStart!")
    self.LuaCSRemoveListener(self,"GameState2007", HandleGameState2007)
    self.LuaCSRemoveListener(self,"GetClientSetting2007", GameLogic2007.s)
    self.LuaCSRemoveListener(self,"SetGameResult2007", HandleSetGameResult2007)
    print("OnDisableOver!")
end--相当于unity的 OnDisable,观察者模式下我的释放监听放于此。
 HandleGameState2007 = function (f1,f2,f3)
    print("HandleGameState2007-------------获取到了游戏状态,如果TXT为UTF8,则显示中文------")
end--监听的回调函数
local s
 function HandleGetClientSetting2007(f1,f2,f3)
    print("HandleGetClientSetting--------我是威少,我获取到了游戏信息-----------")
end--监听的调函数
 HandleSetGameResult2007 =  function (f1,f2,f3)
    print("HandleSetGameResult2007---------------------!")
end--监听的回调函数。

 

 

 9、更热不支持调用泛型,因为泛型需要JIT的支持。

A:通讯协议这一块,还是需要改的,现在我们只是在游戏前端的逻辑上用xLua实现,实现完了后我们一步一步的改过来,因为我们现在用的泛型,T类型在热更上肯定是不行的。

B:对于通信这一块,我们目前用的是短连接,我个人的想法是数据的传递用字典来传递,反正都是要序列化的,前后端将字典一致就行了。这样就免除了T类型的麻烦。

C:我一个朋友那边通讯是在socket上做了封装,取服务端数据是lua调安卓,安卓调ndk,然后由ndk去对接服务端,好像很高端,但是我们现在这么改肯定费时费力。

下图方式在热更上肯定是不行的,不要泛型,不要泛型,不要泛型,重要的事情说三遍还标红。

技术分享

 

10、未完待续,下班回家,下次再继续写。

----------------------------我一直在装逼,却少有装逼成功,请容我继续装逼。

xLua热更方案