跳转至

Lua这玩意儿自己学习时肯定用不到,但是面试时又喜欢考,而且没有经过实战大概率没法完全弄明白这玩意儿...不过话又说回来,建议找工作前还是看一眼,不然当UI仔的机会都没有。这块也算是进阶知识了,最好对Unity有着充分的了解后再来看。

一 XLua与热更新介绍

游戏热更新是指在不需要重新对游戏包体进行编译、打包、发布的情况下,在运行时更新游戏中的一些非核心代码和资源的一些技术。比如你需要在游戏上线时对一些运营活动打打补丁、改改数值和bug,但你不可能让玩家又重新下载一遍这个游戏,所以热更新对于网游开发来说就相当重要。

  • 资源热更新:一般使用AB(AssetBundle)包,更新一些资源(纹理,模型,特效)。前几年的厂子依旧在用AB包,但是现在Unity已经逐渐淘汰AB改为AA包了,后续技术选型如何还未可知。不过资源热更新现在不是这篇文章要讨论的话题,暂且略过。

  • 代码热更新:现在主流采用的依然是Lua的方案,此外还有类似ILRuntime,HybirldCLR的技术来支持代码热更新。如果你要修改代码逻辑又不需要将所有游戏代码重新编译,那么就可以用Lua来解决,这是网游UI仔的必备技能→_→。

不过Lua的技术方案也不止一种,Lua只是一种解释型语言。Unity本身并没有支持Lua,因此就诞生了以xLua,ToLua为主流的技术方案。Lua技术方案大差不差,懂一个就行,本文就主要讲述xLua的使用。

这里先直接将腾讯开源的xLua项目介绍搬过来供大家了解一下:

xLua 为 Unity、.Net、Mono 等 C# 环境增加 Lua 脚本编程的能力,借助 xLua,这些 Lua 代码可以方便的和 C# 相互调用。

二 Lua基础语法快速入门

Lua 是一种轻量小巧的解释型语言(类似于Python),用标准C语言编写并以源代码形式开放。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

学习xLua使用前,建议先了解一下Lua的语法。由于本文默认读者有Unity程序开发的基础,所以主要还是为想学习Lua的进阶游戏开发者快速扫盲,不会讲解一些编程语言的共性,理解Lua的语言差异。

我们先不用考虑框架,也不用下载什么东西,先用在线Lua网址来快速练习Lua语言:https://www.jyshare.com/compile/66/。

后面我也会给出每个模块的测试代码,可以运行一下看看Lua的结果,也可以自己敲一下试试看不同的运行结果。

2.1 注释

Lua语言不以//为注释,而是用--来表示

输入:

--这是单行注释
--[[
    这是跨行注释
]]
print("Hello World!")        --Lua可以不使用分号划分语句

输出:

Hello World!

2.2 Lua数据类型

Lua 是动态类型语言,不需要定义变量类型就可以使用,我们只需要为变量赋值,此时可以使用local标识符来创建局部变量。 值可以存储在变量中,作为参数传递或结果返回。

Lua 中有 8 大基本类型,分别为:

  • nil:无效值(条件表达式中也等同于false),给任意变量赋值nil都等于删除它。
  • boolean:布尔类型,包含两个值false和true
  • number:双精度浮点数
  • string:字符串类型,用""或''表示
  • userdata:表示任意存储在变量中的C数据结构
  • function:由 C 或 Lua 编写的函数
  • thread:协程
  • table:个人认为Lua最重要的结构--表,本质上是一个“关联数组”,需要用{}来创建表。

测试代码:

print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil
print(type(type(X)))            --> string
print(type({}))                 --> table

-- 创建一个空的 table
local tbl1 = {}
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

print(tbl1)
print(tbl2)

Table 表

针对表我们还需要单独讲讲,因为表可以说是lua用的最广泛的数据类型

  • 表可以理解为由多个键值对构成,初始化默认按照按照1、2、3、4的递增序列为值赋予索引。

  • 表也可以单独指定任意的索引和值,只要确保满足是number,string,table的类型。

  • 用nil可以回收表,移除引用。

  • table提供了一些操作如contact、insert、maxn、remove、sort,用来快速做一些表的操作

-- 初始化表
mytable = {}

tbl2 = {"apple", "pear", "orange", "grape"}

-- 指定值
mytable[1]= "Lua"
mytable["wow"] = "修改前"
-- 使用table操作
table.insert(mytable,"mango")

-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存

2.3 Lua循环

lua循环的思想还是和正常代码类似,不过for循环有不少细节值得注意一下

  • while循环:
while( true )
do
   print("循环将永远执行下去")
end
  • for循环
    • 数值for循环:格式为for var=exp1,exp2,exp3 do ... end ,其中var以exp3(默认为1)为步长,从exp1变化到exp2。
    • 泛型for循环:通过一个迭代器来遍历所有值。可以将其理解为C#中的foreach,lua本身也提供了两种默认的迭代器ipairs和pairs供程序员使用。
      • ipairs: 仅仅遍历值,按照索引升序遍历索引中断停止遍历。即不能返回 nil,只能返回数字 0,如果遇到 nil 则退出。它只能遍历到集合中出现的第一个不是整数的 key。
      • pairs :能遍历集合的所有元素。即 pairs 可以遍历集合中所有的 key,并且除了迭代器本身以及遍历表本身还可以返回 nil

注意:lua中的for循环是默认从1开始的,这点

--数值for循环--
for i=1,f(x) do
    print(i)
end

for i=10,1,-1 do
    print(i)
end

--泛型for循环--

local tabFiles = {
        [1] = "test2",
        [6] = "test3",
        [4] = "test1"
    }

for k, v in ipairs(tabFiles) do    --输出"1 test2",在key等于2处断开
    print(k, v)
end

for k, v in pairs(tabFiles) do  --输出所有键值对
    print(k, v)
end

2.4 Lua运算符

主要说说和其他语言差异比较大的地方

  • 算术运算符://--整除符号,因为lua没有整型,这个方法做了一些计算上的弥补。
  • 关系运算符:~=不等于,替代!=
  • 逻辑运算符:用and,or,not来表示,替代&,|,!
  • 其他运算符:
    • ..连接左右两边的字符串,形成新的字符串
    • #返回右边字符串或者表的长度

测试用例:

if ( a and b )
then
   print("a and b - 条件为 true" )
end

if ( a or b )
then
   print("a or b - 条件为 true" )
end


a = "Hello "
b = "World"

print("连接字符串 a 和 b ", a..b )
print("b 字符串长度 ",#b )
print("字符串 Test 长度 ",#"Test" )

local tab = {0,1,2}

print("tab长度",#tab)

2.5 Lua模块与包

这一部分涉及多文件不太好在网站上测试,不过也比较简单就顺便讲了。有兴趣的同学可以自己下载lua在本机上进行测试。

创建模块

Lua的模块是由变量、函数等元素组成的一个table。因此创建模块就是创建table。创建一个完整的模块步骤如下: - 创建table - 将导出的常量、函数放入其中 - 最后返回table 一般我们会将一个文件直接创建为一个模块来使用

module.lua

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}

-- 定义一个常量
module.constant = "这是一个常量"

-- 定义一个函数
function module.func1()
    io.write("这是一个公有函数!\n")
end

local function func2()
    print("这是一个私有函数!")
end

function module.func3()
    func2()
end

return module

使用模块

我们使用require来引用其他文件模块,类似于C/C++的include。引用模块后,我们就可以直接调用模块中的函数与数据结构来进行操作。 test_module.lua

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()

2.6 元表Metatable

在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)

元表(metatable)是Lua语言中相当重要的一部分内容,允许我们改变 table 的行为,每个行为关联了对应的元方法。在我看来,元表类似于其他语言中的重载运算符操作,但更加复杂,基于元表我们还可以实现lua的面向对象(如封装、继承、多态)的功能。这部分有相当多的部分可以扩展,因此会留到后面的Lua进阶内容详细讲述相关内容。目前将元表提出,也是为了让大家了解这个概念,不至于看到后陷入迷茫。

两大元表函数

元表主要由下面两个函数处理 - setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。 - getmetatable(table): 返回对象的元表(metatable)。

mytable = {}                          -- 普通表
mymetatable = {}                      -- 元表
setmetatable(mytable,mymetatable)     -- 把 mymetatable 设为 mytable 的元表
--上面这一部分等价于
mytable = setmetatable({},{})

getmetatable(mytable)       

元方法

元方法定义了元表的行为,我们可以将表赋值给元方法用于返回值,也可以将函数赋值给元方法用来执行方法。针对不同的元方法我们可以实现不同的效果。对于元方法,我将其分为下面几个部分方便大家记忆。

  • __index:主要用于访问。通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable

--查看表中元素是否存在,如果存在则直接返回结果
--否则由 __index 返回结果,key2返回metatablevalue字符串,其他的返回nil。

返回结果为 nil
mytable = setmetatable({key1 = "value1"}, {
  __index = function(mytable, key)
    if key == "key2" then
      return "metatablevalue"
    else
      return nil
    end
  end
})

print(mytable.key1,mytable.key2)
- __newIndex主要用于更新。当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法,并赋值给元表。如果存在则调用这个函数而不进行赋值操作。
--给表赋值,若存在新索引则将其转换为字符串
mytable = setmetatable({key1 = "value1"}, {
    __newindex = function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")
    end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2)

  • __add__sub...等:主要用来定义表之间的各种运算。对应的是+、-等运算符。
--------------------实现两表的加法--------------------
-- 自定义计算表中最大键值函数 table_maxn,即返回表最大键值
function table_maxn(t)
    local mn = 0
    for k, _ in pairs(t) do
        if type(k) == "number" and k > mn then
            mn = k
        end
    end
    return mn
end

-- 两表相加操作
mytable = setmetatable({ 1, 2, 3 }, {
  __add = function(mytable, newtable)
    local max_key_mytable = table_maxn(mytable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, max_key_mytable + i, newtable[i])
    end
    return mytable
  end
})

secondtable = {4, 5, 6}

mytable = mytable + secondtable

for k, v in ipairs(mytable) do
    print(k, v)
end
  • __call主要用于调用。__call 元方法在 Lua 调用一个值时调用,类似于回调函数。
--调用表时,返回表内值的总和

function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        end
    end
    return mn
end

-- 定义元方法__call
mytable = setmetatable({10}, {
  __call = function(mytable, newtable)
        sum = 0
        for i = 1, table_maxn(mytable) do
                sum = sum + mytable[i]
        end
    for i = 1, table_maxn(newtable) do
                sum = sum + newtable[i]
        end
        return sum
  end
})
newtable = {10,20,30}    
print(mytable(newtable))        //10 + 20 + 30 = 70

输出:

70
  • __tostring主要用于输出。__tostring 元方法用于修改表的输出行为,常和print搭配使用。
mytable = setmetatable({ 10, 20, 30 }, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        end
    return "表所有元素的和为 " .. sum
  end
})
print(mytable)

输出:

表所有元素的和为 60

总结

lua说难也不算太难,掌握上面这些内容就可以进行正常的lua编程了。对于lua的面向对象、闭包、协程等进阶内容,我后续有时间的话可以单独再搞一搞。总之现在暂时摸了~毕竟教程主要还是教会大家怎么使用xlua。