找回密码
 立即注册
查看: 322|回复: 0

xLua简单热更新的实现

[复制链接]
发表于 2021-8-13 13:49 | 显示全部楼层 |阅读模式
最近又自学了一下xLua的热更新,过程中也是踩坑无数。感觉以后应该会用的上,所以就在这里插个眼留一下。
个人认为,热更的原理和实现并不难,难的就是写lua的过程,毕竟lua这玩意坑很多。博主写lua并不很熟练,所以学的时候也是踩了不少坑- -。
毕竟我也是刚学习没多久,如果有错误还望指出纠正。
言归正传。就稍微讲一下xLua的简单热更实现。
1,xLua的环境搭建


lua的环境搭建就先不提了。
从git上下载xLua的压缩包, 这里是链接
下载完以后解压,把Asset里的Plugins和XLua文件挪到你unity项目的Asset目录下,把和Asset文件同级的Tools文件挪到unity项目中和Asset同一目录下
接下来就是注入宏
File–BuildSettings–PlayerSettings–Other Settings
在Scripting Define Symbols里输入“HOTFIX_ENABLE”,回车等待编译完成。


接下来就进行一个小测试。
建一个新的场景,新建一个脚本。
  1. using UnityEngine;using XLua;//加上这个命名空间publicclassThisLuaTest:MonoBehaviour{LuaEnv tmpLua;voidStart(){
  2.         tmpLua =newLuaEnv();
  3.         tmpLua.DoString("");}// Update is called once per framevoidUpdate(){}privatevoidOnDestroy(){
  4.         tmpLua.Dispose();}}
复制代码
DoString里是写lua语句,这里暂时先空着,下面会有补充。
在Asset下新建一个文件夹Resources(如果你原来没有的话),因为lua脚本默认是放在Resources文件夹下的。右键打开文件夹,进入Resources文件夹里。


在Resources文件夹下新建一个文本文档,重命名为ThisLuaTest.lua.txt(实际上叫什么无所谓,.tex前面要加上.lua后缀)


保存一下。回到c#脚本,补充DoString里的内容。


保存完成以后回到unity,进行很重要的一步工作
如果你修改过C#的代码,就要先Generate Code一下,等待编译完成;然后再Hotfix Inject In Editor


出现 inject finish 就是注入成功了。


脚本随便挂载了一个物体上,运行。


Bingo。第一步完成。
如果DoString的括号里面直接是"print(‘好吃的食物呢’)",效果也是一样的。
2,xLua热更前的准备


搭建一个简单的计算器,以来实现计算的功能。


编写脚本,挂载到panel上,
  1. using UnityEngine;using UnityEngine.UI;publicclassThisOne:MonoBehaviour{privateInputField tmpOne;privateInputField tmpTwo;privateText tmpResult;privateButton tmpAdd;privateButton tmpClear;voidStart(){Url();//计算按钮的回调
  2.         tmpAdd.onClick.AddListener(AddListen);//清除按钮的回调
  3.         tmpClear.onClick.AddListener(Clear);}voidUrl(){
  4.         tmpOne = transform.Find("OneNum").GetComponent<InputField>();
  5.         tmpTwo = transform.Find("TwoNum").GetComponent<InputField>();
  6.         tmpResult = transform.Find("Result").GetComponent<Text>();
  7.         tmpAdd = transform.Find("Calculate").GetComponent<Button>();
  8.         tmpClear = transform.Find("Clear").GetComponent<Button>();}voidAddListen(){float s =float.Parse(tmpOne.text)+float.Parse(tmpTwo.text);
  9.         tmpResult.text = s.ToString();
  10.         Debug.Log("计算结果为"+ s);}voidClear(){
  11.         tmpOne.text =null;
  12.         tmpTwo.text =null;
  13.         tmpResult.text ="?";
  14.         Debug.Log("清掉了");}voidUpdate(){}}
复制代码
按钮的话,实际上也可以直接写个lambda函数,但后面有个坑我也不知道怎么解决,就只能这样调用了。
保存,运行一下看看效果。
点了一下Add,计算出了结果,是32。

再点一下Claer,就清掉了


说明我们原本的方法是没有问题的。
3,开始用xLua实现简单热更


首先,我们在ThisOne的脚本上,添加命名空间 using XLua
在需要热更的类上,打上[Hotfix]标签(官方是推荐用白名单的方法,不过那种方法我还没来及去学习,这种方法比较简单一些),在你需要修改的方法上,添加[LuaCallCSharp]的标签。
这些工作,是要在你发布项目以前就需要做好的。把可能修改的地方都打上这些标签。
我们在修改的类 ThisOne上面打上 [Hotfix]标签,在修改的两个方法上打上[LuaCallCSharp],如图。



保存,回到unity工程中,新建一个脚本,用来加载lua脚本,并挂载在任意物体上。
  1. using UnityEngine;using XLua;using System.IO;publicclassXluaResources:MonoBehaviour{LuaEnv tmpLua;privatevoidAwake(){
  2.         tmpLua =newLuaEnv();
  3.         tmpLua.AddLoader(LoadLua);//执行lua语句,此处是加载这个xlua文件 //如果不放在streamingAsset文件下的话,默认是在Resources里
  4.         tmpLua.DoString("require'LoadLua'");}voidStart(){}//把lua文件放到streamingAsset文件夹里publicbyte[]LoadLua(refstring file){//注意;streamingAsset文件只能放在Asset的根目录下//如果说在streamingAsset文件中还有子文件,加上子文件夹的名字string filePath = Application.streamingAssetsPath +"/"+ file +".lua.txt";return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(filePath));}voidUpdate(){if(tmpLua !=null){
  5.             tmpLua.Tick();}}privatevoidOnDestroy(){//最后退出的时候销毁lua环境
  6.         tmpLua.Dispose();
  7.         Debug.Log("再销毁环境");}}
复制代码
AddLoader是用来打开非Resources文件夹下的文件(我也不知道这样理解是不是对的- -),因为这里我没有把lua文件放到Resources文件夹下,而是放到了streamingAsset文件夹下。个人认为把lua文件放到streamingAsset文件夹里比放到Resources文件夹下要好。从服务器端下载补丁的时候直接下到streamingAsset文件夹里拿过来用也方便。这个是个人的一些理解。如果有不对的地方还望指出。
Application.streamingAssetsPath + “/” + file + “.lua.txt”
这句话是指把lua文件从streamingAsset文件夹中找到。file就是指的文件名。
在streamingAsset文件夹下,先建立一个 LoadLua.lua.txt的文件。这个是用来加载lua文件的。刚才的脚本里的DoString里,就是加载的这个lua文件。
然后创建ModificationOne.lua.txt,专门写我们要打补丁lua文件。
xlua.hotfix的意思,是用来替换c#的方法。
xlua.hotfix(class, method, func),这是基本格式。
class写要修改的类,method写方法,func写要用lua替换的方法。
  1. print('热更新进来了')
  2. xlua.hotfix(CS.ThisOne,'AddListen',function(self)local s=self.tmpOne.text-self.tmpTwo.text
  3.            self.tmpResult.text=s
  4.           print('这是修改后的'..s)end)
  5. xlua.hotfix(CS.ThisOne,'Clear',function(self)
  6.            self.tmpOne.text=nil
  7.        self.tmpTwo.text=nil
  8.            self.tmpResult.text="@"print('这是Lua的清除')end)
复制代码
保存lua文件,再打开LoadLua.lua.txt这个文件,用来加载我的lua文件。


保存,回到unity。
Generate Code和Hotfix Inject不要忘了。如果是热更相关的c#代码做了修改,就要执行这些步骤;如果是lua脚本的修改,就不需要这样。
点击运行unity。


我们的lua文件已经是加载进来了。
随便的输入一些数字点击Add验证一下


(30+2怎么可能是28那,肯定是错了!wwwwww)
看样子我们的lua方法已经替代了原来的c#方法。
再来试一下Clear。


也是一样,把原来的方法给替换掉了。
这就是lua热更的原理了。
4,停止unity后Lua报错的处理


到这里简单的热更算是基本实现了。为什么说是基本那。
我们来停止一下unity。


我们会注意到报错了。
我来看看官方给的解释(学习lua中有什么问题可以先去看一下官方给的解释。 这里是链接)


简单的说,我们虽然执行了热更,但结束unity的时候,我们释放了lua环境以前,没有滞空那些我们热更的补丁,才导致这样的情况。
解决这个问题也很简单。
在streamingAsset文件夹中,新建一个EndHotfix.lua.txt的文件夹,编写滞空补丁的lua语句。
  1. xlua.hotfix(CS.ThisOne,'AddListen',nil)
  2. xlua.hotfix(CS.ThisOne,'Clear',nil)print('滞空补丁')
复制代码
(我也不知道理解为把补丁滞空这种说法对不对,我觉得这样比较适合我自己的理解。)
保存,回到加载lua的c#脚本中,在OnDestroy方法前加上OnDisable的方法。滞空lua补丁的操作不能够在OnDestroy里执行,要不然lua环境销毁了,但lua补丁还没有执行完毕,也会报错。所以要在OnDestroy前执行完滞空补丁的操作,再销毁lua环境。


保存脚本,回到unity,再次Generate Code和Hotfix Inject。
启动unity后可以直接选择停止来验证一下。


好了,红色的报错解决了。
以上就是lua热更的基本。关于写lua,这些只能自己慢慢的去练习了。
5,搭建虚拟环境模仿从服务器下载资源


既然lua热更我们学会了,接下来我们来试着搭建一个虚拟的环境来模仿从服务器端下载资源。
在这里我用到的是,我直接放一下我的百度云链接吧。
链接:https://pan.baidu.com/s/1S6_tpHfX5sZeV77g291bMg
提取码:5k6d
我是在unity项目外创建了一个新的文件夹。这个名字就随便了。把下载后的压缩包里东西,解压到这里。


新建一个文本文档,随便编辑一些内容(实际上打中文也可以,但我不知道为什么我用网页打开是乱码,就用英文了。)


保存,重命名为 index.html。
把我们在unity项目中streamingAsset文件夹中的lua文件挪到这个文件夹中,同级就行。


streamingAsset里的文件就可以删除了。
首先,我们要先启动这个软件。如果可以自动打开我们刚才创建的网页,就说明是启动了虚拟环境。


首先,我们回到unity,保存刚才的场景,并且新建一个加载场景。


编写slider的移动脚本,挂载在panel上。
  1. publicclassLoadScenes:MonoBehaviour{privateSlider tmpSlider;privateAsyncOperation tmpAsync ;voidStart(){
  2.         tmpSlider = transform.Find("Slider").GetComponent<Slider>();
  3.         tmpAsync =null;StartCoroutine(Load());}//设置进度条(适用于从加载界面到登录界面privatevoidSetLoadingValue(float v){
  4.         tmpSlider.value= v /100;}IEnumeratorLoad(){int displayProgress =0;//当前进度值float toProgress =0;//当前进条想要达到的值
  5.         tmpAsync = SceneManager.LoadSceneAsync("Calculate");
  6.         tmpAsync.allowSceneActivation =false;while(tmpAsync.progress <0.9f){
  7.             toProgress = tmpAsync.progress *100;while(displayProgress < toProgress){++displayProgress;SetLoadingValue(displayProgress);yieldreturnnewWaitForEndOfFrame();//等待当前帧}}
  8.         toProgress =100;//从90%追到100%while(displayProgress < toProgress){++displayProgress;SetLoadingValue(displayProgress);yieldreturnnewWaitForEndOfFrame();}
  9.         Debug.Log("进度条走完了");
  10.         tmpAsync.allowSceneActivation =true;}voidUpdate(){}}
复制代码
然后再新建一个脚本,用来下载我们的lua文件。
  1. using System.Collections;using UnityEngine;using UnityEngine.Networking;using System.IO;publicclassDownLoadLua:MonoBehaviour{voidStart(){StartCoroutine(LoadLua());}//利用软件搭建一个虚拟的网络环境,模仿从服务器端下载补丁//一般要实现的地方和下载lua补丁,要分场景,不然当补丁下载到本地以前c#就已经去执行加载lua命令了,从而报错IEnumeratorLoadLua(){//找到本机的下载路径UnityWebRequest tmpUnity = UnityWebRequest.Get(@"http://localhost/LoadLua.lua.txt");// 创建一个下载yieldreturn tmpUnity.SendWebRequest();//下载文本文件string tmpText = tmpUnity.downloadHandler.text;//下载到streamingAsset里
  2.         File.WriteAllText(Application.streamingAssetsPath+"/LoadLua.lua.txt",tmpText);UnityWebRequest tmpUnity1 = UnityWebRequest.Get(@"http://localhost/ModificationOne.lua.txt");yieldreturn tmpUnity1.SendWebRequest();string tmpText1 = tmpUnity1.downloadHandler.text;
  3.         File.WriteAllText(Application.streamingAssetsPath +"/ModificationOne.lua.txt", tmpText1);UnityWebRequest tmpUnity2 = UnityWebRequest.Get(@"http://localhost/EndHotfix.lua.txt");yieldreturn tmpUnity2.SendWebRequest();string tmpText2 = tmpUnity2.downloadHandler.text;
  4.         File.WriteAllText(Application.streamingAssetsPath +"/EndHotfix.lua.txt", tmpText2);}voidUpdate(){}}
复制代码
前面提到个人感觉把lua文件放到streamingAsset文件夹中是比较好的选择,这里下载的话也是直接下载到streamingAsset里,这样不管是lua文件的读取路径,还是下载路径,就不用去写一个死的绝对地址。
保存,挂载到任意物体上。
还是别忘了Generate Code和Hotfix Inject。
这时候,别急着启动unity,要不然会报错。(博主忘了截图了- -,反正就是会报null的错误,提示吗没有加入这个场景。)


通过这个Add Open Scenes,添加我们创建的场景,这样就不会报错了。
把加载场景设置为0,把计算场景设置为1.
最后,我们启动unity,进行测试。


Bingo。
lua文件已经是下载到streamingAsset文件夹下了。
可能你打开streamingAsset文件夹还是空的,这时候右键Refresh下,就看到了。既然lua文件已经下载了,那么后面的工作也可以正常的进行了。
我在下载lua文件的地方,加了句注释。“一般要实现的地方和下载lua补丁,要分场景,不然当补丁下载到本地以前c#就已经去执行加载lua命令了,从而报错”
这也是我为什么要单独弄个场景,去下载lua文件。
最开始的时候,我是把下载lua文件的脚本挂载计算的界面上,测试的时候发生了报错。我的猜想应该是,还没有下载完lua文件,c#加载lua文件的语句就已经开始执行了,导致报错。
游戏在热更的时候,都是在登录的时候让你进行热更下载,然后再进入游戏。
差不多就这些了吧。关于热更。
但毕竟没有真正的服务器端让我去练习,我也没去过一些什么大公司。毕竟咱也是进unity没多长时间的萌新而已(也不萌wwww),我感觉原理应该是和这个差不多的
----------------------------------------关于热更的一个补充----------------------------------------------------
2020.11.14日
在其他的地方翻到了一个关于方法的补充的功能。即,如果一个方法只是需要去补充,而不是需要重写的话,就可以用这种方法。
首先,先创建一个空的场景,新建一个Cube。


写一个脚本,挂载在GameObject上,实现Cube的移动。
  1. using UnityEngine;publicclassTestTwo:MonoBehaviour{Transform tmpGame;publicfloat tmpFloat=10f;voidStart(){
  2.         tmpGame = transform.Find("Cube");}voidUpdate(){CubeMove();}voidCubeMove(){if(Input.GetKey(KeyCode.W)){
  3.             tmpGame.transform.Translate(Vector3.forward * Time.deltaTime * tmpFloat, Space.Self);}if(Input.GetKey(KeyCode.S)){
  4.             tmpGame.transform.Translate(Vector3.back * Time.deltaTime * tmpFloat, Space.Self);}if(Input.GetKey(KeyCode.A)){
  5.             tmpTrans.transform.Translate(Vector3.left * Time.deltaTime * tmpFloat, Space.Self);}if(Input.GetKey(KeyCode.D)){
  6.            tmpTrans.transform.Translate(Vector3.right * Time.deltaTime * tmpFloat, Space.Self);}}}
复制代码
可以自行去验证一下效果。
接下来就是写lua了。
(这里就先不放在streamingAsset里了,先放在Resources里)
新建一个Resources文件夹,右键打开,新建一个txt文档,重命名为This.lua.txt。
下一步在unity中搜索 util 这个文件,把它复制到你刚刚创建的Resources文件夹下。


(这里有两个是因为我已经把这个文件夹复制到Resources里了,所以回搜到两个)
开始写lua了。
  1. print('Lua热更进来啦~')local UnityEngine=CS.UnityEngine
  2. local Vector3=CS.UnityEngine.Vector3
  3. --首先先加载这个文件(要拖到加载lua文件夹中local util = require 'util'--这里就不用xlua.hotfix
  4. util.hotfix_ex(CS.TestTwo,'CubeMove',function(self)--先调取要补充的方法
  5.       self:CubeMove(self)if(UnityEngine.Input.GetKey(UnityEngine.KeyCode.A))then
  6.         self.tmpGame.transform:Translate(Vector3.left*UnityEngine.Time.deltaTime*self.tmpFloat,UnityEngine.Space.self)print('这是lua的一个左移动')endif(UnityEngine.Input.GetKey(UnityEngine.KeyCode.D))then
  7.         self.tmpGame.transform:Translate(Vector3.right*UnityEngine.Time.deltaTime*self.tmpFloat,UnityEngine.Space.self)print('这是lua的一个右移动')endend)
复制代码
在这里,就不要你用xlua.hotfix了,用util.hotfix_ex
并且为了区分移动,加了个print
在补充方法以前,要先调用需要补充的方法。
这里我是补充了一个A和D的左右移动,写完以后就把C#中AD控制移动的代码给注释掉。
然后再新建一个lua文件,命名为ThisClear.lua.txt
这个脚本是用来滞空补丁的
  1. print('滞空补丁')
  2. xlua.hotfix(CS.TestTwo,'CubeMove',nil)
复制代码
新建一个脚本,用来加载lua以及销毁lua环境等。
  1. using UnityEngine;using XLua;publicclassThisTwo:MonoBehaviour{LuaEnv luaEnv;privatevoidAwake(){
  2.         luaEnv =newLuaEnv();
  3.         luaEnv.DoString("require'This'");}voidStart(){}// Update is called once per framevoidUpdate(){if(luaEnv !=null){
  4.             luaEnv.Tick();}}privatevoidOnDisable(){
  5.         luaEnv.DoString("require'ThisClear'");}privatevoidOnDestroy(){
  6.         luaEnv.Dispose();}}
复制代码
接下来就是给要修改的类打上标签,方法打上标签,并且注释掉一些代码。



同样为了区分,也加了些debug。
保存脚本,加载lua的脚本随便挂载在某物体上。
等待编译,并且不要忘了GenerateCode和HotfixInjetc。
运行,随便移动一下


ok,这一块就简单的实现了。

后话:写lua的话,实际上也还是多去练习,一开始的时候可以先去写c#,然后翻译成lua。不熟练的时候可以这样做。不通过c#去翻译成lua,而是直接的就去写,也需要一定时间去练习。目前博主去通过lua翻译原来的c#的东西,问题不很大,脱离c#去写lua的话,感觉还是有点不太好下手。
用纯lua去写,这方面也不很熟练。
反正还有挺长的路要走。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-31 12:08 , Processed in 0.094832 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表