目录
1. Xlua
1. 简介
- Xlua下载地址:https://github.com/Tencent/xLua 在右侧 Releases 中获取
- 导入包中 Assets 文件夹下的Plugins和XLua
2. 基本使用
- LuaEnv 类用于在Unity中运行lua代码
private LuaEnv luaEnv;
void Start()
{
// 创建 Lua 虚拟机
luaEnv = new LuaEnv();
// 运行 lua 代码
luaEnv.DoString(@"print('Hello xlua')");
// 运行 Unity 代码
luaEnv.DoString(@"CS.UnityEngine.Debug.Log('Hello Unity')");
}
private void OnDestroy()
{
// 释放
luaEnv.Dispose();
}
- 从Resources目录下可加载lua代码并执行,因为lua后缀文件并不能让unity识别,所以后缀需改为
lua.text
- 利用lua指令
require 'path'
,可在Resources目录下自动加载后缀为lua.text
的文件
//创建 LuaEnv 类
LuaEnv luaEnv = new LuaEnv();
// 1. 加载并运行
TextAsset ta = Resources.Load<TextAsset>("hello");
luaEnv.DoString(ta.text);
// 2. 自动加载运行
luaEnv.DoString("require 'hello'");
3. 通过Loader自定义require加载路径
- 使用方法 AddLoader 添加加载路径函数,返回值为脚本数据,入参为 require 路径
- 使用
File.ReadAllText(path)
获取路径下脚本文本,使用System.Text.Encoding.UTF8.GetBytes(data)
将文本转化为字节数组
void Start()
{
// 创建 LuaEnv 类
LuaEnv luaEnv = new LuaEnv();
luaEnv.AddLoader(MyLoader);
luaEnv.DoString("require 'stream'");
// 释放
luaEnv.Dispose();
}
private byte[] MyLoader(ref string filePath)
{
string path = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";
print(path);
string data = File.ReadAllText(path);
return System.Text.Encoding.UTF8.GetBytes(data);
}
4. C#访问Lua
- 获取Lua中的全局变量
luaEnv.Global.Get<T>(string)
// 必须先运行脚本,内存中生成变量后方可获取数值
// 创建 LuaEnv 类
LuaEnv luaEnv = new LuaEnv();
// 2. 自动加载运行
luaEnv.DoString("require 'hello'");
// 仅当为整数时可正常获取,否则返回0
int iValue = luaEnv.Global.Get<int>("value");
float fValue = luaEnv.Global.Get<float>("value");
string data = luaEnv.Global.Get<string>("data");
bool flag = luaEnv.Global.Get<bool>("flag");
- 获取Lua中的table
- 用类获取(获得的是复制份,修改并不能更改原表)
LuaData luaData = luaEnv.Global.Get<LuaData>("table"); public class LuaData { public int value; public string data; public bool flag; }
- 接口(接口为public且添加特性CSharpCallLua,若发现仍无法正常运行,可在 Xlua 栏下 -> Clear Generated Code -> Generated Code)
ILuaData luaData = luaEnv.Global.Get<ILuaData>("table"); luaData.value = 234; luaData.say(2); [CSharpCallLua] public interface ILuaData { int value { get; set; } string data { get; set; } bool flag { get; set; } void say(int time); }
table = { value = 100, data = "liluo", flag = false, } function table:say(time) print(self.data .. "第" .. time .. "次" .. "说") end
- 字典和列表
// 返回键值对数据 Dictionary<string, object> luaData = luaEnv.Global.Get<Dictionary<string, object>>("table"); // 返回单纯的列表数据 List<object> luaList = luaEnv.Global.Get<List<object>>("table");
- LuaTable
LuaTable tab = luaEnv.Global.Get<LuaTable>("table"); print(tab.Get<string>("data")); print(tab.Get<int>("value")); print(tab.Get<bool>("flag")); foreach (var key in tab.GetKeys()) print(tab.Get<string, object>(key.ToString()));
5. C#访问Lua中的全局function
- Action 方式
luaEnv= new LuaEnv();
Action<string> act = luaEnv.Global.Get<Action<string>>("Add");
act("I say");
act = null;
// 若采用自定义委托
[CSharpCallLua]
delegate int Add(int a, int b);
function Add(data)
print(data)
end
- 自定义委托
需为委托添加特性 CSharpCallLua
Add act = luaEnv.Global.Get<Add>("Add");
int a, b;
// 额外的返回值用out接收
print(act(2, 3, out a, out b));
print(a + ", " + b);
act = null;
// 应为public,若发现仍无法正常运行,可 Generated Code
[CSharpCallLua]
public delegate int Add(int a, int b, out int resA, out int resB);
function Add(a, b)
return a + b, a, b
end
- LuaFunction
// 创建 LuaEnv 类
luaEnv = new LuaEnv();
LuaFunction func = luaEnv.Global.Get<LuaFunction>("Add");
// 获得返回值列表
object[] os = func.Call(1, 2);
foreach (object data in os)
{
print(data);
}
function Add(a, b)
return a + b, a, b
end
6. Lua访问C#
-- 新建对象
CS.UnityEngine.GameObject()
CS.UnityEngine.GameObject("obj")
-- 访问成员属性和方法
print(CS.UnityEngine.Time.deltaTime)
CS.UnityEngine.Time.timeScale = 0.5
local camera = CS.UnityEngine.GameObject.Find("Main Camera")
camera.name = "luaupdate"
CS.UnityEngine.GameObject.Destroy(camera:GetComponent("Camera"))
注意:对于非静态对象,调用成员方法,第一个参数需要传该对象,建议用冒号语法糖,如下
testobj:DMFunc()
2. Xlua热更新
1. 启用热更新
- 想要启用热更新,首先要在项目设置的玩家中添加脚本定义符号 HOTFIX_ENABLE 并应用,这时Xlua选单中就会额外出现一个 Hotfix Inject In Editor (注意:各平台需要分别设置)
- 添加 Xlua 中的 Tools 文件夹到 Assets 同级目录下
- 重新生成lua代码 (Xlua 栏下 -> Clear Generated Code -> Generated Code)
- 点击Xlua选单中的 Hotfix Inject In Editor 注入代码到编辑器
- 添加程序集:将
Unity\2020.3.8f1c1\Editor\Data\Managed
路径下的Unity.Cecil.Pdb.dll
Unity.Cecil.Mdb.dll
Unity.Cecil.dll
三个配置文件加入工程的Assets\XLua\Src\Editor
目录下
2. 补丁开发过程
- 首先开发业务代码
- 在所有可能出现问题的类上打上hotfix的标签,在所有lua调用CSharp的方法上打上LuaCallCSharp,在所有CSharp调用Lua的方法上打上CSharpCallLua
- 修改bug时只需要更新Lua文件,修改资源时(声音,模型,贴图,图片,UI)只需要更新ab包。用户只需要去下载lua文件跟ab包。
3. 补丁应用实例
-
注意:补丁注册必须要在所有脚本之前,否则无法保证补丁应用成功!(比如对Start函数的修改)
-
想要添加补丁,需要为想要修改的类添加 Hotfix 热更新特性,再为将要修改的函数添加 LuaCallCSharp 特性,该类便可被热更新捕获
编写lua代码,调用函数xlua.hotfix(CS.class, 'functionName', function(self))
更新函数,其中class为添加 Hotfix 热更新特性的类,functionName为添加 LuaCallCSharp 特性的函数名,function为替换的函数,self为类本身[Hotfix] public class Treasour : MonoBehaviour { public GameObject gold; public GameObject diamands; public Transform cavas; [LuaCallCSharp] private void CreatePrize() { for (int i = 0; i < 5; i++) { GameObject go = Instantiate(gold, transform.position + new Vector3(-10f + i * 30, 0, 0), transform.rotation); go.transform.SetParent(cavas); GameObject go1 = Instantiate(diamands, transform.position + new Vector3(0, 30, 0) + new Vector3(-10f + i * 30, 0, 0), transform.rotation); go1.transform.SetParent(cavas); } } }
xlua.hotfix(CS.Treasour, 'CreatePrize', function(self) local GameObject = CS.UnityEngine.GameObject for i = 0, 4, 1 do local go = GameObject.Instantiate(self.gold, self.transform.position + CS.UnityEngine.Vector3(-10 + i * 100, 0, 0), self.transform.rotation); go.transform:SetParent(self.cavas) local go1 = GameObject.Instantiate(self.diamands, self.transform.position + CS.UnityEngine.Vector3(0, 30, 0) + CS.UnityEngine.Vector3(-10 + i * 100, 0, 0), self.transform.rotation); go1.transform:SetParent(self.cavas); end end)
-
使用完毕后,在 Dispose 虚拟机前应释放所有引用,hotfix引用可用
xlua.hotfix(CS.class, 'functionName', function(nil))
进行释放 -
若发生访问不到的私有成员,可用方法
xlua.private_accessible(CS.class)
来允许访问 -
若想要在原本函数的基础上进行扩展,即实现类似override的功能,需执行以下几步:
- 引入Xlua中的扩展包util (在路径
Assets/XLua/Resources/xlua/util.lua.txt
下) - lua中引入包
local util = require "util"
- 使用函数
util.hotfix_ex(CS.class, 'functionName', function(self))
原本的函数可用self:Update()
来调用。 - 解除引用仍使用
xlua.hotfix(CS.class, 'functionName', function(nil))
进行释放
- 引入Xlua中的扩展包util (在路径
xlua.private_accessible(CS.Treasour)
-- data 是函数的入参
util.hotfix_ex(CS.Treasour, 'Do', function(self, data)
print('say by lua')
self:Update()
end)
4. 函数重载问题
在有差别仅为int
和float
函数重载的函数中(如Range()
函数),由于传入参数时二者皆可用int入参,Lua默认数值为float类型,导致入参被误判为float,返回float数值,而此时接受参数的c#变量为int,以至于无法正确接受,导致结果变0,无法得到正确的结果。
解决方案:
对于随机数取整,可用 UnityEngine.Mathf.FloorToInt()
将返回值转换成一个最接近的整数。
5. 热更新报错:xlua.access,no field __Hotfix0_Update
重新打包加载热更新c#代码即可
6. 标识热更新的简介办法
通过反射,在一个静态类的静态字段或属性里配置一个列表,属性可以用于实现的比较复杂的配置,比如根据命名空间做白名单。
public static List<Type> by_property
{
get
{
// 反射对应C#程序集
return (from type in Assembly.Load("Assembly-CSharp").GetTypes()
// 命名空间
// where type.Namespace == "XXXX"
select type).ToList();
}
}