上次想要学习使用ulua,结果误打误撞下了个基于tolua#的LuaFramework,还当成ulua在用,哈哈哈哈哈——详见(Unity 初次使用ulua,熟悉C#和Lua交互)
既然用的是tolua#,那这次再整理一下tolua#,将官方的几个examples都尝试一下
从http://www.ulua.org/index.html下载,因为我学的Unity版本比较新,使用的是UGUI,所以选择LuaFramework_UGUI版本
从GitHub上Clone下来后,有这些内容:
新建一个Unity项目,新建完成后,打开项目所在目录,将LuaFramework_UGUI中的Assets和LuaEncoder文件夹拷贝至项目目录下:
打开Unity项目,等待加载完成后,会发现菜单栏中多了一些东西
算是导入成功了
导入成功后
项目Assets>LuaFramework>ToLua>Examples文件路径下,有一些例子,接下来一个个执行看看:
先从第一个HelloWorld开始
打开Examples>01_HelloWorld下的那个场景文件,切换到该场景
运行:
HelloWorld.cs中的代码:
using UnityEngine;
using LuaInterface;
using System;
public class HelloWorld : MonoBehaviour
{
void Awake()
{
LuaState lua = new LuaState();
lua.Start();
string hello =
@"
print('hello tolua#')
";
lua.DoString(hello, "HelloWorld.cs");
lua.CheckTop();
lua.Dispose();
lua = null;
}
}
这个代码逻辑很简单,直接将string类型的lua代码放在lua虚拟机中执行,通过lua打印显示hello,然后析构虚拟机并置空,结束。
打开02_ScriptsFromFile下的场景文件,运行
点击DoFile和Reqire按钮都可以执行文件中的lua代码
ScriptsFromFile.cs代码如下:
using UnityEngine;
using System.Collections;
using LuaInterface;
using System;
using System.IO;
//展示searchpath 使用,require 与 dofile 区别
public class ScriptsFromFile : MonoBehaviour
{
LuaState lua = null;
private string strLog = "";
void Start ()
{
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived += Log;
#else
Application.RegisterLogCallback(Log);
#endif
lua = new LuaState();
lua.Start();
//如果移动了ToLua目录,自己手动修复吧,只是例子就不做配置了
string fullPath = Application.dataPath + "\\LuaFramework/ToLua/Examples/02_ScriptsFromFile";
lua.AddSearchPath(fullPath);
}
void Log(string msg, string stackTrace, LogType type)
{
strLog += msg;
strLog += "\r\n";
}
void OnGUI()
{
GUI.Label(new Rect(100, Screen.height / 2 - 100, 600, 400), strLog);
if (GUI.Button(new Rect(50, 50, 120, 45), "DoFile"))
{
strLog = "";
lua.DoFile("ScriptsFromFile.lua");
}
else if (GUI.Button(new Rect(50, 150, 120, 45), "Require"))
{
strLog = "";
lua.Require("ScriptsFromFile");
}
lua.Collect();
lua.CheckTop();
}
void OnApplicationQuit()
{
lua.Dispose();
lua = null;
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived -= Log;
#else
Application.RegisterLogCallback(null);
#endif
}
}
ScriptsFromFile.lua中的代码如下:
print("This is a script from a utf8 file")
print("tolua: 你好! こんにちは! 안녕하세요!")
上面的C#代码中展现了两种加载文件的方式,DoFile和Require
在lua中也有这两种加载lua文件的方式doFile和require(注意!lua中是小驼峰!)
两者的区别如下:
require:在加载一个.lua文件时,require会先在package.loaded中查找此模块是否存在,如果存在,直接返回模块;如果不存在,则加载模块文件。在加载时不需要写文件的后缀名,函数会通过特定的搜索路径去查找符合规则的模块,例如上面C#中的lua.Require(“ScriptsFromFile”);
doFile:读入代码文件并编译执行。每调用dofile一次,都会重新编译执行一次。在使用该函数时需要写上文件后缀,例如上面C#中的lua.DoFile(“ScriptsFromFile.lua”);
所以相对来说,Require只会加载文件一次,更加高效,推荐使用Require
顺带一提,在lua中还有一种加载文件的方式:loadFile
loadFile是编译代码,将这个模块当做一个函数返回,但是不执行,例如:
a = loadfile("test1.lua")
此时的a是一个包含test1.lua所有内容的函数,但是test1.lua并没有被执行
doFile相当于在loadFile外再进行了一次包装,直接执行
在C#中调用lua文件的方式除了上面两种之外,还可以读取lua文件转化为字符串进行执行
TextAsset scriptFile;
// scriptFile读取文件
luaState.DoString(scriptFile.text);
打开第三个example,运行:
example中展示了四种调用lua函数的方式,C#代码如下:
using UnityEngine;
using System.Collections;
using LuaInterface;
using System;
public class CallLuaFunction : MonoBehaviour
{
private string script =
@" function luaFunc(num)
return num + 1
end
test = {}
test.luaFunc = luaFunc
";
LuaFunction luaFunc = null;
LuaState lua = null;
string tips = null;
void Start ()
{
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived += ShowTips;
#else
Application.RegisterLogCallback(ShowTips);
#endif
new LuaResLoader();
lua = new LuaState();
lua.Start();
DelegateFactory.Init();
lua.DoString(script);
//Get the function object
luaFunc = lua.GetFunction("test.luaFunc");
if (luaFunc != null)
{
int num = luaFunc.Invoke(123456);
Debugger.Log("generic call return: {0}", num);
num = CallFunc();
Debugger.Log("expansion call return: {0}", num);
Func Func = luaFunc.ToDelegate>();
num = Func(123456);
Debugger.Log("Delegate call return: {0}", num);
num = lua.Invoke("test.luaFunc", 123456, true);
Debugger.Log("luastate call return: {0}", num);
}
lua.CheckTop();
}
void ShowTips(string msg, string stackTrace, LogType type)
{
tips += msg;
tips += "\r\n";
}
#if !TEST_GC
void OnGUI()
{
GUI.Label(new Rect(Screen.width / 2 - 200, Screen.height / 2 - 150, 400, 300), tips);
}
#endif
void OnDestroy()
{
if (luaFunc != null)
{
luaFunc.Dispose();
luaFunc = null;
}
lua.Dispose();
lua = null;
#if UNITY_5 || UNITY_2017 || UNITY_2018
Application.logMessageReceived -= ShowTips;
#else
Application.RegisterLogCallback(null);
#endif
}
int CallFunc()
{
luaFunc.BeginPCall();
luaFunc.Push(123456);
luaFunc.PCall();
int num = (int)luaFunc.CheckNumber();
luaFunc.EndPCall();
return num;
}
}
这里使用的是Invoke<>()函数来调用函数
<>内写的是参数和返回值,例如上面例子中的luaFunc.Invoke
表示参数为int,返回值为int
Invoke
Invoke<>内可以放多个输入参数的类型,但只能有一个返回类型且放在最后
因为Lua支持多参数返回,如果使用上面的Invoke调用函数的话,只会返回Lua函数中的第一个返回值
例如:
function luaFunc(a, b)
return a+b, a-b, 'helloworld'
end
在C#中只能得到第一个返回值a+b
如果需要得到lua返回的所有数据,可以将lua的返回值用数组封装起来,然后在C#中利用object[]读取,例如:
--lua
function luaFunc(a, b)
return {a+b, a-b, 'helloworld'}
end
//C#
luaFunction = luaState.GetFunction("luaFunc");
object[] res = luaFunction.Invoke(1, 2);
Debugger.Log("generic call return: {0}, {1}, {2}", res[0].ToString(), res[1].ToString(), res[2].ToString());
这样可以得到所有的返回数据
这种方式调用函数相对复杂一些,利用了lua的堆栈
我尝试了一下lua函数的多参数输入和返回,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LuaInterface;
public class Example03_CallLuaFunction : MonoBehaviour {
private static string luaScript =
@"
function luaFunc(a, b)
return a+b, a-b, 'helloworld'
end
";
private LuaState luaState;
private LuaFunction luaFunction;
// Use this for initialization
void Start () {
luaState = new LuaState();
luaState.Start();
luaState.DoString(luaScript);
luaFunction = luaState.GetFunction("luaFunc");
if (luaFunction != null)
{
luaFunction.BeginPCall();
luaFunction.Push(1);
luaFunction.Push(2);
luaFunction.PCall();
int res1 = (int)luaFunction.CheckNumber();
int res2 = (int)luaFunction.CheckNumber();
string res3 = luaFunction.CheckString();
Debugger.Log("expansion call return: {0}, {1}, {2}", res1, res2, res3);
luaFunction.EndPCall();
}
}
}
这种方式利用Push压入参数,PCall开始调用,然后是用CheckXXX()函数取出对应类型的参数,可以支持lua的多参数返回
这是将lua的function转化为C#的委托(delegate)
Func Func = luaFunc.ToDelegate>();
num = Func(123456);
Debugger.Log("Delegate call return: {0}", num);
example中的Func(123456)实际是调用了delegate
Func作为一个delegate,还是能够通过+操作添加其他同类型函数,-操作删除函数委托的
这个第一种调用方式一样,只是这种方式调用不需要先获取lua的function,而是直接通过luastate调用
LuaState.Invoke<>内同样可以指定多个输入参数,和一个返回参数(放在末尾),同LuaFunction.Invoke
LuaState.Invoke
调用时输入参数为lua的函数名(用string表示),函数的输入参数,以及一个bool类型的beLogMiss(这个beLogMiss只是指定在找不到lua函数的时候是否打印日志)
if (beLogMiss)
{
Debugger.Log("Lua function {0} not exists", name);
}