xLua简单热更新的实现

最近又自学了一下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”,回车等待编译完成。
在这里插入图片描述
接下来就进行一个小测试。
建一个新的场景,新建一个脚本。

using UnityEngine;
using XLua;//加上这个命名空间

public class ThisLuaTest : MonoBehaviour
{
    LuaEnv tmpLua;
    void Start()
    {
        tmpLua = new LuaEnv();
        tmpLua.DoString("");
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void OnDestroy()
    {
        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上,

using UnityEngine;
using UnityEngine.UI;

public class ThisOne : MonoBehaviour
{
    private InputField tmpOne;
    private InputField tmpTwo;
    private Text tmpResult;
    private Button tmpAdd;
    private Button tmpClear;

    void Start()
    {
        Url();

        //计算按钮的回调
        tmpAdd.onClick.AddListener(AddListen);
        //清除按钮的回调
        tmpClear.onClick.AddListener(Clear);
    }

    void Url()
    {
        tmpOne = transform.Find("OneNum").GetComponent<InputField>();
        tmpTwo = transform.Find("TwoNum").GetComponent<InputField>();
        tmpResult = transform.Find("Result").GetComponent<Text>();
        tmpAdd = transform.Find("Calculate").GetComponent<Button>();
        tmpClear = transform.Find("Clear").GetComponent<Button>();
    }

    void AddListen()
    {
        float s = float.Parse(tmpOne.text) + float.Parse(tmpTwo.text);
        tmpResult.text = s.ToString();
        Debug.Log("计算结果为" + s);
    }

    void Clear()
    {
        tmpOne.text = null;
        tmpTwo.text = null;
        tmpResult.text = "?";
        Debug.Log("清掉了");
    }

    void Update()
    {

    }
}

按钮的话,实际上也可以直接写个lambda函数,但后面有个坑我也不知道怎么解决,就只能这样调用了。
保存,运行一下看看效果。

点了一下Add,计算出了结果,是32。在这里插入图片描述
再点一下Claer,就清掉了
在这里插入图片描述
说明我们原本的方法是没有问题的。

3,开始用xLua实现简单热更


首先,我们在ThisOne的脚本上,添加命名空间 using XLua
在需要热更的类上,打上[Hotfix]标签(官方是推荐用白名单的方法,不过那种方法我还没来及去学习,这种方法比较简单一些),在你需要修改的方法上,添加[LuaCallCSharp]的标签。
这些工作,是要在你发布项目以前就需要做好的。把可能修改的地方都打上这些标签。
我们在修改的类 ThisOne上面打上 [Hotfix]标签,在修改的两个方法上打上[LuaCallCSharp],如图。
在这里插入图片描述
在这里插入图片描述
保存,回到unity工程中,新建一个脚本,用来加载lua脚本,并挂载在任意物体上。

using UnityEngine;
using XLua;
using System.IO;
public class XluaResources : MonoBehaviour
{
    LuaEnv tmpLua;
    private void Awake()
    {
        tmpLua = new LuaEnv();
        tmpLua.AddLoader(LoadLua);
        //执行lua语句,此处是加载这个xlua文件 
        //如果不放在streamingAsset文件下的话,默认是在Resources里
        tmpLua.DoString("require'LoadLua'");
    }
    void Start()
    {
       
    }
    //把lua文件放到streamingAsset文件夹里
    public byte[] LoadLua(ref string file)
    {
        //注意;streamingAsset文件只能放在Asset的根目录下
        //如果说在streamingAsset文件中还有子文件,加上子文件夹的名字
        string filePath = Application.streamingAssetsPath + "/" + file + ".lua.txt";
        return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(filePath));
    }
    void Update()
    {
        if (tmpLua != null)
        {
            tmpLua.Tick();
        }
    }
    private void OnDestroy()
    {
        //最后退出的时候销毁lua环境
        tmpLua.Dispose();
        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替换的方法。

print('热更新进来了')
xlua.hotfix(CS.ThisOne,'AddListen',function(self)
	  local s=self.tmpOne.text-self.tmpTwo.text
	   self.tmpResult.text=s
	  print('这是修改后的'..s)
end
)

xlua.hotfix(CS.ThisOne,'Clear',function(self)
	   self.tmpOne.text=nil
       self.tmpTwo.text=nil
	   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语句。

xlua.hotfix(CS.ThisOne,'AddListen',nil)

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上。

public class LoadScenes  : MonoBehaviour
{
    private Slider tmpSlider;
    private AsyncOperation tmpAsync ;
    void Start()
    {
        tmpSlider = transform.Find("Slider").GetComponent<Slider>();
        tmpAsync = null;

        StartCoroutine(Load());
    }

    //设置进度条(适用于从加载界面到登录界面
    private void SetLoadingValue(float v)
    {
        tmpSlider.value = v / 100;
    }
    IEnumerator Load()
    {
        int displayProgress = 0;//当前进度值
        float toProgress = 0;//当前进条想要达到的值
        tmpAsync = SceneManager.LoadSceneAsync("Calculate");
        tmpAsync.allowSceneActivation = false;

        while (tmpAsync.progress < 0.9f)
        {
            toProgress = tmpAsync.progress * 100;
            while (displayProgress < toProgress)
            {
                ++displayProgress;
                SetLoadingValue(displayProgress);
                yield return new WaitForEndOfFrame();//等待当前帧
            }
        }
        toProgress = 100;
        //从90%追到100%
        while (displayProgress < toProgress)
        {
            ++displayProgress;
            SetLoadingValue(displayProgress);
            yield return new WaitForEndOfFrame();
        }
        Debug.Log("进度条走完了");
        tmpAsync.allowSceneActivation = true;
    }

    void Update()
    {
        
    }
}

然后再新建一个脚本,用来下载我们的lua文件。

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
public class DownLoadLua : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(LoadLua());
    }

    //利用软件搭建一个虚拟的网络环境,模仿从服务器端下载补丁
    //一般要实现的地方和下载lua补丁,要分场景,不然当补丁下载到本地以前c#就已经去执行加载lua命令了,从而报错
    IEnumerator LoadLua()
    {
        //找到本机的下载路径
        UnityWebRequest tmpUnity = UnityWebRequest.Get(@"http://localhost/LoadLua.lua.txt");
       // 创建一个下载
        yield return tmpUnity.SendWebRequest();
        //下载文本文件
        string tmpText = tmpUnity.downloadHandler.text;
        //下载到streamingAsset里
        File.WriteAllText(Application.streamingAssetsPath+ "/LoadLua.lua.txt",tmpText);

        UnityWebRequest tmpUnity1 = UnityWebRequest.Get(@"http://localhost/ModificationOne.lua.txt");
        yield return tmpUnity1.SendWebRequest();
        string tmpText1 = tmpUnity1.downloadHandler.text;
        File.WriteAllText(Application.streamingAssetsPath + "/ModificationOne.lua.txt", tmpText1);

        UnityWebRequest tmpUnity2 = UnityWebRequest.Get(@"http://localhost/EndHotfix.lua.txt");
        yield return tmpUnity2.SendWebRequest();
        string tmpText2 = tmpUnity2.downloadHandler.text;
        File.WriteAllText(Application.streamingAssetsPath + "/EndHotfix.lua.txt", tmpText2);
    }

    void Update()
    {
        
    }
}

前面提到个人感觉把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的移动。

using UnityEngine;
public class TestTwo : MonoBehaviour
{
    Transform tmpGame;
    public float tmpFloat=10f;
    void Start()
    {
        tmpGame = transform.Find("Cube");
    }


    void Update()
    {
        CubeMove();
    }

    void CubeMove()
    {
        if (Input.GetKey(KeyCode.W))
        {
            tmpGame.transform.Translate(Vector3.forward * Time.deltaTime * tmpFloat, Space.Self);
        }

        if (Input.GetKey(KeyCode.S))
        {
            tmpGame.transform.Translate(Vector3.back * Time.deltaTime * tmpFloat, Space.Self);
        }

        if (Input.GetKey(KeyCode.A))
        {
            tmpTrans.transform.Translate(Vector3.left * Time.deltaTime * tmpFloat, Space.Self);
        }

        if (Input.GetKey(KeyCode.D))
        {
           tmpTrans.transform.Translate(Vector3.right * Time.deltaTime * tmpFloat, Space.Self);
        }
    }
}

可以自行去验证一下效果。

接下来就是写lua了。
(这里就先不放在streamingAsset里了,先放在Resources里)
新建一个Resources文件夹,右键打开,新建一个txt文档,重命名为This.lua.txt。
下一步在unity中搜索 util 这个文件,把它复制到你刚刚创建的Resources文件夹下。
在这里插入图片描述
(这里有两个是因为我已经把这个文件夹复制到Resources里了,所以回搜到两个)
开始写lua了。

print('Lua热更进来啦~')
local UnityEngine=CS.UnityEngine
local Vector3=CS.UnityEngine.Vector3

--首先先加载这个文件(要拖到加载lua文件夹中
local util = require 'util'
--这里就不用xlua.hotfix
util.hotfix_ex(CS.TestTwo,'CubeMove',function(self)
	--先调取要补充的方法
      self:CubeMove(self)
	if(UnityEngine.Input.GetKey(UnityEngine.KeyCode.A)) then
	self.tmpGame.transform:Translate(Vector3.left*UnityEngine.Time.deltaTime*self.tmpFloat,UnityEngine.Space.self)
print('这是lua的一个左移动')
	end

	if(UnityEngine.Input.GetKey(UnityEngine.KeyCode.D)) then
	self.tmpGame.transform:Translate(Vector3.right*UnityEngine.Time.deltaTime*self.tmpFloat,UnityEngine.Space.self)
print('这是lua的一个右移动')
	end
end
)

在这里,就不要你用xlua.hotfix了,用util.hotfix_ex
并且为了区分移动,加了个print
在补充方法以前,要先调用需要补充的方法。
这里我是补充了一个A和D的左右移动,写完以后就把C#中AD控制移动的代码给注释掉。
然后再新建一个lua文件,命名为ThisClear.lua.txt
这个脚本是用来滞空补丁的

print('滞空补丁')

xlua.hotfix(CS.TestTwo,'CubeMove',nil)

新建一个脚本,用来加载lua以及销毁lua环境等。

using UnityEngine;
using XLua;
public class ThisTwo : MonoBehaviour
{
    LuaEnv luaEnv;
    private void Awake()
    {
        luaEnv = new LuaEnv();
        luaEnv.DoString("require'This'");

    }
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (luaEnv != null)
        {
            luaEnv.Tick();
        }
    }

    private void OnDisable()
    {
        luaEnv.DoString("require'ThisClear'");
    }

    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}

接下来就是给要修改的类打上标签,方法打上标签,并且注释掉一些代码。
在这里插入图片描述
在这里插入图片描述
同样为了区分,也加了些debug。
保存脚本,加载lua的脚本随便挂载在某物体上。

等待编译,并且不要忘了GenerateCode和HotfixInjetc。
运行,随便移动一下
在这里插入图片描述
ok,这一块就简单的实现了。


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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值