Unity ToLua 使用教程

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中生成的类
![在这里插入图片描述](https://img-blog.csdnimg.cn/8f5f5813fefd430e8a288bd1e1d55af5.png

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

(2) 生成Lua Delegates和LuaBinder

在这里插入图片描述

生成的Warp类需要要LuaBinder中注册到lua虚拟机中,生成的lua委托需要在DelegateFactory中注册到lua虚拟机中,直接自动生成
在这里插入图片描述

执行逻辑

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

(3)解决报错问题

  1. 一个报错定位到代码处
    在这里插入图片描述

在Generate All一下
在这里插入图片描述

  1. 定位到每个报错位置
    在这里插入图片描述
    另外几个依次如此

  2. 为防止下次Generate All重新生成这个报错,将这个脚本放到Assets/ToLua/BaseType下,并且在CustomSettings对应的_GT对应的类型注释掉

  3. 打包,注意这一步是为了跑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 alloc

func.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;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44806700/article/details/122347896