一:使用xLua的步骤

——导入xLua插件
其实xLua本质就是一个Unity工程,把Asset中的文件导入到Unity工程中就搞定了(导入之后编辑器菜单栏会扩展出一个XLua选项)
xLua(九)——实战_热更新

 


——添加宏
File——Build Settings——Player Settings——Other Settings——Scriptsing Define Symbols——输入HOTFIX_ENABLE——按下Enter
检测添加是否成功:选择之前菜单栏的XLua选项,发现多出了一个Hotfix Inject In Editor选项
xLua(九)——实战_Lua 热更新_02

 


——重新生成代码&重新注入代码
xLua(九)——实战_Lua 热更新_03
每当CS代码发生修改时,就必须点击重新生成代码(生成成功后控制台输出:finished! use XXX ms)
和将热修复补丁注入到Unity编辑器中(注入成功后控制台输出:XXX had injected!)

如果点击Hotfix Inject In Editor时报错:please install the Tools
则需要将xLua插件中的Tools文件夹也导入Unity工程中(注意与Assets文件夹同级)
xLua(九)——实战_Lua 热更新_04


二:xLua的一些坑

——没有导入Tools文件夹
如果报以下错误:please install the Tools
则需要将xLua插件中的Tools文件夹也导入Unity工程中(注意与Assets文件夹同级)
xLua(九)——实战_Lua 热更新_04


——Untiy工程目录不能带有中文
如果报以下错误:Exception: project path must contain only ascii character
则需要将项目放在一个没有中文的目录下
xLua(九)——实战_热更新_06


——没有重新生成代码&重新注入代码&没有添加热修复标签
如果报以下错误:LuaException: xlua.access, no field __Hotfix0_XXX
一种情况是没有重新生成代码并重新注入代码到Unity编辑器中
xLua(九)——实战_Lua 热更新_03
或者是没有给XXX类添加[Hotfix]的标签
xLua(九)——实战_Lua 热更新_08


——C#仍指向Lua虚拟机中某个function的委托
如果报以下错误:InvalidOperationException: try to dispose a LuaEnv with C# callback!
则需要使用xlua.hotfix(class, method, nil)删除(注入的时候是使用xlua.hotfix(class, method, func)注入的)
xLua(九)——实战_热更新_09
建议开发过程中单独写一个Lua脚本进行所有委托的销毁,将加载此脚本的方法写在释放Lua虚拟机的前面

大概说一下热补丁的实现原理,当我们给一个类打补丁后使用xlua.hotfix为类中的方法重写时,这个重写的Lua方法会注册到Lua虚拟机的一个委托中,当我们释放Lua虚拟机时,这个Lua方法并没有被释放所以会报错
关于热补丁的实现原理,推荐知乎上xLua作者写的:xLua热补丁实现原理
另外推荐一篇介绍中间语言IL的文章:中间语言IL


——访问成员方法时参数中没有传入调用者自身
如果报以下错误:LuaException: invalid arguments to XXX!
则说明XXX方法是一个成员方法,参数中需要传入调用者自身
例如SetParent方法是一个成员方法,可以使用冒号去访问或者在参数中传入调用者自身
xLua(九)——实战_Lua 热更新_10


——打上[LuaCallCSharp]或者[LuaCallCSharp]标签


——报一些奇怪的错误
删除Example文件夹,清空代码重新生成注入


三:打补丁的开发过程

——正常开发C#代码
——在所有可能出现问题的类上打上[Hotfix]标签(不能后期发布后再打标签)
——在所有Lua调用C#的方法上打上[LuaCallCSharp]标签,在所有C#调用Lua的方法上打上[CSharpCallLua]标签
——修改bug时只需要修改Lua文件,修改资源时只需要更新ab包,用户只需要去下载Lua文件和ab包即可


四:不同情况下补丁脚本的重写

——对于CS脚本中的大多数方法
例如在CS脚本中:
xLua(九)——实战_热更新_11
对应的Lua补丁脚本是这样的:

xlua.private_accessible(CS.Text)    --这样才可以使用类中的私有字段,属性,方法

xlua.hotfix(CS.Text,'Method',function(self,value)
    value=self.x	
    local _go=CS.UnityEngine.Object.Instantiate(self.prefabGo,self.UnityEngine.Vector3(value,0,0),CS.UnityEngine.Quaternion.identity)
    _go.transform:SetParent(CS.UnityEngine.GameObject.Find("Canvas").transform);
end)

 

 


——对于CS脚本中需要修改的地方在补丁脚本中执行,不需要修改的地方仍然在CS脚本中执行(这种方式尽量少使用,会降低性能)
例如在CS脚本中:
xLua(九)——实战_Lua 热更新_12
对应的Lua补丁脚本中我们希望go=GameObject.Find("MyGo").gameObject这句话仍然在CS脚本中执行,补丁脚本中只修改value的值:

local util=require 'util'

xlua.private_accessible(CS.Test) 
util.hotfix_ex(CS.Test,'Method',function(self)
	self:Method()
	self.value=5
end)

util是xLua提供的一个库,因为require的加载是加载同级目录的脚本,所以需要将util.lua.txt复制到补丁脚本的同级目录下
util.lua.txt的位置在xLua-master\Assets\XLua\Resources\xlua中

 

 

——对于CS脚本中的Start生命周期函数
有些时候我们为CS脚本的方法打补丁时,会发现补丁代码重写的方法并没有执行,仍然执行的是CS中的原方法,这是因为加载Lua脚本的代码(也就是补丁代码)没有最先被调用,而是在CS原方法执行后再被调用,这样的话补丁方法就失去了作用
例如将加载Lua补丁脚本的代码放在Start方法中,补丁代码重写的也是一个类中的Start方法,这样就有可能先执行CS原方法,补丁方法就失去了作用,简单工程中建议加载Lua补丁脚本的代码放在Awake中,加载Lua脚本的代码最好放在最最最最最前!xLua(九)——实战_Lua 热更新_13

 


——对于CS脚本中的Random.Range
首先转到Random.Range的定义,可以看出它是一个重载方法
xLua(九)——实战_热更新_14
接下来在CS脚本和Lua补丁脚本中测试,运行后可以得出xLua默认调用的是返回值为float的Random.Range方法,又因为CS访问Lua文件中的number类型时,会进行自动的类型转换(低类型可以自动转高类型,也就是可以给高类型的值赋予一个低类型的值)因为Lua中的数字类型统一为number,而C#有int,double,float等类型,例如用Lua文件中定义的一个本质为float类型的值去赋值给C#中的int类型的变量,则会输出为0),以上的测试结果也是如此,因为之前CS脚本中定义的num是一个int类型数字,而UnityEngine.Random.Range得到的是一个float类型数字,所以num会赋值为0
xLua(九)——实战_热更新_15

1.第一种解决方法:使用Unity中的向下取整函数将小数变为整数

xlua.hotfix(CS.Test,'Start',function(self)
    self.num=CS.UnityEngine.Mathf.FloorToInt(CS.UnityEngine.Random.Range(0,5))
end)

 

 

——对于CS脚本中的泛型方法
例如在CS脚本中:
xLua(九)——实战_Lua 热更新_16
对应的Lua补丁脚本是这样的:

xlua.hotfix(CS.Test,'Start',function(self)
    local _go=CS.UnityEngine.Object.Instantiate(self.go)
    _go:GetComponent('Rigibody').useGravity=false
end)

 

 

——补丁脚本中加载AB包资源(例如加载一个游戏物体)
首先前期在CS中写好扩展的接口,提供后期有需要的时候去调用:

using UnityEngine;
using System.Collections.Generic;

public class Extension
{
    private static Dictionary<string, GameObject> prefabDict;

    //加载资源
    public static void LoadResource(string abName, string resName)
    {
        AssetBundle ab = AssetBundle.LoadFromFile(@"Assets/MyAssetBundles/" + abName);
        GameObject _go = ab.LoadAsset<GameObject>(resName);

        if (prefabDict == null)
        {
            prefabDict = new Dictionary<string, GameObject>();
        }
        prefabDict.Add(resName, _go);
    }

    //得到物体
    public static GameObject GetGo(string resName)
    {
        return prefabDict[resName];
    }
}

之后在Lua补丁脚本中实例化这个物体

--在Start或者Awake中先加载出这个资源
CS.Extension.LoadResource('abName','resName')

--当需要的时候再实例化出来
local _go=CS.Extension.GetGo('resName')
CS.UnityEngine.Object.Instantiate(_go)

 

 

——对于无法预测的CS类(无法预测的功能)
例如当前有一个物体,我们无法预测它后期会添加什么功能,我们可以给它添加一个空类,空类里定义一些可能会用到的空方法:

using UnityEngine;
using XLua;

[Hotfix]
public class Empty : MonoBehaviour
{
    private void Start()
    {
        
    }

    private void Update()
    {
        
    }

    private void Method1()
    {

    }

    private void Method2()
    {

    }
}

之后需要的时候在Lua补丁脚本中重写打了补丁的空类中提供的方法:

xlua.private_accessible(CS.Empty)
xlua.hotfix(CS.Empty,'Start',function(self)

end)
xlua.hotfix(CS.Empty,'Update',function(self)	

end)
xlua.hotfix(CS.Empty,'Method1',function(self)

end)

 

 

——对于CS中的协程
例如在CS脚本中:
xLua(九)——实战_Lua 热更新_17
对应的Lua补丁脚本是这样的:

local util=require 'util'

xlua.hotfix(CS.Test,{
Start=function(self)
	return util.cs_generator(function()
		coroutine.yield(CS.UnityEngine.WaitForSeconds(5))	
		CS.UnityEngine.MonoBehaviour.print('Wait For 5 Seconds')
	end)
    end
})