LuaFramework 热更新流程:
- Main脚本调用整个游戏的启动函数Startup
- 游戏管理器GameManager生成
- GameManager调用CheckExtractResource函数,检查“数据目录”是否存在
- 如果“数据目录”不存在,说明是初次运行游戏,就将“游戏包资源目录”的内容解压缩到“数据目录”
- 如果“数据目录”存在,就检查是否需要从服务器下载资源,GameManager调用OnUpdateResource函数下载“网络资源地址”上的files.txt,然后与“数据目录”中文件的md5码做比较,更新有变化的文件
- 更新完成后,GameManager调用OnResourceInited函数,启动Lua状态机,游戏开始
调试:在Main.lua加入调试语句,在LuaManager的StartMain访问热更新增的Lua脚本,生成新打包的Prefab
AppConst类的UpdateMode设为true,则从指定服务器下载资源,否则从本地“数据目录”获取。LuaBundleMode设为true,则从AssetBundle解压Lua脚本,否则直接读取项目脚本。
可以打开AssetsLuaFrameworkEditorCustomSettings.cs看到所有可以供lua调用的类
添加新的Lua脚本到AssetBundle包:
将Lua脚本存放到 Assets/LuaFramework/Lua 目录,因为,当按下LuaFramework-Build XXX Resources的时候,框架会自动将Assets/LuaFramework/Lua下的所有内容打成AssetBundle包,放在StreamingAssets下,所以可以发现,StreamingAssets文件夹下会有个Lua文件夹,里面放的就是我们Assets/LuaFramework/Lua在打包之后的结果
注意1:打开StreamingAssets/Lua,会发现,这个文件夹下大致分为两部分,3rd文件夹,和一堆资源包
其中,资源包的命名特点是对应着路径信息的,例如lua_3rd_cjson和lua_3rd_cjson.unity3d这一组资源包,他对应着的是LuaFramework/Lua/3rd/cjson里面的Lua文件,告诉使用者,我这组资源包就是这个文件夹下的lua文件打包出来的结果,那么我们的 打包出来的资源包又是谁呢?那就是: 这一组资源包
注意2:我们会发现StreamingAssets/Lua文件夹下,除了有一堆资源包外,还有一个3rd文件夹,里面放的又是啥呢?其实,我们可以打开Assets/LuaFramework/Lua/3rd 目录,然后打开其中一个,例如cjson的文件夹,可以发现,这文件夹里面除了放置一些lua文件外,还有一些其他的txt配置文件或说明文件,所以,这个StreamingAssets/Lua/3rd文件夹下,放的就是这些不是lua文件的文件资源注意3:综上所述,如果我们要打包一个自定义的Lua文件(不是框架提供的Main.lua文件)的话,那么我们完全可以先在Assets/LuaFramework/Lua这个文件夹下,自定义一个专门放我们编写的Lua文件的文件夹,然后当打包出来后,会发现,StreamingAssets/Lua下会有一个你自定义的文件夹名.unity3d这么一组资源包,例如:
这里的MyLua以及底下的MyLua文件是我自定义的,然后当我打包出来后,会发现StreamingAssets/Lua这个文件夹下多了一个与之对应的一组资源包
注意4:
更改Lua入口,不用框架提供给我们的Main.lua这个文件:
找到LuaManger这个C#类,然后找到StartMain()这个方法,只需要把lua.DoFile里面的参数修改为你自定义的lua文件名,然后LuaFunction main=lua.GetFunction(“Main”)这一行的括号内的参数,修改为作为入口且存在于你自定义的lua文件中的lua方法即可,当然,
这段代码是可以删掉的,因为不管有没有设定入口函数,系统都会把DoFile设置的lua文件从头到尾来读,而之所以作者这样写,感觉是为了让Lua代码写得更好看而已,因为你一开始就调用Main函数的话,那么你就可以把你要运行的lua代码通通写到这个Main函数中,这样的话,看起来lua代码不会那么散乱吧!但是不管是不是自己定义lua入口文件,最好都放在Assets/LuaFramework/Lua下,不要再另外加文件夹,因为放在Assets/LuaFramework/Lua这路劲下的所有lua文件都会直接一同打包在lua.unity3d这么一个资源包中,而程序设置好就是去这个包里读取lua的入口文件,除非你会改动lua入口文件的读取路径,不然就安分一些!注意5:可以读取多个lua文件
创建新的AssetBundle包的步骤:
LuaFramework/Build XXX Resources的功能全都写在了Assets/LuaFramework/Editor/Packager.cs这个C#类中,以及跟随打包生成的StreamingAssets/files这个MD5校验码文件是怎么生成的,也是写在这个Packager.cs文件中,所以,以后你如果想打包一些这个框架没提供的平台资源的话,你可以到这个Packager模仿着去增加对应的打包方法
4.x : 在定义了打包函数的脚本 Assets/LuaFramework/Editor/Packager.cs 添加新增资源包的信息
5.x : 在Inspector设置资源包名,在Packager.cs添加5.x的打包函数调用
因为这个文档是更新于2018/4/27的,所以这里讲的是这个日期所对应的最版本的新framework的打包:
注意1:
打包函数的主体代码在Packager.cs中的BuildAssetResource,HandleExampleBundle,AddBuildMap,HandleLuaBundle,HandleLuaFile这五个方法
作用:
BuildAssetResource是进行资源文件夹的刷新,并且根据AppConst这个类里面的静态布尔属性的设置的情况的不同,分别调用HandleExampleBundle,HandleLuaBundle,HandleLuaFile函数
HandleExampleBundle,他的作用是告诉使用者怎么打包美术素材,他的框架格式是什么,如果你有想打包的美术资源,你可以根据这个格式自定义一个例如叫(HandleMyWorkBundle)的方法,来进行打包,这个AddBuildMap方法里面的参数意思:
第一个参数:打包出来的资源叫什么名字,这个名字是用AppConst.ExtName(值为unity3D)作为后缀名
第二个参数:你要打包的这个资源本来是什么格式的资源,*.XXX代表所有这种格式的资源
第三个参数:这个框架是对指定路径的文件夹进行整体打包操作的,所以,第三个参数是,你要打包的资源位于项目哪个文件夹中
所以综上所述,如果你要打包美术资源,请先在项目中创建一个专门存放你自己的美术资源的文件夹,然后在Packager.cs中新建一个函数,里面的代码请复制这个HandleExampleBundle的,然后修改AddBuildMap参数,在完成上述步骤后,因为你的项目打包出来之前,目测是会删掉那些框架提供的事例场景的,所以要把AppConst中的ExampleMode设置为false,最后在Packager.cs这里加上拉黑的代码,调用你的自定义打包函数
HandleLuaBundle,HandleLuaFile这两个函数是告诉用户怎么将Lua文件打包成AssetBundle的,通常状况下不建议修改,因为功能已经很齐全了,当然,如果你想将这种功能实现应用到如Xlua中,可以去看看这两个函数是怎么写的。
因为在我看来,所谓的热更新主要由三部分组成:
1:C#与Lua的互相调用机制
2:Lua文件的打包
3:网络传输,校验
所谓的XLua,ToLua,ULua…只是提供了C#与Lua的互相调用机制,并没有提供其余两者的功能实现,所以如果自己想开发个基于如Xlua这样的热更新框架,可以参照这个基于ToLua的框架
1:打包出来可以发现会有个files的文件,这个文件记录了所有lua资源包和美术素材资源包的MD5码,客户端的files的MD5码会与这个每次打包一次都会更新的MD5码进行比对,发现有不同的MD5码就会自动进行对这个MD5码所对应的资源进行更新操作
2:每个美术素材资源包都会有一个对应的依赖信息文件,这个依赖信息文件记录的是这个美术素材包的共用材质/贴图在哪个美术素材包中(所谓的共用美术素材,指代的是,例如两个prefab,他们共用一个材质,所以你不但要打包两个prefab,还要打包他们所共用的美术素材,而这个共用的美术素材打包出来后就是一个共用美术素材包)
例如:在上面的截图中可以看到,我的Cube和Sphere共用的材质是ShareMaterial,而材质所用的贴图是MyTexture
因为这个框架是对文件夹整体打包的,所以打包出来的结果分别为myterials/mymaterials.unity3d,myprefabs/ myprefabs.unity3d和myprefabs/myprefabs.unity3d这三组结果,“/“左边是记录素材包依赖信息的文本文件(可以用文本编辑器打开看),“/”右边是真正的美术资源包
依赖信息的文本文件如图:
图中的Assets代表的是,你这打包出来的东西的来源
Dependecies代表的是,你这打包出来的东西又依赖于哪个打包出来的东西
使用框架对下载的资源进行加载:
在此之前,你要明白为何这个框架可以让你的lua脚本访问到C#的类的,当你按下Lua/Clear wrap Files的时候,会弹出这么个框框
此时,你必须要按下确定,那么这个框架就会自动给你生成一堆Wrap结尾的C#文件,如下图所示例如LuaFramework_AppConstWrap,那么这个自动生成的文件所对应的就是一个叫AppConst的类,如此类推,而这个框架就是让Lua通过访问这些自动生成的wrap的类来达到访问C#类的目的(注意:这些自动生成的wrap类不要去更改)
接下来我们讲一下资源的加载和生成,在这里我会讲述三种加载方法
1:自定义加载函数,供Lua访问
步骤1:首先在ResourceManager文件下创建截图中的东西
(注释已经说明了这些我自己写的代码的作用)
此时,你会搜索出一组C#类,然后打开ResourceManager所对应的自动生成的Wrap文件(也就是拉黑的第一个C#文件)
请注意这部分代码,可以发现,你在ResourceManager新增的LoadPrefab方法已经存在于这个L.RegFunction中了,这意味着,你可以在Lua中调用你这个自定义的加载方法了,如果想更详细地知道,Lua所调用的实质是啥,可以看看这段代码:
在我看来,这个框架将你C#的方法,以Lua能够读懂的形式又重新弄成对应的代码,如上图,然后放在对应的Wrap中同时也在Wrap中用L.RegFunction去注册让Lua能够访问,所以Lua真正要访问的,其实是这个重新生成的方法
现在我们已经更改了ResourceManager并且也生成了与其对应的供Lua调用的Wrap包,那么接下来,我们就在Lua中用ResourceManager中的我们自定义的LoadPrefab方法去从指定的下载下来的AssetBundle包中把指定的美术素材生成在Scene视图中,Lua代码如下:
在这里,我们的C#代码调用方式是“命名空间.类名”+如果是实例方法,就“:方法名(参数列表)”,如果是静态方法或构造方法就“.方法名(参数列表)/.New(参数列表)”,当然,考虑到我们的自定义C#脚本有些是没有命名空间的,所以没有命名空间的C#类就不用把命名空间加上(后面会说怎么利用这框架使得lua也能使用我们的自定义C#脚本),在这里我们要使用到框架提供的一个C#类,叫LuaHelper,他可以让我们快速获得C#的类的实例,其中包括各种继承了Mono的类(如这些框架提供的XXXManager)
接着,我们看到,在把Cube游戏对象从myprefabs.u3d的AssetBundle包中解压出来之前,首先要解压它所依赖的美术素材,因为Cube依赖于ShareMaterial,ShareMaterial依赖于MyTexture。所以加载的顺序是MyTexture->ShareMaterial->Cube,这样的顺序加载才不会让Cube的材质丢失
最后我们来看看效果:
2:直接利用框架的加载函数,用Lua访问
第一种方法其实是旧版框架的做法,起码2017年刚学这框架就是这样做的,当然在最新版的框架,这做法还是能行得通的,但是,现在是2018/4/27的最新版的框架,所以这里介绍另外一种做法:
同样的,也是在ResourceManager中,框架提供了3个重载的LoadPrefab方法
第一个参数:你要加载的AssetBundle包的名字
第二个参数:你要加载的是这个AssetBundle包中的哪些美术素材
第三个参数:回调方法(这个方法的参数列表有一个UnityEngine.Object[]类型的变量的,这个数组变量存的就是你所加载的美术资源)
Lua中的调用方式:
注意,这里的go是userdata类型,遍历的时候必须用Length去取得go的长度才行,不然会报错
最终结果:
3.与第一种加载方式的代码一样,只不过,我们不把加载函数写在ResourceManager中,而是新建一个C#文件,把加载方法写在这个C#文件中,然后通过CunstomSetting去使得框架生成对应的Wrap文件,然后用Lua去调用
步骤1:创建脚本,这里我创建了一个叫MyCSharp的脚本
步骤2:找到CustomSetting这个框架提供的类,把你自定义的MyCSharp注册到这里:
步骤3:按下Lua/Clear wrap files -> 确定 或者Lua Generate All生成wrap文件,生成wrap文件后,搜索MyCSharp,你会发现,搜索结果多了一个与MyCSharp文件对应的Wrap
打开这个wrap,看看Lua能够调用的方法
可以发现,这里有个New方法也是可以供Lua调用的,这个New方法是让Lua新建对象用的,可以理解为Lua的构造方法接着,我们看看Lua代码应该怎么写,如图:
结果:
使用NetBox在本地搭建一个服务器程序,然后利用这个框架,本地资源更新到本地,模拟整套热更新流程:步骤1:下载NetBox
然后,你这个Netbox所在的文件夹就会成为默认的服务器资源文件夹,客户端是会从这服务器资源文件夹下载资源来更新的步骤2:开启NetBox,直接双击
就可以开启,当开启NetBox后,这里会有黑色箭头
步骤3:验证NetBox真正开启:
在
所处的目录新建一个html后缀的网页文件
我这里是个叫index.html,然后用文本编辑器打开这个index.html文件,在里面写一些html代码用以网页显示,如:
然后,你开启NetBox后,浏览器会自动打开来到这个index.html
当然,你也可以自己手动在浏览器地址栏打上localhost:80来访问,访问成功则代表NetBox服务顺利开启步骤4:更改框架提供的AppConst设置
ExampleMode设置为false,意思是什么,前面的关于加载资源的部分已经说过,请仔细阅读
将UpdateMode设置为true,意思为从WebUrl提供的网址进行加载
将LuaBundle设置为true,意思为,程序所读的Lua脚本是从资源包中解压出来的Lua脚本,
而不是 这里的脚本直接读取
WebUrl则填上你的服务器资源文件夹物理路径(物理路径指代的是如:C:/XXXX/XXX)所映射成的网址,因为我使用的是NetBox,所以他会把他所在的这个物理路径:
自动映射为http://localhost:80/,所以我在这里填上了这网址
步骤5:因为你修改了框架的AppConst,所以你要按下Lua/Clear wraps Files->确定,为了安全起见,在编译完成后,再按下Lua/Generate All
步骤6:为了测试热更新,你必须要有一个原版客户端,但是又由于你是基于这个框架开发的项目,也就是说,你在项目仍在开发阶段就已经把这框架导入了,所以,你在导出原版客户端之前,先按下LuaFramework/Build XXX Resource,让框架把你项目中用到的资源打包到StreammingAssets文件夹
然后你再File->BuildSetting,将你的项目导出为原版客户端,之所以要先按下LuaFramework/Build XXX Resource,是因为这个框架所提供的一些代码资源,和files文档也是必须要先存在于你的客户端的,特别是files,你以后的热更新就是根据这个files文件的MD5码与服务器资源文件夹的files的MD5码进行比对,看哪些资源MD5码不同,就下载更新哪个资源的
做完上述一切后,我Buil出了这么个客户端
此时,你留意一下你的C盘的根目录,你会发现,暂时是不存在一个叫luaframework的文件夹的
现在,我们打开这个Build出来的客户端,看看有何效果
首先,你会发现C盘根目录底下会多出个luaframework文件夹,这文件夹是会在客户端运行的时候,自动生成在客户端一则的(根据不同平台而不同),因为我这个客户端是PC平台,所以就自动默认生成在这里,而他的作用,是先把你的原版客户端的资源复制一份搁在这里
然后根据files的MD5码去和服务器资源文件夹的files的MD5码进行对比,以此来判断应该从服务器的资源文件夹下载更新哪些资源到我的客户端上然后你又会发现,客户端打开后
这里毛都没有一条,那是因为,你还没把原版的StreammingAssets下的打包好的资源放在服务器资源文件夹里,那就意味着,客户端的luaframework文件夹中的file的MD5码不能和服务器的file的MD5码进行比对了,所以才会出现这情况,解决方法是
把拉黑部分,通通复制到你的服务端资源文件夹
此时,重新打开你的客户端,你会发现,你的客户端又可以正常运行了
步骤7:更改原版Lua代码,然后热更新到这个Build出来的原版客户端上
在这里,我修改的是项目的Main.lua文件,
本来Lua代码是这样的
然后我修改成:
此时,我们按下LuaFramework/Build XXX Resoure
此时,你会发现,你新Build出来的资源中的files文件的这个Main.lua所在的资源包和其他Lua资源包所对应的MD5码,与原版的会有所不同,而关于美术的资源包的对应的MD5码却没有变化,如图:(左图为客户端MD5码,右图为新打包的MD5码)
拉黑部分和其下方的是Lua资源包的MD5码,会发现都改变了,拉黑部分上方是美术资源包的MD5码
然后,我们再把新Build出来的
覆盖掉,服务器资源文件夹的原版的打包资源
最后,重新打开客户端看效果,可以发现,热更新成功: