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的文档,可以学习学习。

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

,然后在子文件夹全新新建一个c#文件,之后为了在c#中调用LuaEnterance.lua这个文件,得在c#中加入代码,同时引入命名空间使用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);

            //加载Unity3D资源            
        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;

        静态LuaStatic()
        {
            Load = DefaultLoader;
        }

        static byte [] DefaultLoader(string name)
        {
            byte [] str = null;
            string path = Util.LuaPath(name);

            使用(FileStream file = new FileStream(path,FileMode.Open))
            {
                str = new byte [(int)file.Length];
                file.Read(str,0,str.Length);
                file.Close();
            }

            返回str
        }

 

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

    /// <summary>
    ///取得Lua路径
    /// </ summary>
    public static string LuaPath(string name){
        string path = Application.dataPath +“/”;
        string lowerName = name.ToLower();
        if(lowerName.EndsWith(“。lua”)){
            返回路径+“lua /”+名称;
        }
        返回路径+“lua /”+名称+“.lua”;
    }

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

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

	void Start()
	{
		mgr = new LuaScriptMgr();
		mgr.Start();
		mgr.DoFile(ReviseLuaPath(“LuaEnterance.lua”));
	}

	public void DoLuaFile(string filepath)
	{
		if(mgr!= null)
			mgr.DoFile(ReviseLuaPath(filepath));
		其他
			Debug.Log(“DoLuaFile ERROR!Plz create LuaScriptMgr First”);
	}

	公共字符串ReviseLuaPath(字符串路径)
	{
		返回“../Scripts/”+路径;
	}

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

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

然后在C#中获取它

	私人LuaFunction funcUpdate;
	void Start()
	{
		mgr = new LuaScriptMgr();
		mgr.Start();
		mgr.DoFile(ReviseLuaPath(“LuaEnterance.lua”));
		funcUpdate = mgr.GetLuaFunction(“Update”);

	}

然后在更新中运行

	void Update()
	{
		if(mgr!= null)
		{
			funcUpdate.Call(Time.deltaTime);
		}
	}

结果发现什么都没有输出…醉了醉了。遇到问题那就查!

推测更新这个函数没有获取到。看一下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);
            }
            其他
            {
                Debuger.LogWarning(“Lua函数{0}不存在”,name);
            }

            LuaDLL.lua_settop(L,oldTop);            
        }
        其他
        {
            func.AddRef();
        }

        返回func作为LuaFunction;
    }

每次获取缓存的lua函数,会尝试从词典< string ,  LuaBase > dict中获取,然后这个键是根据你传入的名字而定的,那么问题就来了,不同文件下的同名函数怎么办?因为有些函数是乌卢阿内置的,都是在_G下的,有时候一不小心就可能命名一致,这里的问题也是因为这个导致的,乌卢阿中自带一个Main.lua的文件,这个LUA文件中有一个同名函数更新!!所以我们GetLuaFunction实际获得是这个函数!,因为其先被调用,并存在了字典中!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”);

#if!MULTI_STATE
        traceback = GetLuaFunction(“traceback”);
#万一        

        dofile处理( “Main.lua”);
        CallLuaFunction( “主”);
        updateFunc = GetLuaFunction(“Update”);
        lateUpdateFunc = GetLuaFunction(“LateUpdate”);
        fixedUpdateFunc = GetLuaFunction(“FixedUpdate”);
        levelLoaded = GetLuaFunction(“OnLevelWasLoaded”);
    }

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

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

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

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

改改改!修修修!

coroutine.start会自动将传入的函数,作为coroutine.create的参数创建一个新的协程,并立刻恢复执行,进入到测试函数,输出“一个简单的协同测试”,然后等待,依靠CoTimer,计算时间,这里需要做一个处理:在C#中执行

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

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

更新目的是为了设置的DeltaTime,不然LUA中的的DeltaTime会一直是0,影响定时器。

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

看一下CoTimer

开始的时候依赖的是CoUpdateBeat,而CoUpdateBeat是个事件,而添加正是在事件中声明的,并调用了仿函数,然后修改

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

再次修改

同样用处理器去构造coTimer,以上为的是解决CoTimer:更新的自我丢失问题。

最后运行!

这里的时间误差是因为LUA中时间用的是os.clock,而等待则是根据统一来的,准确的说应该是根据帧率来的,1S在团结中已经走完,所以根据统一,这样是么问题的。