本篇博客内容本着“我们只是大自然的搬运工”这样的理念,为大家快速入门Lua和学习使用XLua(Unity Lua编程解决方案)提供一个学习线路以方便快速上手使用LuaMVC框架(基于pureMVC+XLua开发的Unity热更新框架)。
Lua是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库。这使得Lua体积小、启动速度快。它用ANSI C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。和许多“大而全”的语言不一样,网路通讯、图形界面等都没有默认提供。但是Lua可以很容易地被扩展:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。事实上,现在已经有很多成熟的扩展模块可供选用。
Lua是一种动态类型语言,因此语言中没有类型的定义,不需要声明变量类型,每个变量自己保存了类型。有8种基本类型:nil、布尔值(boolean)、数字体(number)、字符串型(string)、用户自定义类型(userdata)、函数(function)、线程(thread)和表(table)。
print(type(nil)) -- 输出 nil
print(type(99.7+12*9)) -- 输出 number
print(type(true)) -- 输出 boolean
print(type("Hello Wikipedia")) -- 输出 string
print(type(print)) -- 输出 function
print(type{1, 2, test = "test"}) -- 输出 table
以下是一段项目中的Lua代码,简单的语法让学习Lua语言变的较为容易,在项目中使用Lua甚至不需要刻意的去学习Lua,跟着Lua教程案例写几篇,然后针对具体的几个难点(类型实现、构造、继承等)学习一下即可快速入门。
在lua环境执行以下代码或者是在.lua文件中写入以下代码由loader加载,具体方式见《第一个 Lua 程序》。
print('Hello LuaMVC')
-- 引用包 和C#中的using类似,但有些区别
require('NotificationType')
require('ViewNames')
-- XLua中使用CS.调用C#类型
UnityEngine = CS.UnityEngine
GameObject = CS.UnityEngine.GameObject
LuaMVC = CS.LuaMVC.LuaApplicationFacade -- 用于给luaMVC发送通知
AssetLoader = CS.LuaMVC.AssetLoader -- 用于加载Resources/Assetbundle资源
-- 声明awake方法,此方法由C#调用
function awake()
-- XLua中用print可在unity console面板打印输出
print('lua part framework start up.')
-- self类似C#中的this指针,一下是调用C#中的方法
-- 注意区分调用方法时'.'和':'的区别
self:RegisterLuaCommand("StartUpCommand")
local canvasParent = GameObject.Find("Canvas/UICamera").transform
-- 调用C#中带委托参数的方法,可以用匿名函数
AssetLoader.LuaLoadAsset("Views.unity3d","LoginView",function(asset)
local loginView = GameObject.Instantiate(asset)
loginView.transform:SetParent(canvasParent)
loginView.transform.localScale = UnityEngine.Vector3.one
loginView:AddComponent(typeof(CS.LuaMVC.LuaMonobehaviour)):Init('LoginView')
self:RegisterLuaMediator('LoginViewMediator')
end)
end
-- 声明ondestroy,此方法由C#调用
function ondestroy()
print('lua part framework shut down.')
end
至于Lua语法的学习,推荐以下站点或书籍:
- 《Lua菜鸟教程》
- 《Lua视频教学》
- 《Programming in Lua》
- 《Lua程序设计电子手册》
以下内容均搬运至XLua官方,或由官方内容总结,可直接前往XLua官方了解。
XLua是针对Unity的Lua编程解决方案,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。它支持安卓,iOS,Windows等其他系统。
xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是:
下载XLua官方Release包,导入Unity工程,新建C#脚本继承至MonoBehaviour,添加到一个游戏物体上,在Start方法中添加以下代码:
XLua.LuaEnv luaenv = new XLua.LuaEnv();
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
luaenv.Dispose();
切回到Unity界面,等待编译,点击Play,可看到Console面板输出。
以下内容均出至XLua教程
点击下载XLua教程Doc文件
点击查看其他XLua文档
以下默认为最推荐的方法,效率最好
// 访问全局字段
// luaenv为当前运行的lua虚拟机(虚拟环境)
luaenv.Global.Get<int>("a");
// 设置全局字段
luaenv.Global.Set("a",1);
// 访问Lua对象字段
// scriptEnv为当前Lua对象在C#中映射的LuaTable
// scriptName为Lua对象的名称
string name = scriptEnv.GetInPath<string>(Person+".Name");
// 设置Lua对象字段
scriptEnv.SetInPath(Person+".age",30);
以下为Lua对象的实现方式,Lua本没有面向对象的能力,但是我们可以利用Lua的Table构造出对象。
-- Lua'类'的实现
Person = {}
this = Person
Person.Name = 'default'
Person.age = 28
return Person
// 将Lua方法映射到委托,再调用 (推荐 ,推荐 推荐 )
Action act = luaenv.Global.Get("FunctionName");
act();
// 将Lua方法映射到LuaFunction,再调用 (效率低)
LuaFunction func = luaenv.Global.Get("FunctionName");
func.Call();
使用建议:1、访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
2、如果lua测的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。LuaMVC中就是使用将Lua代码映射委托,再注入到PureMVCz中的方式,使得使用C#或是Lua编码,或者同时使用两种语言都完全没有耦合,开发时不需处理两者间的调用问题,只需要关注业务逻辑。
local newGameObj = CS.UnityEngine.GameObject("gameobject")
CS.UnityEngine.Time.deltaTime
-- 字段、属性
person.Name = 'LuaMVC'
-- 方法
-- 调用成员方法,第一个参数需要传该对象,建议用冒号语法糖
person:Say()
源码可参考XLua官方案例,或是LuaMVC.LuaApplicationFacade中的多种自定义Loader。
luaenv = new LuaEnv();
luaenv.DoString("print('hello world')");
这种方式虽简单,但因为直接写在C#脚本中,导致失去了热更新的能力,基本只在测试时可用。
luaenv = new LuaEnv();
luaenv.DoString("require 'byfile'");
这种方式可加载.lua文件,XLua对DoString做了拓展,使得这种方式可以加载Resource文件夹下的lua脚本,而且由于Resource文件夹下文件后缀的限制,lua脚本必须改为.lua.txt后缀,使得在很多编辑器中需要手动调整语法才能适配。
private void Start()
{
luaEnv.AddLoader(LuaPathLoader);
}
private byte[] LuaPathLoader(string filePath)
{
string fullPath = Application.persistentDataPath + "/LuaScripts/" + filePath + ".lua" + luaExtension;
return Encoding.UTF8.GetBytes(File.ReadAllText(fullPath));
}
利用以上自定义Loader的方法可以直接加载本地文件,也可以加载从服务器获取的Lua脚本,同时执行解密,也可直接加载assetbundle文件种的lua脚本。
调用成员方法时,C#中是用.来调用的,而在Lua中是用:调用,其实Lua中的:是一种语法糖,相比’.’调用,它省略了传递一个对象作为参数,是一种简写的方式,而C#只是已经处理了这一点。我们用Lua代码来还原一下这个过程:
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance -v
end
-- 直接以表对象调用方法
Account.withdraw(100)
a = Account
Account = nil
a.withdraw(100) -- 报错
报错原因:因为a表违背了对象应有的独立生命周期的原则,也就是说a.withdraw()时,withdraw并不知道操作的是哪一个对象,我们修改一下withdraw方法,如下:
function Account.withdraw(self,v)
self.balance = self.balance - v
end
b = Account
Account = nil
b.withdraw(b,100) -- 正确
原理解释:self参数的使用是很多面向对象语言的要点,大多数语言隐藏了这一机制,所以’:’相比于’.’只是一种语法的便利,当然相反的,如果你定义函数时使用的是’:’,在使用’.’调用函数时,也需要传入对象作为参数。
Lua中我们用表来效仿类型,基于类似js中的原型(prototype)。在Lua中我们使用__index和metatable来效仿prototype。
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o,self)
self.__index = self
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
a = Account:new{balance = 100}
a:deposit(100)
a对象的metatable对象为Account,因此在a对象中找不到deposit方法时,会调用Account.__index:deposit()方法。
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o,self)
self.__index = self
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
-- SpecialAccount是Account的实例
SpecialAccount = Account:new()
-- 继承Account,并将self指针指向SpecialAccount
s = SpecialAccount:new(limit = 1000)
-- 为SpcicalAccount添加新的成员函数
function SpecialAccount:getLimit()
return self.limit or 0
end
-- s对象调用diposit方法
s:deposit(50)
原理解释:s对象的metatable对象是SpecialAccount,而SpecialAccount的metatable对象是Account,因此s调用deposit方法是SpecialAccount从父类Account继承来的方法。
源码 : https://github.com/ll4080333/luaMVC
如果对你有用,记得点一波Star,关注博客哦。
LuaMVC是我在项目种的经验总结,如果恰巧你也需要这样的框架来快速开发,那你可以期待后续的更新哦。
如果你有什么更好的意见与建议欢迎加留言或者加群:LuaMVC/SpringGUI交流群 593906968 。