ToLua框架

news2024/10/7 15:26:30

ToLua 是一个用于在 Unity 中为 Lua 提供 C# 语言绑定的框架。通过 ToLua,你可以方便地将 C# 代码暴露给 Lua 脚本,并在 Lua 脚本中调用 C# 类、方法和属性。

更新流程

原理:使用AssetBundle进行资源的更新,而由于lua运行时才编译的特性,所以lua文件也可以被看成是一种资源文件(与fbx、Image等一样)可以打进ab包中

流程:

游戏运行时从服务器下载files.txt清单文件,与本地的files.txt清单文件进行对比。如果新下载的files里面的md5值与本地files的md5值不一样,或者本地清单里没有对应文件,那么就从服务器下载AB包,然后解压进行初始化

LuaState

C#能调用lua的原理就是创建了一个lua虚拟机,LuaState封装了对lua 主要数据结构 lua_State 指针的各种堆栈操作。

去反射

旧版本中lua调用C#函数是基于反射的,而现阶段是基于去反射,意思就是:把所有的c#类的public成员变量、成员函数,都导出到一个相对应的Wrap类中。而这些成员函数通过特殊的标记,映射到lua的虚拟机中,当在lua中调用相对应的函数时候,直接调用映射进去的c# wrap函数,然后再调用到实际的c#类,完成调用过程

Lua虚拟栈

在这里插入图片描述

C#会告诉虚拟机传入的参数和需要返回的参数,然后虚拟机开始访问栈,从栈中取出对应函数传送给Lua编译器。然后lua程序调用Lua文件全局表(Global table)查找对应函数,然后将返回参数入栈,C#再从栈中读取数据。

  1. 可以按1N的顺序从栈底向上,也可以从-1-N从栈顶向下
  2. 栈中可以存储任何数据,函数、table等,空的位置用nil表示

前置准备

使用Tolua的相关类和方法都需要调用命名空间LuaInterface,LuaState.Start 需要在tolua代码加载到内存后调用。如果使用assetbunblde加载lua文件,调用Start()之前assetbundle必须加载好。

调用lua脚本必须先创建一个lua虚拟机, 一般对于客户端,推荐只创建一个LuaState对象。如果要使用多State需要在Unity中设置全局宏 MULTI_STATE,创建步骤为:

LuaState lua = new LuaState();

加载脚本

重要方法lua.AddSearchPath ,通过此方法添加lua文件的路径,只有添加了文件路径之后,在该路径上的lua文件才可以被读取。这样DoFile跟Require函数可以只用文件名,无需写全路径。

在C#中运行一段lua脚本最简单的方法就是lua.DoString,该方法声明如下:

public object[] DoString(string chunk, string chunkName = "LuaState.DoString")

注意dofile需要扩展名, 可反复执行, 后面的变量会覆盖之前的DoFile加载的变量这里加载了就会从开始逐句执行。

LuaState.Require

public void Require(string filename)

因为Require 读取文件是会检查该文件是否被加载过,如果被加载过,则直接返回一个索引,否则则加载并返回一个索引

  • 使用完lua虚拟机之后记得要销毁,具体操作如下:

    • 先进行lua虚拟机的判空,具体做法为lua.CheckTop
    • 析构掉lua虚拟机,具体做法为:lua.Dispose

C#调用lua函数

调用lua方法

我们可以把lua中的函数看成一个对象,在虚拟机初始化完成后,加载对应的lua文件,接着需要创建一个LuaFunction类型的对象来表示这个lua函数,可以通过调用:lua.GetFunction("方法名"); 来获取对应的函数对象

获取了之后就需要在C#中调用了,主要方式有两种:

  1. 直接调用LuaFunction类型的对象的func.Call 方法,完整声明为:
public object[] Call(params object[] args)

这种调用方法比较简单,但是有一个缺点,lua对象的内存无法被自动释放,所以当使用完这个lua函数对象之后,我们需要手动的调用LuaFunction类型的对象的func.Dispose();方法,释放掉垃圾内存,否则会造成内存泄漏!

不过选择都采用的是委托的方式,需要先进行DelegateFactory.Init();

int num = luaFunc.Invoke<int,int>(arg);

或者是

Func<int, int> Func = luaFunc.ToDelegate<Func<int, int>>();
num = Func(123456);
  1. 如同案例当中的CallFunc()
int CallFunc()
{        
    luaFunc.BeginPCall();                
    luaFunc.Push(123456);
    luaFunc.PCall();        
    int num = (int)luaFunc.CheckNumber();
    luaFunc.EndPCall();
    return num;                
}

首先我们必须先以func.BeginPCall;开始,通过func.Push(参数)来给方法传参–通过众多重载函数来解决参数转换的gc,然后需要通过func.PCall();来运行,接着通过对应的func.Checkxxx()方法来获取返回值,最后通过func.EndPCall();结束–清楚函数调用导致的堆栈变化。整个流程比较繁琐且不易封装,不过优点是不会有垃圾内存,所以不用手动释放GC。

获取lua中的变量

创建全局变量

在lua虚拟机创建完成且初始化完毕(调用Start方法)之后,可以直接声明一个lua虚拟机的全局变量:

LuaState lua = new LuaState();
lua.Start();
lua["Objs2Spawn"] = 5;

获取全局变量格式是相同的,这点和lua相同,没有就创建否则就读取。值得注意的是对于获取的函数需要强制转换为LuaFunction:LuaFunction func = lua["TestFunc"] as LuaFunction;

获取与创建lua的table

通过调用虚拟机的方法lua.GetTable 来获取lua中的table,用LuaTable类型来储存lua中的Table,通过调用Luatable的成员方法table.AddTable来创建lua中的table , 除了通过虚拟机的GetTable方法访问之外,直接通过 LuaTable 型变量按字典的类似方法也可以调用table

LuaTable table1 = lua.GetTable("varTable");
Debug.Log("Read VarTable from lua name:{0}", table1[map.name]);//会将map.name作为整体识别为键,值为nil,不会有任何输出
table1["map.name"] = "new";// table字符串只能为key,现在为“new”
LuaTable table2 = (LuaTable)table["newmap"];

对于lua元表也能使用LuaTable的GetMetaTable()方法来获取

对于变量的获取有一点值得注意:

如同注释中的那样,[]中的string会作为整体。lua语法中,如果有一个table A,那么A.x代表的是A[“x”],表示的是由字符串 “x”索引的表, 而a[x]表示的是变量x对应的值索引的表

a = {}
x = "y"
a[x] = 10
a.x -->nil 字段“x”的值,未定义
a.y -->10, 字段“y”的值

因此如果想要C#正确获取的话,需要像下面这样:

LuaTable table = lua.GetTable("varTable");
LuaTable map = (LuaTable)table["map"];
Debugger.Log("Read varTable from lua,  name: {0}", map["name"]);

对LuaTable类型的变量,在使用完后需要手动释放内存,否则会因为内存未自动释放造成内存泄漏,具体方法为调用LuaTable对象的方法table.Dispose();

GameObject

private string script =
    @"                                    
        local GameObject = UnityEngine.GameObject          
        local ParticleSystem = UnityEngine.ParticleSystem            
        local go = GameObject('go')
        go:AddComponent(typeof(ParticleSystem))
        local node = go.transform
        node.position = Vector3.one      
        print('gameObject is: '..tostring(go))    
        GameObject.Destroy(go, 2)                        
    ";
    
LuaState lua = null;

void Start()
{        
    lua = new LuaState();
    lua.LogGC = true;
    lua.Start();
    LuaBinder.Bind(lua);
    lua.DoString(script);            
}

void Update(){
    lua.CheckTop();
    lua.Collect();
}

LuaBinder.Bind(); ——该方法需要传入一个 LuaState 类型参数。该方法会将 C# 代码中定义的类、方法、属性等绑定到该 Lua 模块中。这样,Lua 脚本就可以调用 C# 代码中定义的函数和属性,而 C# 代码也可以通过 Lua 脚本调用 Lua 中的函数和属性。

lua.Collect();——垃圾回收, 对于被自动gc的LuaFunction, LuaTable, 以及委托减掉的LuaFunction, 延迟删除的Object之类。等等需要延迟处理的回收, 都在这里自动执行

lua调用C#函数

如同 案例中的那样,

local ParticleSystem = UnityEngine.ParticleSystem
local GameObject = UnityEngine.GameObjectlocal 
go = GameObject('go')
go:AddComponent(typeof(ParticleSystem))
local node = go.transform
node.position = Vector3.one                  
print('gameObject is: '..tostring(go))

对于UnityEngine下的一部分方法,lua中可以直接引用。

自定义脚本

对于自己实现的脚本比较麻烦,首先需要调用 LuaBinder.Bind(LuaState lua)

其次需要将自定义的类写入CustomSetting.cs文件中,你将你的自定义类加入到customTypeList数组中的末尾,如果是静态类就还需加入staticClassTypes中。然后依次点击Lua菜单中的Clear wrap files 和Gen All

自定义委托

只要需要使用到delegate字段,都要调用一次DelegateFactory.Init(),如果有自定义的委托还是要添加到CustomSetting.cs中的customDelegateList

如何给按钮动态添加监听事件? 首先我们可以定义一个LuaHelper的静态类,在其中实现一个AddButtonClick的静态函数,NGUI中给按钮添加事件代码示例:EventDelegate.Add(UIButton.onclick, Call call)

public static void AddButtonClick(GameObject g,LuaFunction callback)
{
    EventDelegate.Add(g.GetComponent<UIButton>().onClick, callback.Call);
}

在lua代码中只需找到对应的游戏物体

local helper = LuaHelper
local btn = UnityEngine.GameObject.Find("Button")

local function Onclick()
    print("OnClick")
end
helper.AddButtonClick(btn,Onclick)

Lua调用C#的规范就是不在Start周期函数中去做初始化,而是包装好初始化相关的函数,在lua中获取该函数在调用

获取C#数组元素

lua想要访问C#数组有几种方法,

  • 通过下标访问——这与C#中一致,而且还可以通过array.Length获取数组大小
  • 通过迭代器访问——array:GetEnumerator()获取数组的迭代器,通过iter.Current获得当前的迭代器所指向的元素值,通过iter:MoveNext()将迭代器所指位置移至下一个
  • 调用 array:ToTable() 实现将数组对象转化为对应的lua中的Table表的形式进行访问,注意lua数组下标从0开始

C#中获得lua函数的返回值,可以通过func.Checkxxx()来获得

比如

double arg1 = func.CheckNumber();//获得func函数double类型的返回值
string arg2 = func.CheckString();//获得func函数string类型的返回值
// 调用通用函数时需要转换类型,避免拆成多个参数
object[] objs = func.Invoke((object)array);
if (objs != null)
{
    Debugger.Log("return is {0} {1} {2}", objs[0], objs[1], objs[2]);
}

ToLua框架UML

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/476061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

架构设计-分布式ID

一、 分布式ID基础 1.背景 1.为什么要引用分布式主键ID&#xff1f; 比如单机 MySQL 数据库&#xff0c;前期因为业务量不大&#xff0c;只是使用单个数据库存数据&#xff0c;后期发现业务量一下子就增长&#xff0c;单机 MySQL 已经不能满足于现在的数据量&#xff0c;单机…

【实战项目开发技术分享】常用的ROS工具与命令

常用的ROS工具介绍 作为机器人领域最为流行的开源框架之一,ROS(Robot Operating System)提供了丰富的工具来支持机器人的开发和部署。下面是一些常用的ROS工具及其功能的介绍: roscore:roscore是ROS的主要进程,它启动ROS Master节点并协调ROS系统中的所有进程。所有ROS节…

NEFU数据库基本操作实验总结

前言 SQL语言集数据定义、数据操纵、数据查询、数据控制功能于一体。 数据定义&#xff1a;create&#xff0c;drop&#xff0c;alter 数据操纵&#xff1a;增(insert into … )&#xff0c;删(delete from …)&#xff0c;改(update … set …) 数据查询&#xff1a;select 数…

如何在 Linux 环境下安装使用 pycharm?介绍一种快捷打开方式

之所以要出这个教程&#xff0c;是想介绍一种更快捷的打开方式。官网上的教程&#xff0c;每次打开都要切换到下载地址&#xff0c;然后输入 ./pycharm.sh 才能运行。 如图&#xff0c;在任意位置打开终端&#xff0c;敲快捷键pc就能打开 pycharm 怎么样&#xff0c;非常方便…

MYSQL进阶01

MYSQL进阶 存储引擎存储引擎的特性MyISAMInnoDBMEMORY 如何选择合适的引擎 存储引擎 MYSQL默认支持多种存储引擎&#xff0c;可以根据用户不同需求选择合适的储存引擎。MYSQL支持的存储引擎包括但不局限于以下几种&#xff08;MyISAM、InnoDB、MEMORY、MERGE…&#xff0c;创建…

【Leetcode -142.环形链表Ⅱ -143.重排链表】

Leetcode Leetcode -142.环形链表ⅡLeetcode - 143.重排链表 Leetcode -142.环形链表Ⅱ 题目&#xff1a;给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 …

CodeRush Ultimate Crack终极版

CodeRush Ultimate Crack终极版 CodeRush Ultimate通过提高生产力&#xff0c;同时消除侵蚀创造力的重复&#xff0c;帮助开发人员在更短的时间内提供更多高质量的功能。它可以帮助您在几秒钟内创建复杂的代码块&#xff0c;并立即扩展代码模板&#xff0c;并在您按逻辑键入、扩…

[架构之路-179]-《软考-系统分析师》-19- 系统可靠性分析与设计 - 故障模型、可靠性模型、可靠性分析

目录 前言&#xff1a; 1 9 . 1 系统可靠性概述 19.1.1 系统故障模型 1. 在信息系统中&#xff0c;故障或错误有如下儿种表现形式&#xff1a; 2. 故障的缘由 3. 故障模型 &#xff08;1&#xff09;逻辑电路级的故障 &#xff08;2&#xff09; 数据结构级的故障 &a…

spass modeler

课时1&#xff1a;SPSS Modeler 简介 本课时一共分为五个模块&#xff0c;分别是Modeler概述、工具安装、窗口说明以及功能介绍和应用案例。相信通过本课时内容的学习&#xff0c;大家将会对SPSS Modeler有个基础的了解. 在学习本节课内容之前&#xff0c;先来看看本节课我们究…

Wi-Fi (-6) 知识点整理

Wi-Fi - 6 知识点整理 2019年IEEE推出IEEE 802.11ax&#xff0c;WiFi联盟&#xff08;WFA&#xff09;称其为WiFi-6 Wi-Fi (Wireless Fidelity) 无线保真&#xff0c;无线兼容性认证 通信技术 商标 商业认证 技术联盟 Wi-Fi 网络基本要素 接入点&#xff08;Access Point&a…

前端存储二:indexedDB

indexedDB 特点&#xff1a;以域名纬度&#xff0c;浏览器大量结构化数据存储方案&#xff0c;运行在浏览器的非关系型数据库。 大小&#xff1a;不会小于 250MB&#xff0c;支持二进制存储。 接口&#xff1a;异步接口&#xff0c;支持事物机制 这里使用网页脚本生成&#x…

【五一创作】【Simulink】采用延时补偿的三相并网逆变器FCS-MPC

&#x1f449; 【Simulink】基于FCS-MPC的三相并网逆变器控制 上一篇博客介绍了FCS-MPC的基本操作&#xff0c;并且以三相并网逆变器为控制对象进行了Simulink仿真。 但实际仿真中没有考虑补偿延时。本篇博客将讨论为什么要考虑延时并进行补偿&#xff0c;最后对此仿真验证。 …

参会记录|全国多媒体取证暨第二届多媒体智能安全学术研讨会(MAS‘2023)

前言&#xff1a;2023年4月8日上午&#xff0c;我与实验室的诸位伙伴们共聚浙江杭州西子湖畔的六通宾馆&#xff0c;参加了为期一天半的全国多媒体取证暨第二届多媒体智能安全学术研讨会&#xff08;MAS’2023&#xff09;。本届学术研讨会由浙江省自然科学基金委员会资助&…

3.2 静态随机存取存储器

学习目标&#xff1a; 学习静态随机存取存储器&#xff08;SRAM&#xff09;的基本原理、结构和工作方式&#xff0c;理解其与动态随机存取存储器&#xff08;DRAM&#xff09;的区别和优缺点&#xff0c;掌握SRAM的性能参数和应用领域&#xff0c;了解SRAM的发展历程和未来趋…

三元操作 三元操作符 if-else / ? :

Python 三元操作符 if-else &#xff0c; 其他语言三元操操作符 ? : 。 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单……地址&#xff1a;h…

【Java】类和对象,封装

目录 1.类和对象的定义 2.关键字new 3.this引用 4.对象的构造及初始化 5.封装 //包的概念 //如何访问 6.static成员 7.代码块 8.对象的打印 1.类和对象的定义 对象&#xff1a;Java中一切皆对象。 类&#xff1a;一般情况下一个Java文件一个类&#xff0c;每一个类…

深度学习的定义和未来发展趋势

深度学习的定义和未来发展趋势 什么是深度学习数学和编程的基础知识深度学习的应用领域深度学习的常见算法和模型训练深度学习模型深度学习的未来 &#x1f3d8;️&#x1f3d8;️个人简介&#xff1a;以山河作礼。 &#x1f396;️&#x1f396;️:Python领域新星创作者&#…

Linux [常见指令 (1)]

Linux常见指令 ⑴ 1. 操作系统1.1什么事操作系统1.2选择指令的原因 2.使用工具3.Linux的指令操作3.1mkdir指令描述:用法:例子 mkdir 目录名例子 mkdir -p 目录1/ 目录2/ 目录3 3.2 touch指令描述:用法:例子 touch 文件 3.2pwd指令描述:用法:例子 pwd 3.4cd指令描述:用法:例子 c…

SQL语句截取字段某指定字符的前半段/后半段内容

最近项目中遇到一个小问题&#xff1a; 需要从数据库中取出对应数据&#xff0c;并根据某个字段中的前半段内容进行排序&#xff0c;搜索资料后得以解决&#xff0c;现将解决方法记录如下&#xff1a; 最初的查询SQL&#xff1a; SELECT file_name,sort FROM base_annexesfil…

Linux守护进程(Daemon Process)

1. 守护进程概念&#xff1a; 独立于终端控制并周期性地执行处理某些任务的后台进程。 2. 守护进程创建步骤&#xff1a; 核心&#xff1a;让进程脱离控制终端→创建新会话。 &#xff08;1&#xff09;创建子进程&#xff0c;父进程退出&#xff08;必须&#xff09;&#x…