[Unity3D热更框架] LuaMVC之XLua

1.Lua

  本篇博客内容本着“我们只是大自然的搬运工”这样的理念,为大家快速入门Lua和学习使用XLua(Unity Lua编程解决方案)提供一个学习线路以方便快速上手使用LuaMVC框架(基于pureMVC+XLua开发的Unity热更新框架)。

1.1 Lua特性

  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

1.2 Lua示例

  以下是一段项目中的Lua代码,简单的语法让学习Lua语言变的较为容易,在项目中使用Lua甚至不需要刻意的去学习Lua,跟着Lua教程案例写几篇,然后针对具体的几个难点(类型实现、构造、继承等)学习一下即可快速入门。

  • Hello World

  在lua环境执行以下代码或者是在.lua文件中写入以下代码由loader加载,具体方式见《第一个 Lua 程序》


print('Hello LuaMVC')
  • 项目Lua脚本
-- 引用包 和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语法的学习,推荐以下站点或书籍:

2.XLua

  以下内容均搬运至XLua官方,或由官方内容总结,可直接前往XLua官方了解。

2.1 什么是XLua

  XLua是针对Unity的Lua编程解决方案,xLua为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用。它支持安卓,iOS,Windows等其他系统。

2.2 XLua特性与优势

xLua在功能、性能、易用性都有不少突破,这几方面分别最具代表性的是:

  • 可以运行时把C#实现(方法,操作符,属性,事件等等)替换成lua实现
  • 出色的GC优化,自定义struct,枚举在Lua和C#间传递无C# gc alloc
  • 编辑器下无需生成代码,开发更轻量
  • 热补丁
    • 侵入性小,老项目原有代码不做任何调整就可使用
    • 运行时影响小,不打补丁基本和原有程序一样
    • 出问题了可以用Lua来打补丁,这时才会走到lua代码逻辑

2.3 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面板输出。

3.LuaMVC中Lua基础

以下内容均出至XLua教程

3.1 Lua与C#的互相访问

以下默认为最推荐的方法,效率最好

3.1.1 C#访问Lua

  • 字段
// 访问全局字段
// 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<Action>("FunctionName");
act();

// 将Lua方法映射到LuaFunction,再调用 (效率低)
LuaFunction func = luaenv.Global.Get<LuaFunction>("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编码,或者同时使用两种语言都完全没有耦合,开发时不需处理两者间的调用问题,只需要关注业务逻辑。

3.1.2 Lua访问C

  • 构造方法(支持重载)
local newGameObj = CS.UnityEngine.GameObject("gameobject")
  • 静态属性、方法
CS.UnityEngine.Time.deltaTime
  • 成员(对象)属性、方法
-- 字段、属性
person.Name = 'LuaMVC'
-- 方法 
-- 调用成员方法,第一个参数需要传该对象,建议用冒号语法糖
person:Say()

3.2 C#如何加载Lua脚本

  源码可参考XLua官方案例,或是LuaMVC.LuaApplicationFacade中的多种自定义Loader。

  • 加载String
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后缀,使得在很多编辑器中需要手动调整语法才能适配。

  • 自定义Loader
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脚本。

3.3 Lua面向对象编程核心

3.3.1 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参数的使用是很多面向对象语言的要点,大多数语言隐藏了这一机制,所以’:’相比于’.’只是一种语法的便利,当然相反的,如果你定义函数时使用的是’:’,在使用’.’调用函数时,也需要传入对象作为参数。

3.3.2 类型实现

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()方法。

3.3.3 Lua继承

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继承来的方法。

4.关于LuaMVC框架

源码 : https://github.com/ll4080333/luaMVC
如果对你有用,记得点一波Star,关注博客哦。

  LuaMVC是我在项目种的经验总结,如果恰巧你也需要这样的框架来快速开发,那你可以期待后续的更新哦。
  如果你有什么更好的意见与建议欢迎加留言或者加群:LuaMVC/SpringGUI交流群 593906968 。

猜你喜欢

转载自blog.csdn.net/qq_29579137/article/details/79222442