tolua下载地址
https://github.com/jarjin/LuaFramework_NGUI https://github.com/jarjin/LuaFramework_UGUI

环境搭建

(1) 生成Wrap类

打开这个工程,生成注册文件:

unity插入Tolua unity tolua教程_unity

这一步将Unity常用的C#类生成Warp类并注册到lua虚拟机中,这样在lua中就可以调用这些C#类了

这一步等效于,在Unity中的

unity插入Tolua unity tolua教程_unity_02

完成这一步后,在Assets/LuaFramework/ToLua/Source/Generate目录看到许多Wrap类,

主要还是在CustomSetting.cs中生成的类

unity插入Tolua unity tolua教程_unity_03

同理:增加自己的C#类也是这样

(2) 生成Lua Delegates和LuaBinder

unity插入Tolua unity tolua教程_开发语言_04

生成的Warp类需要要LuaBinder中注册到lua虚拟机中,生成的lua委托需要在DelegateFactory中注册到lua虚拟机中,直接自动生成

unity插入Tolua unity tolua教程_开发语言_05

执行逻辑

  1. 根据CustomSetting中的customDelegateList,生成lua委托并在DelegateFactory中注册到lua虚拟机中
  2. 根据CustomSettings中的customTypeList,生成Wrap类
  3. 在LuaBinder中生成Wrap类的注册逻辑

(3)解决报错问题

  1. 一个报错定位到代码处

在Generate All一下

unity插入Tolua unity tolua教程_lua_06

  1. 定位到每个报错位置
  2. unity插入Tolua unity tolua教程_unity插入Tolua_07

  3. 另外几个依次如此
  4. 为防止下次Generate All重新生成这个报错,将这个脚本放到Assets/ToLua/BaseType下,并且在CustomSettings对应的_GT对应的类型注释掉
  5. 打包,注意这一步是为了跑main场景而做的工作,如果不想跑main场景,自己实现AB的话,下面的步骤可以不用解决
  6. unity插入Tolua unity tolua教程_unity_08

但是会有问题,都是一些找不到属性或者赋值给一个只读属性的问题,找到引用,不让这个属性注册进去即可,并且将对应的方法注释掉

例如:

unity插入Tolua unity tolua教程_unity_09

打包成功后,示例场景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

参考示例场景

unity插入Tolua unity tolua教程_unity_10

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();方法

委托方法参考:

unity插入Tolua unity tolua教程_c#_11

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#中的结构体

unity插入Tolua unity tolua教程_unity插入Tolua_12

像Vector2,Vector3,byte这种其实LuaState内容已经实现了这种类似的机制

unity插入Tolua unity tolua教程_unity_13


unity插入Tolua unity tolua教程_c#_14


unity插入Tolua unity tolua教程_unity_15


没有定义的结构体跟着样子定义即可

在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行为对象的方法

unity插入Tolua unity tolua教程_unity插入Tolua_16


在生成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;
    }
}