Unity热更新之ULua 踩坑篇

Unity 的原生c#是无法在移动端上进行热更新的,那么如果线上发布遇到重大闪退事故的话,那么就不可以通过游戏内的热更新进行bug修复,只能重新提交版本,而往往在提交版本到发布的时间内,必然有玩家遇到这种问题,导致流失的,对于团队来说,这个可是很严重的。

所以我google了下,目前已经有开发者实现了这一功能,有c#Light ,ULua,Nlua等,lua在cocos上可以说是非常成功的,与C/C++强大的交互能力,小巧高速的能力,在C/C++上体现的非常好。

最近开始搞Unity,学习了下ULua(ULua资料),撇开Luajit 64位的坑。首先,打开ulua_v1.08.unitypackage,导入Ulua,如图:


uLua文件夹中 包含一些例子,还有LuaInterface的文档,可以学习学习。

导入成功之后,我们新建一个文件夹Scripts,新建一个Lua文件

,然后在子文件夹global新建一个c#文件,之后为了在c#中调用LuaEnterance.lua这个文件,得在c#中加入代码,同时引入命名空间using LuaInterface;这样才能调用LuaScriptMgr,这里建议将LuaScriptMgr对象声明位一个类成员。然后编译一下,没有问题!然后运行程序,就发现了第一个坑。

    void Start()  
    {  
        mgr = new LuaScriptMgr ();  
        mgr.Start ();  
        mgr.DoFile ("LuaEnterance.lua");  
      
    }  

运行doFile函数的时候获取文件的路径通过

    LuaDLL.lua_pushstdcallcfunction(L,tracebackFunction);  
          int oldTop=LuaDLL.lua_gettop(L);  
      
              // Load with Unity3D resources              
          byte[] text = LuaStatic.Load(fileName);  

用的是deletage,类似于c++的函数指针

    public delegate byte[] ReadLuaFile(string name);  
          
        public static class LuaStatic  
        {  
            public static ReadLuaFile Load = null;  
            //private static int trace = 0;  
      
            static LuaStatic()  
            {  
                Load = DefaultLoader;  
            }  
      
            static byte[] DefaultLoader(string name)  
            {  
                byte[] str = null;  
                string path = Util.LuaPath(name);  
      
                using (FileStream file = new FileStream(path, FileMode.Open))  
                {  
                    str = new byte[(int)file.Length];  
                    file.Read(str, 0, str.Length);  
                    file.Close();  
                }  
      
                return str;  
            }  


然后为了获取准确路径,调用Util.LuaPath,

    /// <summary>  
    /// 取得Lua路径  
    /// </summary>  
    public static string LuaPath(string name) {  
        string path = Application.dataPath + "/";  
        string lowerName = name.ToLower();  
        if (lowerName.EndsWith(".lua")) {  
            return path + "lua/" + name;  
        }  
        return path + "lua/" + name + ".lua";  
    }  

最后我发现doFile总是会到lua文件夹去找lua文件,这也太不自由了。当然我想到了其他可能性,或许作者为ulua打包做了处理,lua文件夹的文件有特别的好处?或者跟unity的机制有些关系?目前尚不清楚。

不考虑这些情况,我们可以简单做个处理。

    void Start()  
    {  
        mgr = new LuaScriptMgr ();  
        mgr.Start ();  
        mgr.DoFile (ReviseLuaPath("LuaEnterance.lua"));  
    }  
      
    public void DoLuaFile(string filepath)  
    {  
        if (mgr != null)  
            mgr.DoFile (ReviseLuaPath (filepath));  
        else  
            Debug.Log ("DoLuaFile ERROR! Plz create LuaScriptMgr First");  
    }  
      
    public string ReviseLuaPath(string path)  
    {  
        return "../Scripts/" + path;  
    }  


然后运行,便发现成功了。。。

之后进行进行下一步,在c#中运行lua中函数,首先在lua文件中定义一个函数

然后在c#中获取它

   private LuaFunction funcUpdate ;  
    void Start()  
    {  
        mgr = new LuaScriptMgr ();  
        mgr.Start ();  
        mgr.DoFile (ReviseLuaPath("LuaEnterance.lua"));  
        funcUpdate = mgr.GetLuaFunction ("Update");  
      
    }  

然后在update中运行

    void Update()  
    {  
        if (mgr != null)  
        {  
            funcUpdate.Call (Time.deltaTime);  
        }  
    }  
结果发现什么都没有输出。。。醉了醉了。遇到问题那就查!

推测Update这个函数没有获取到。看一下GetLuaFunction

//会缓存LuaFunction  
    public LuaFunction GetLuaFunction(string name)  
    {  
        LuaBase func = null;  
  
        if (!dict.TryGetValue(name, out func))  
        {  
            IntPtr L = lua.L;  
            int oldTop = LuaDLL.lua_gettop(L);  
  
            if (PushLuaFunction(L, name))  
            {  
                int reference = LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);  
                func = new LuaFunction(reference, lua);                  
                func.name = name;  
                dict.Add(name, func);  
            }  
            else  
            {  
                Debuger.LogWarning("Lua function {0} not exists", name);  
            }  
  
            LuaDLL.lua_settop(L, oldTop);              
        }  
        else  
        {  
            func.AddRef();  
        }  
  
        return func as LuaFunction;  
    }

每次获取缓存的lua函数,会尝试从Dictionary<stringLuaBase> dict中获取,然后这个key则是根据你传入的name而定的,那么问题就来了,不同文件下的同名函数怎么办?因为有些函数是Ulua内置的,都是在_G下的,有时候一不小心就可能命名一致,这里的问题也是因为这个导致的,ulua中自带一个Main.lua的文件,这个lua文件中有一个同名函数Update!!所以我们GetLuaFunction实际获得是这个函数!,因为其先被调用,并存在了dict中!LOOK!  LuaScriptMgr中有这么一段

    public void Start()  
     {  
         OnBundleLoaded();  
     }  
      
      
     void OnBundleLoaded()  
     {  
         DoFile("Golbal.lua");  
         unpackVec3 = GetLuaFunction("Vector3.Get");  
         unpackVec2 = GetLuaFunction("Vector2.Get");  
         unpackVec4 = GetLuaFunction("Vector4.Get");  
         unpackQuat = GetLuaFunction("Quaternion.Get");  
         unpackColor = GetLuaFunction("Color.Get");  
         unpackRay = GetLuaFunction("Ray.Get");  
      
         packVec3 = GetLuaFunction("Vector3.New");          
         packVec2 = GetLuaFunction("Vector2.New");  
         packVec4 = GetLuaFunction("Vector4.New");  
         packQuat = GetLuaFunction("Quaternion.New");  
         packRaycastHit = GetLuaFunction("Raycast.New");  
         packColor = GetLuaFunction("Color.New");  
         packRay = GetLuaFunction("Ray.New");  
         packTouch = GetLuaFunction("Touch.New");  
      
     !MULTI_STATE  
         traceback = GetLuaFunction("traceback");  
    dif          
      
         DoFile("Main.lua");  
         CallLuaFunction("Main");  
         updateFunc = GetLuaFunction("Update");  
         lateUpdateFunc = GetLuaFunction("LateUpdate");  
         fixedUpdateFunc = GetLuaFunction("FixedUpdate");  
         levelLoaded = GetLuaFunction("OnLevelWasLoaded");  
     }  

它将一些基本的ulua库文件载入了,同时也运行了Main.lua,所以导致了这个错误。

如何解决?可以通过统一的命名规范避免这个问题。曾经还想过根据dofile的name来为getluafunction开航,不过也是有问题的,就是require ,因为lua的require做的差不多也是dofile干的事情,这样就定位不准确了,容易出问题,所以放弃了,感觉还是规范更好些。

后来本人测试了下ulua协程,结果发现也是有问题,报错。红叉叉的看着真是不舒服。


先写一段coroutine的测试代码。发现在wait这里断掉了。 = =。

改改改!修修修!

coroutine.start 会自动将传入的函数,作为coroutine.create的参数创建一个新的协程,并立刻resume执行,进入到Test函数,输出 "a simple coroutine test",然后wait ,依靠CoTimer,计算时间,这里需要做一个处理:在c#中执行

    void Update()  
    {  
        if (mgr != null)  
        {  
            mgr.Update();  
        }  
    }  
      
    void LateUpdate()  
    {  
        if (mgr != null)  
        {  
            mgr.LateUpate();  
        }  
    }  

LateUpdate目的为的是执行CoUpdateBeat() ,这个函数是放在Main中的,不然cotimer不会更新,

Update目的是为了设置deltatime,不然lua中的deltatime会一直是0,影响定时器。

然后修改几处地方,在functions.lua中加入一个函数

看一下CoTimer

 

start的时候依赖的是CoUpdateBeat,而CoUpdateBeat是个Event,而Add正是在Event中声明的,并调用了functor,然后修改


中的44行,利用刚才定义的handler让xpacall,这样就将所有的有参函数,都统一变为了无形参(忽略隐藏参数self)函数,更重要的是解决了cfunc与luafunc的调用问题,可以参考quick-cocos2dx的做法。

再次修改

同样用handler去构造coTimer,以上为的是解决CoTimer:Update的self丢失问题。

最后运行!!!

这里的时间误差是因为lua中Time用的是os.clock,而wait则是根据unity来的,准确的说应该是根据帧率来的,1s在unity中已经走完,所以根据unity,这样是么问题的。

OVER~~~~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值