tolua下载地址
https://github.com/jarjin/LuaFramework_NGUI https://github.com/jarjin/LuaFramework_UGUI
环境搭建
(1) 生成Wrap类
打开这个工程,生成注册文件:
这一步将Unity常用的C#类生成Warp类并注册到lua虚拟机中,这样在lua中就可以调用这些C#类了
这一步等效于,在Unity中的
完成这一步后,在Assets/LuaFramework/ToLua/Source/Generate目录看到许多Wrap类,
主要还是在CustomSetting.cs中生成的类
同理:增加自己的C#类也是这样
(2) 生成Lua Delegates和LuaBinder
生成的Warp类需要要LuaBinder中注册到lua虚拟机中,生成的lua委托需要在DelegateFactory中注册到lua虚拟机中,直接自动生成
执行逻辑
- 根据CustomSetting中的customDelegateList,生成lua委托并在DelegateFactory中注册到lua虚拟机中
- 根据CustomSettings中的customTypeList,生成Wrap类
- 在LuaBinder中生成Wrap类的注册逻辑
(3)解决报错问题
- 一个报错定位到代码处
在Generate All一下
- 定位到每个报错位置
- 另外几个依次如此
- 为防止下次Generate All重新生成这个报错,将这个脚本放到Assets/ToLua/BaseType下,并且在CustomSettings对应的_GT对应的类型注释掉
- 打包,注意这一步是为了跑main场景而做的工作,如果不想跑main场景,自己实现AB的话,下面的步骤可以不用解决
但是会有问题,都是一些找不到属性或者赋值给一个只读属性的问题,找到引用,不让这个属性注册进去即可,并且将对应的方法注释掉
例如:
打包成功后,示例场景main就可以跑起来了
主要脚本介绍
LuaBinder
用来自动注册生成的Wrap类
在这里插入图片描述
DelegateFactory
委托注册到lua虚拟机中
LuaState
lua虚拟机(解释器),
LuaLooper
为了实现Unity中MonoBehaviour中的生命周期函数。
注意:如果没有LuaLooper,则lua的协程无法执行
Lua库
tolua库,使用时tolua.xxxx
gettime
获取系统时间
typename
获取对象的类型名称
setpeer(a,b)
像继承,a继承b,可以实现Lua中table b扩展C#类a
实际上封装了setmetatable,可以给C#中的类和Lua的类做继承关系。
为C#a对象设置一个lua代理b(table),所有调用a的方法,都会调用b的同名函数,访问或者设置a的属性也会访问b属性
getpeer
获取替身
getfunction
获取函数
initset initget
初始化get,set“访问器”,为一个table的成员变量设置get和set访问器,实际上都返回了一个表,命名为gettable和settable,当访问table的变量时,会调用gettable里的同名函数(如果有),当设置table的变量时,会调用settable里的变量
int64 uint64
生成一个int64,uint64对象,相当于调用了int64库和uint64库的new方法。可以进行正常的加减乘除的运算。还有tomun2函数,返回两个数,第二个数是右移32为的值,第一个数是剩下的值(&0xFFFFFFFF)
UTF8,Lua使用
utf8.len(“字符串”)–返回长度
utf8.byte_indices(“字符串”)–返回的时迭代函数,配合for可以进行遍历字符串
utf8.count(“字符串”,“指定字符串”)–返回字符串中出现指定字符串的次数
utf8.replace(“原字符串”,“老的”,“新的”)–将字符串中老的字符串替换成新的
操作系统OS
os.clock()–返回当前运行时间
C#中使用lua
参考示例场景
1,简单使用
(1) 创建一个Lua解释器:LuaState lua = new LuaState();
(2) 创建全局变量lua[“num”] = 2;
(3) 创建表:lua.NewTable(“tableName”)
获取表:var tb = lua.GetTable(“tableName”);
配置表:tb[“a”] = 111;执行Lua脚本
DoFile是执行已经写好的Lua文件
DoString执行当前语句执行lua中的函数
注意,如果lua函数中使用了self的话,调用时需要把表传递过去,
例如:
xp.lua脚本:
XP={}
XP:Init()
self.age = 24
self.height = 178
end
Unity调用:
var lua = new LuaState()
lua.DoFile(xp.lua)
var table = lua.GetTable(“XP”)
table.GetLuaFunction(“Init”).Call(table);
//调用他不会隐式的把自身传过去,需要手动传递过去var func = lua.GetFunction(“函数名”);
func.Invoke<参数类型,返回类型>(“参数”);//只会返回一个参数
func.Call(“参数”);//无返回值
func.LazyCall(参数);//返回多个参数,用object[]存起来了,但是会产生gc allocfunc.BeginPCall();
func.Push(参数);
func.PCall();
func.CheckNumber()//获取参数1
func.CheckString()//获取参数2
其实Call和Invoke底层也是这样实现的,类比后,也可以实现多个参数多个返回在Lua中创建Unity对象
反射调用方式不推荐使用,因为效率太慢,推荐使用wrap类反射模式调用,
去反射最大的弊端在于提前需要把C#的类导入Lua中,如果上线发现有些类没有导入,反射就可以通过临时的调用未wrap的类,进行使用,当大版本更新时,再将此类加入warp,这时候反射就是解决这种情况出现,但是概率小反射调用:
string s = @"
tolua.loadassembly(‘Assembly-CSharp’) --加载程序集
local BindingFlags = require ‘System.Reflection.BindingFlags’–引入枚举
local t = typeof(‘Test’) --获得Type类
local func = tolua.getmethod(t, ‘方法名’)–得到对应的方法
func:Call(“参数名”);–调用方法
local obj = tolua.createinstance(t, 构造函数参数)–创建实例local property = tolua.getproperty(t, ‘属性名’)–得到属性
local num = property:Get(obj,null)–得到属性对应的值
property:Set(obj,444,null)–设置属性值local filed = tolua.getfield(t,‘字段名’)–获得字段反射
num =filed:Get(obj)–获得对应值
field:Set(obj,222)–设置值lua.DoString(s);
非反射:
都要CustomSetting中添加这个类,然后生成对于的Warp类,在调用之前先注册绑定
LuaBinder.Bind(lua)
string s = @"
local GameObject = UnityEngine.GameObject–获取类
local MeshRenderer = UnityEngine.MeshRenderer–获取类
local newGo = GameObject(‘obj’)–创建对象
local go = GameObject.Find(“Main”)–调用静态查找方法
newGo:AddComponent(typeof(MeshRenderer))–添加组件
";在lua中使用Unity携程
先在场景中添加LuaLooper脚本,在设置对应的lua虚拟机
looper = gameObject.AddComponent()
looper.luaState = lua
LuaCoroutine.Register(lua, this);
在Lua中使用Unity字典,枚举,List
在CustomSetting中添加对于的类型,在生成对应的Warp类,在Binder一下
在Lua中使用Unity中的委托
先在DelegateFactory的Reginster方法中注册对应的委托和委托方法
在调用DelegateTraits<委托类型>.Init(委托方法);
在调用DelegateFactory.Init();方法
委托方法参考:
Lua调用C#的扩展方法
在生成Warp类时做手脚
比如扩展类A对B类做了扩展方法XPMethod
在CustomSetting中BindTypes中修改一下
_GT(typeof(B)).AddExtendType(typeof(A))
这样生成Warp类时就会带上XPMethod这个方法在Lua使用JSON
先引入模块cjson,在使用
local json = require ‘cjson’
x = json.decode(“json字符串”)–反序列化
jsonStr = json.encode(x)–序列化
在Lua使用C#的String类型
跟调用其他类型,比如GameObject类型差不多,
如何查看在lua中能调用哪些方法?
在对应的Warp查看
在Lua中使用C#中的结构体
像Vector2,Vector3,byte这种其实LuaState内容已经实现了这种类似的机制
没有定义的结构体跟着样子定义即可
在Lua中实现MonoBehaviour以及互相通信
C#主动调用Lua
现在lua中定义一个创建类的函数class
在类中定义new方法生成对象
在lua中调用class函数生成Lua行为类
在Lua行为类定义好周期函数
在Unity中的Awake方法生成lua虚拟机以及加载需要的lua脚本
并在Awake方法中获取Lua行为类并调用new方法获得Lua行为对象存起来
在Unity的周期函数中,通过Lua行为对象获取其中的周期函数并调用。
注意调用时一定要把Lua行为对象传递进去
public class TestBehaviour : MonoBehaviour
{
LuaState lua;
LuaTable luaObject;
LuaFunction startMethod, updateMethod;
private void Awake()
{
lua = new LuaState();
lua.Start();
LuaBinder.Bind(lua);
lua.DoString(@"
function class(classname,super)
local cls = {}
local superType = type(super)
if(superType~='table') then
superType = nil
super = nil
end
if(super) then
setmetatable(cls,{__index = super})
cls.super = super
end
cls.name = classname
cls.__index = cls
function cls:new()
instance = setmetatable({},cls)
instance.class = cls
instance:ctor()
return instance
end
return cls
end");
lua.DoString(@"
LuaBehaviour = class('LuaBehaviour')
function LuaBehaviour:ctor()
--这里可以增加实例化GameObject
--并给这个GameObject增加C#Lua行为类
--并调用里面的方法将self这个对象传递过去
end
function LuaBehaviour:Start(obj)
self.gameObject = obj
print('Start方法' .. self.gameObject.name)
end
function LuaBehaviour:Update()
print('Update方法' .. self.gameObject.name)
end");
}
void Start()
{
var classL = lua.GetTable("LuaBehaviour");
luaObject = classL.GetLuaFunction("new").Invoke<GameObject,LuaTable>(gameObject);
startMethod = luaObject.GetLuaFunction("Start");
if (startMethod != null)
startMethod.Call(luaObject,gameObject);
}
void Update()
{
if (updateMethod != null)
updateMethod.Call(luaObject);
else
updateMethod = luaObject.GetLuaFunction("Update");
}
}
将上面的脚本挂载在场景中即可,也可以通过DoString中的字符串放入文件中,通过DoFile也可以,如果要实现lua脚本与lua脚本之间的通信,将luaObject弄成public的,在将这个类注册一下生成Warp类以及bind,在Lua脚本中通过GameObject.Find(‘Test’):GetComponent(typeof(TestBehaviour)).luaObject
获取对应的对象,得到这个Lua行为对象后,操作这个对象就可以实现lua脚本之间的通信
Lua来控制
上面那个方法有个弊端,那就是lua行为对象是在Unity周期函数中主动调用的去获取,而不是被动的获取,一般场景中开始是没有具体的行为的,具体的行为是通过Lua来创建GameObject时传递对象给C#。
稍微改变上面脚本,C#Lua行为类开始时就不要挂载在场景中,通过Lua脚本去调用生成具体的行为,也不要在周期函数获取对象了,在类中添加一个设置lua行为对象的方法
在生成Warp类以及Bind
然后在Unity的资源加载完成,并将lua脚本加载进去
将lua Lua行为类的构造函数修改一下
改变调用Lua方式
将下面这个脚本挂载在场景中
public class Test : MonoBehaviour
{
LuaState lua;
private void Awake()
{
lua = new LuaState();
lua.Start();
LuaBinder.Bind(lua);
lua.DoString(@"
function class(classname,super)
local cls = {}
local superType = type(super)
if(superType~='table') then
superType = nil
super = nil
end
if(super) then
setmetatable(cls,{__index = super})
cls.super = super
end
cls.name = classname
cls.__index = cls
function cls:new()
instance = setmetatable({},cls)
instance.class = cls
instance:ctor()
return instance
end
return cls
end");
lua.DoString(@"
LuaBehaviour = class('LuaBehaviour')
function LuaBehaviour:ctor()
self.gameObject = UnityEngine.GameObject('test') --这里实例化GameObject
self.behaviour = self.gameObject:AddComponent(typeof(TestBehaviour))--添加脚本
self.behaviour:SetLuaObject(self)--传递表过去
end
function LuaBehaviour:Start()
print('Start方法' .. self.gameObject.name)
end
function LuaBehaviour:Update()
print('Update方法' .. self.gameObject.name)
end
");
lua.DoString(@"
--创建
luaBehaviour = LuaBehaviour:new()
");
}
}
这个脚本开始的时候就不要添加到场景了,通过lua脚本去自动添加,记得要生成对应的Warp类以及Bind
public class TestBehaviour : MonoBehaviour
{
LuaTable luaObject;
LuaFunction startMethod, updateMethod;
void Start()
{
startMethod = luaObject.GetLuaFunction("Start");
if (startMethod != null)
startMethod.Call(luaObject,gameObject);
}
// Update is called once per frame
void Update()
{
if (updateMethod != null)
updateMethod.Call(luaObject);
else
updateMethod = luaObject.GetLuaFunction("Update");
}
public void SetLuaObject(LuaTable luable)
{
this.luaObject = luable;
}
}