tolua的github下载地址:https://github.com/topameng/tolua
假设我们下载的是LuaFramework_UGUI,它是基于Unity 5.0 + UGUI + tolua构建的工程
下载下来得到一个LuaFramework_UGUI-master.zip
解压之后就是一个Unity的工程,直接用Unity打开,首次打开工程会询问生成注册文件,点击确定即可
首先要执行lua资源的生成(打AssetBundle),点击菜单【LuaFramework】-【Build Windows Resource】
会把lua代码达成AssetBundle放在StreamingAssets中。
如果你用的不是Unity5.x,而是Unity2018,那么可能会报错:
这是因为新版本的Unity有些属性和接口已经废弃了的原因,我们需要特殊处理一下
一个是Light类,一个是QualitySettings类,这两个类我们一般不需要在lua中使用,所以我们不对他们生产Wrap即可:
生成成功后,我们可以在StreamingAssets中看到很多AssetBundle文件
接下来,我们就可以运行Demo场景了。打开main场景
运行
可以使用subline,也可以使用visual studio,个人偏好使用visual studio,配合插件BabeLua
Unity写lua代码的vs插件:BabeLua: https://blog.csdn.net/linxinfa/article/details/88191485
AppFacade.Instance.StartUp(); //启动游戏
这个接口会抛出一个NotiConst.START_UP事件,对应的响应类是StartUpCommand
using UnityEngine;
using System.Collections;
using LuaFramework;
public class StartUpCommand : ControllerCommand {
public override void Execute(IMessage message) {
if (!Util.CheckEnvironment()) return;
GameObject gameMgr = GameObject.Find("GlobalGenerator");
if (gameMgr != null) {
AppView appView = gameMgr.AddComponent();
}
//-----------------关联命令-----------------------
AppFacade.Instance.RegisterCommand(NotiConst.DISPATCH_MESSAGE, typeof(SocketCommand));
//-----------------初始化管理器-----------------------
AppFacade.Instance.AddManager(ManagerName.Lua);
AppFacade.Instance.AddManager(ManagerName.Panel);
AppFacade.Instance.AddManager(ManagerName.Sound);
AppFacade.Instance.AddManager(ManagerName.Timer);
AppFacade.Instance.AddManager(ManagerName.Network);
AppFacade.Instance.AddManager(ManagerName.Resource);
AppFacade.Instance.AddManager(ManagerName.Thread);
AppFacade.Instance.AddManager(ManagerName.ObjectPool);
AppFacade.Instance.AddManager(ManagerName.Game);
}
}
这里初始化了各种管理器,我们可以根据具体需求进行改造和自定义。
LuaManager这个管理器是必须的,掌管整个lua虚拟机的生命周期。它主要是加载lua库,加载lua脚本,启动lua虚拟机,执行Main.lua。
AppConst定义了一些常量。
其中AppConst.LuaBundleMode是lua代码AssetBundle模式。它会被赋值给LuaLoader的beZip变量,在加载lua代码的时候,会根据beZip的值去读取lua文件,false则去search path中读取lua文件,否则从外部设置过来的bundle文件中读取lua文件。默认为true。在Editor环境下,建议把AppConst.LuaBundleMode设为false,这样方便运行,否则写完lua代码需要生成AssetBundle才可以运行到。
LuaLoader和LuaResLoader都继承LuaFileUtils。lua代码会先从LuaFramework.Util.AppContentPath目录解压到LuaFramework.Util.DataPath目录中,lua文件列表信息记录在files.txt中,此文件也会拷贝过去。然后从LuaFramework.Util.DataPath目录中读取lua代码。
/// LuaFramework.Util.DataPath
///
/// 应用程序内容路径
/// AppConst.AssetDir = "StreamingAssets"
///
public static string AppContentPath() {
string path = string.Empty;
switch (Application.platform) {
case RuntimePlatform.Android:
path = "jar:file://" + Application.dataPath + "!/assets/";
break;
case RuntimePlatform.IPhonePlayer:
path = Application.dataPath + "/Raw/";
break;
default:
path = Application.dataPath + "/" + AppConst.AssetDir + "/";
break;
}
return path;
}
///
/// 取得数据存放目录
///
public static string DataPath {
get {
string game = AppConst.AppName.ToLower();
if (Application.isMobilePlatform) {
return Application.persistentDataPath + "/" + game + "/";
}
if (AppConst.DebugMode) {
return Application.dataPath + "/" + AppConst.AssetDir + "/";
}
if (Application.platform == RuntimePlatform.OSXEditor) {
int i = Application.dataPath.LastIndexOf('/');
return Application.dataPath.Substring(0, i + 1) + game + "/";
}
return "c:/" + game + "/";
}
}
完了之后,再进行远程的更新检测,看看用不用热更lua代码,远程url就是AppConst.WebUrl,先下载files.txt,然后再读取lua文件列表进行下载。
启动框架后,会创建GameManager游戏管理器,它负责检测lua逻辑代码的更新检测和加载(Main.lua是在LuaManager中执行的),我们可以在GameManager中取DoFile我们自定义的lua脚本,比如Game.lua脚本。
GameManager可以获取到LuaManager对象,通过LuaManager.CallFunction接口调用。
也可以用Util.CallMethod接口调用,两个接口的参数有差异,需要注意。
/// LuaManager.CallFunction接口
public object[] CallFunction(string funcName, params object[] args) {
LuaFunction func = lua.GetFunction(funcName);
if (func != null) {
return func.LazyCall(args);
}
return null;
}
/// Util.CallMethod接口
public static object[] CallMethod(string module, string func, params object[] args) {
LuaManager luaMgr = AppFacade.Instance.GetManager(ManagerName.Lua);
if (luaMgr == null) return null;
return luaMgr.CallFunction(module + "." + func, args);
}
假设现在我们有一个C#类
using UnityEngine;
public class MyTest : MonoBehaviour
{
public int myNum;
public void SayHello()
{
Debug.Log("Hello,I am MyTest,myNum: " + myNum);
}
public static void StaticFuncTest()
{
Debug.Log("I am StaticFuncTest");
}
}
我们想在lua中访问这个MyTest类的函数。首先,我们需要在CustomSettings.cs中的customTypeList数组中添加类的注册:
_GT(typeof(MyTest)),
然后然后再单击菜单【Lua】-【Generate All】生成Wrap,生成完我们会看到一个MyTestWrap类
接下来就可以在lua中访问了。(注意AppConst.LuaBundleMode的值要设为false,方便Editor环境下运行lua代码,否则需要先生成AssetBundle才能运行)
function Game.TestFunc()
-- 静态方法访问
MyTest.StaticFuncTest()
local go = UnityEngine.GameObject("go")
local myTest = go:AddComponent(typeof(MyTest))
-- 成员变量
myTest.myNum = 5
-- 成员方法
myTest:SayHello()
end
调用Game.TestFunc()
注意,静态方法、静态变量、成员变量、成员属性使用 “.” 来访问,比如上面的 myTest.myNum,成员函数使用 “:” 来访问,比如上面的 myTest:SayHello()
function fib(n)
local a, b = 0, 1
while n > 0 do
a, b = b, a + b
n = n - 1
end
return a
end
function CoFunc()
print('Coroutine started')
for i = 0, 10, 1 do
print(fib(i))
coroutine.wait(0.1)
end
print("current frameCount: "..Time.frameCount)
coroutine.step()
print("yield frameCount: "..Time.frameCount)
local www = UnityEngine.WWW("http://www.baidu.com")
coroutine.www(www)
local s = tolua.tolstring(www.bytes)
print(s:sub(1, 128))
print('Coroutine ended')
end
调用
coroutine.start(CoFunc)
local co = coroutine.start(CoFunc)
coroutine.stop(co)
假设现在有这么一份json文件
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Mark up Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
假设我们已经把上面的json文件的内容保存到变量jsonStr字符串中,现在在lua中要解析它
local json = require 'cjson'
function Test(str)
local data = json.decode(str)
print(data.glossary.title)
s = json.encode(data)
print(s)
end
// c#传托管给lua
System.Action cb = (s) => { Debug.Log(s); };
Util.CallMethod("Game", "TestCallBackFunc", cb);
-- lua调用C#的托管
function Game.TestCallBackFunc(cb)
if nil ~= cb then
System.Delegate.DynamicInvoke(cb,"Hello, I am lua, I call Delegate")
end
end
有时候,我们没有把我们的C#类生成Wrap,但是又需要在lua中调用,这个时候,可以通过反射来调用。
假设我们有一个C#类:MyClass
// MyClass.cs
public sealed class MyClass
{
//字段
public string myName;
//属性
public int myAge { get; set; }
//静态方法
public static void SayHello()
{
Debug.Log("Hello, I am MyClass's static func: SayHello");
}
public void SayNum(int n)
{
Debug.Log("SayNum: " + n);
}
public void SayInfo()
{
Debug.Log("SayInfo, myName: " + myName + ",myAge: " + myAge);
}
}
在lua中
-- Game.lua
function Game.TestReflection()
require 'tolua.reflection'
tolua.loadassembly('Assembly-CSharp')
local BindingFlags = require 'System.Reflection.BindingFlags'
local t = typeof('MyClass')
-- 调用静态方法
local func = tolua.getmethod(t, 'SayHello')
func:Call()
func:Destroy()
func = nil
-- 实例化
local obj = tolua.createinstance(t)
-- 字段
local field = tolua.getfield(t, 'myName')
-- 字段Set
field:Set(obj, "linxinfa")
-- 字段Get
print('myName: ' .. field:Get(obj))
field:Destroy()
-- 属性
local property = tolua.getproperty(t, 'myAge')
-- 属性Set
property:Set(obj, 29, null)
-- 属性Get
print('myAge: ' .. property:Get(obj, null))
property:Destroy()
--public成员方法SayNum
func = tolua.getmethod(t, 'SayNum', typeof('System.Int32'))
func:Call(obj, 666)
func:Destroy()
--public成员方法SayInfo
func = tolua.getmethod(t, 'SayInfo')
func:Call(obj)
func:Destroy()
end
nil是lua对象的空,null表示c#对象的空。假设我们在c#中有一个GameObject对象传递给了lua的对象a,接下来我们把这个GameObject对象Destroy了,并在c#中把这个GameObject对象赋值为null,此时lua中的对象a并不会等于nil
如果要在lua中判断一个对象是否为空,安全的做法是同时判断nil和null
-- lua中对象判空
function IsNilOrNull(o)
return nil == o or null == o
end