Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 特性
- 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
- 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
-
其它特性:支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
Lua 应用场景
- 游戏开发
- 独立应用脚本
- Web 应用脚本
- 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
- 安全系统,如入侵检测系统
开发环境搭建
第一个 Lua 程序
交互式编程
Lua 提供了交互式编程模式。我们可以在命令行中输入程序并立即查看效果。
Lua 交互式编程模式可以通过命令 lua -i 或 lua 来启用:
脚本式编程
我们可以将 Lua 程序代码保持到一个以 lua 结尾的文件,并执行,该模式称为脚本式编程,如我们将如下代码存储在名为 hello.lua 的脚本文件中:
使用 lua 名执行以上脚本,输出结果为:
注释
- 单行注释
两个减号是单行注释:
--Lua注释 - 多行注释
--[[ Lua注释
Lua注释
Lua注释 --]]
标示符
Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上0个或多个字母,下划线,数字(0到9)。
最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。
Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 W3c 与 w3c 是两个不同的标示符。
关键词
以下列出了 Lua 的保留关键字。保留关键字不能作为常量或变量或其他用户自定义标示符:
and
break
do
else
elseif
end
false
for
function
if
in
local
nil
not
or
repeat
return
then
true
until
while
一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。
全局变量
在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。
数据类型
Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
我们可以使用type函数测试给定变量或者值的类型:
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
变量
变量在使用前,必须在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显示声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 nil。
-- test.lua 文件脚本
a = 5 -- 全局变量
local b = 5 -- 局部变量
function joke()
c = 5 -- 全局变量
local d = 6 -- 局部变量
end
joke()
print(c,d) --> 5 nil
do
local a = 6 -- 局部变量
b = 6 -- 全局变量
print(a,b); --> 6 6
end
print(a,b) --> 5 6
赋值语句
赋值是改变一个变量的值和改变表域的最基本的方法。
Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
a = "hello" .. "world"
t.n = t.n + 1
a, b = 10, 2*x <--> a=10; b=2*x
x, y = y, x -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
多值赋值经常用来交换变量,或将函数调用返回给变量:a, b = f()
应该尽可能的使用局部变量,有两个好处:
- 避免命名冲突。
- 访问局部变量的速度比全局变量更快。
索引
对 table 的索引使用方括号 []。Lua 也提供了 . 操作。
t[i]
t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用
循环
很多情况下我们需要做一些有规律性的重复操作,因此在程序中就需要重复执行某些语句。
一组被重复执行的语句称之为循环体,能否继续重复,决定循环的终止条件。
循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。
循环语句是由循环体及循环的终止条件两部分组成的。
Lua 语言提供了以下几种循环处理方式:
循环类型 | 描述 |
---|---|
while 循环 | 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。 |
for 循环 | 重复执行指定语句,重复次数可在 for 语句中控制。 |
repeat...until | 重复执行循环,直到 指定的条件为真时为止 |
循环嵌套 | 可以在循环内嵌套一个或多个循环语句(while、for、do..while) |
循环控制
循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。
Lua 支持以下循环控制语句:
控制语句 | 描述 |
---|---|
break 语句 | 退出当前循环或语句,并开始脚本执行紧接着的语句。 |
在循环体中如果条件永远为 true 循环语句就会永远执行下去
-
while 循环
Lua 编程语言中 while 循环语句在判断条件为 true 时会重复执行循环体语句。
Lua 编程语言中 while 循环语法:
while(condition)
do
statements
end
statements(循环体语句) 可以是一条或多条语句,condition(条件) 可以是任意表达式,在 condition(条件) 为 true 时执行循环体语句。
流程图如下:
在以上流程图中我们可以看出在condition(条件)为 false 时会跳过当前循环并开始脚本执行紧接着的语句。
-
for 循环
Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
Lua 编程语言中 for语句有两大类::
- 数值for循环
Lua 编程语言中数值for循环语法格式:
for var=exp1,exp2,exp3 do
<执行体>
end
var从exp1变化到exp2,每次变化以exp3为步长递增var,并执行一次"执行体"。exp3是可选的,如果不指定,默认为1。
- 泛型for循环
泛型for循环通过一个迭代器函数来遍历所有值,类似java中的foreach语句。
Lua 编程语言中泛型for循环语法格式:
--打印数组a的所有值
for i,v in ipairs(a)
do print(v)
end
i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
-
repeat...until 循环
Lua 编程语言中 repeat...until 循环语句不同于 for 和 while循环,for 和 while 循环的条件语句在当前循环执行开始时判断,而 repeat...until 循环的条件语句在当前循环结束后判断。
Lua 编程语言中 repeat...until 循环语法格式:
repeat
statements
until( condition )
我们注意到循环条件判断语句(condition)在循环体末尾部分,所以在条件进行判断前循环体都会执行一次。
如果条件判断语句(condition)为 false,循环会重新开始执行,直到条件判断语句(condition)为 true 才会停止执行。
Lua repeat...until 循环流程图如下:
-
循环嵌套
Lua 编程语言中允许循环中嵌入循环。以下实例演示了 Lua 循环嵌套的应用。
流程控制
Lua 编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。
控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。
Lua 提供了以下控制结构语句:
语句 | 描述 |
---|---|
if 语句 | if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。 |
if...else 语句 | if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。 |
if 嵌套语句 | 你可以在if 或 else if中使用一个或多个 if 或 else if 语句 。 |
-
if 语句
if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
if(布尔表达式)
then
--[ 在布尔表达式为 true 时执行的语句 --]
end
在布尔表达式为 true 时会if中的代码块会被执行,在布尔表达式为 false 时,紧跟在 if 语句 end 之后的代码会被执行。
Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
if 语句流程图如下:
-
if...else 语句
Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。
Lua if...else 语句语法格式如下:
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
在布尔表达式为 true 时会if中的代码块会被执行,在布尔表达式为 false 时,else 的代码块会被执行。
Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
if 语句流程图如下:
if...else 语句
if...elseif...else 语句
Lua if 语句可以与 elseif...else 语句搭配使用, 在 if 条件表达式为 false 时执行 elseif...else 语句代码块,用于检测多个条件语句。
Lua if...elseif...else 语句语法格式如下:
if( 布尔表达式 1)
then
--[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2)
then
--[ 在布尔表达式 2 为 true 时执行该语句块 --]
elseif( 布尔表达式 3)
then
--[ 在布尔表达式 3 为 true 时执行该语句块 --]
else
--[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
-
嵌套语句
if 语句允许嵌套, 这就意味着你可以在一个 if 或 else if 语句中插入其他的 if 或 else if 语句。
函数
在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。
Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。
Lua 函数主要有两种用途:
- 完成指定的任务,这种情况下函数作为调用语句使用;
- 计算并返回值,这种情况下函数作为赋值语句的表达式使用。
函数定义
Lua 编程语言函数定义格式如下:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
名称 | 解释 |
---|---|
optional_function_scope: | 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。 |
function_name: | 指定函数名称。 |
argument1, argument2, argument3..., argumentn: | 函数参数,多个参数以逗号隔开,函数也可以不带参数。 |
function_body: | 函数体,函数中需要执行的代码语句块。 |
result_params_comma_separated: | 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。 |
实例
以下实例定义了函数 max(),参数为 num1, num2,用于比较两值的大小,并返回最大值:
--[[ 函数返回两个值的最大值 --]]
function max(num1, num2)
if (num1 > num2) then
result = num1;
else
result = num2;
end
return result;
end
-- 调用函数
print("两值比较最大值为 ",max(10,4))
print("两值比较最大值为 ",max(5,6))
以上代码执行结果为:
两值比较最大值为 10 两值比较最大值为 6
Lua 中我们可以将函数作为参数传递给函数,如下实例:
myprint = function(param)
print("这是打印函数 - ##",param,"##")
end
function add(num1,num2,functionPrint)
result = num1 + num2
-- 调用传递的函数参数
functionPrint(result)
end
myprint(10)
-- myprint 函数作为参数传递
add(2,5,myprint)
以上代码执行结果为:
这是打印函数 - ## 10 ## 这是打印函数 - ## 7 ##
多返回值
Lua函数可以返回多个结果值,比如string.find,其返回匹配串"开始和结束的下标"(如果不存在匹配串返回nil)。
Lua函数中,在return后列出要返回的值得列表即可返回多值,如:
function maximum (a)
local mi = 1 -- 最大值索引
local m = a[mi] -- 最大值
for i,val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end
print(maximum({8,10,23,12,5}))
以上代码执行结果为:
23 3
可变参数
Lua函数可以接受可变数目的参数,和C语言类似在函数参数列表中使用三点(...) 表示函数有可变的参数。
Lua将函数的参数放在一个叫arg的表中,#arg 表示传入参数的个数。
例如,我们计算几个数的平均值:
function average(...)
result = 0
local arg={...}
for i,v in ipairs(arg) do
result = result + v
end
print("总共传入 " .. #arg .. " 个数")
return result/#arg
end
print("平均值为",average(10,5,3,4,5,6))
以上代码执行结果为:
总共传入 6 个数 平均值为 5.5
运算符
运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua提供了以下几种运算符类型:
- 算术运算符
下表列出了 Lua 语言中的常用算术运算符,设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
+ | 加法 |
A + B 输出结果 30 |
- | 减法 |
A - B 输出结果 -10 |
* | 乘法 |
A * B 输出结果 200 |
/ | 除法 |
B / A 输出结果 2 |
% | 取余 |
B % A 输出结果 0 |
^ | 乘幂 |
A^2 输出结果 100 |
- | 负号 |
-A 输出结果v -10 |
- 关系运算符
下表列出了 Lua 语言中的常用关系运算符,设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
== | 等于,检测两个值是否相等,相等返回 true,否则返回 false |
(A == B) 为 false。 |
~= | 不等于,检测两个值是否相等,相等返回 false,否则返回 true |
(A ~= B) 为 true。 |
> | 大于,如果左边的值大于右边的值,返回 true,否则返回 false |
(A > B) 为 false。 |
< | 小于,如果左边的值大于右边的值,返回 false,否则返回 true |
(A < B) 为 true。 |
>= | 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false |
(A >= B) 返回 false。 |
<= | 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false |
(A <= B) 返回 true。 |
- 逻辑运算符
下表列出了 Lua 语言中的常用逻辑运算符,设定 A 的值为 true,B 的值为 false:
操作符 | 描述 | 实例 |
---|---|---|
and | 逻辑与操作符。 如果两边的操作都为 true 则条件为 true。 |
(A and B) 为 false。 |
or | 逻辑或操作符。 如果两边的操作任一一个为 true 则条件为 true。 |
(A or B) 为 true。 |
not | 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false。 |
not(A and B) 为 true。 |
- 其他运算符
下表列出了 Lua 语言中的连接运算符与计算表或字符串长度的运算符:
操作符 | 描述 | 实例 |
---|---|---|
.. | 连接两个字符串 |
a..b ,其中 a 为 "Hello " , b 为 "World", 输出结果为 "Hello World"。 |
# | 一元运算符,返回字符串或表的长度。 |
#"Hello" 返回 5 |
运算符优先级
从高到低的顺序:
^
not - (unary)
* /
+ -
..
< > <= >= ~= ==
and
or
除了^和..外所有的二元运算符都是左连接的。
a+i < b/2+1 <--> (a+i) < ((b/2)+1)
5+x^2*8 <--> 5+((x^2)*8)
a < y and y <= z <--> (a < y) and (y <= z)
-x^2 <--> -(x^2)
x^y^z <--> x^(y^z)
字符串
字符串或串(String)是由数字、字母、下划线组成的一串字符。
Lua 语言中字符串可以使用以下三种方式来表示:
- 单引号间的一串字符。
- 双引号间的一串字符。
- [[和]]间的一串字符。
转义字符用于表示不能直接显示的字符,比如后退键,回车键,等。如在字符串转换双引号可以使用 """。
所有的转义字符和所对应的意义:
转义字符 | 意义 | ASCII码值(十进制) |
---|---|---|
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
|代表一个反斜线字符''' | 092 | |
' | 代表一个单引号(撇号)字符 | 039 |
" | 代表一个双引号字符 | 034 |
\0 | 空字符(NULL) | 000 |
\ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 1到2位十六进制所代表的任意字符 | 二位十六进制 |
字符串操作
Lua 提供了很多的方法来支持字符串的操作:
-
string.upper(argument)
:字符串全部转为大写字母。
-
string.lower(argument)
:字符串全部转为小写字母。 -
string.gsub(mainString,findString,replaceString,num)
在字符串中替换,mainString为要替换的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换)。 -
string.strfind (str, substr, [init, [end]])
在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil。 -
string.reverse(arg)
字符串反转 -
string.format(...)
返回一个类似printf的格式化字符串 -
string.char(arg) 和 string.byte(arg[,int])
char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。 -
string.len(arg)
计算字符串长度。string.len("abc")3 -
string.rep(string, n))
返回字符串string的n个拷贝 -
..
链接两个字符串
数组
数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。
Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。
一维数组
一维数组是最简单的数组,其逻辑结构是线性表。一维数组可以用for循环出数组中的元素,如下实例:
array = {"Lua", "Tutorial"}
for i= 0, 2 do
print(array[i])
end
以上代码执行输出结果为:
nil Lua Tutorial
正如你所看到的,我们可以使用整数索引来访问数组元素,如果知道的索引没有值则返回nil。
在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。
除此外我们还可以以负数为数组索引值:
array = {}
for i= -2, 2 do
array[i] = i *2
end
for i = -2,2 do
print(array[i])
end
多维数组
多维数组即数组中包含数组或一维数组的索引键对应一个数组。
以下是一个三行三列的阵列多维数组:
-- 初始化数组
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j] = i*j
end
end
-- 访问数组
for i=1,3 do
for j=1,3 do
print(array[i][j])
end
end
迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址
在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型 for 迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k, v in pairs(t) do
print(k, v)
end
上面代码中,k, v为变量列表;pair(t)为表达式列表。
查看以下实例:
array = {"Lua", "Tutorial"}
for key,value in ipairs(array)
do
print(key, value)
end
以上实例中我们使用了 Lua 默认提供的迭代函数 ipairs。
- 下面我们看看范性for的执行过程:
- 首先,初始化,计算in后面表达式的值,表达式应该返回范性for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
- 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 第三,将迭代函数返回的值赋给变量列表。
- 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
- 第五,回到第二步再次调用迭代函数
- 在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:
- 无状态的迭代器
- 多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是ipairs,他遍历数组的每一个元素。
以下实例我们使用了一个简单的函数来实现迭代器,实现 数字 n 的平方:
function square(iteratorMaxCount,currentNumber)
if currentNumber<iteratorMaxCount
then
currentNumber = currentNumber+1
return currentNumber, currentNumber*currentNumber
end
end
for i,n in square,3,0
do
print(i,n)
end
多状态的迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。
以下实例我们创建了自己的迭代器:
array = {"Lua", "Tutorial"}
function elementIterator (collection)
local index = 0
local count = #collection
-- 闭包函数
return function ()
index = index + 1
if index <= count
then
-- 返回迭代器的当前元素
return collection[index]
end
end
end
for element in elementIterator(array)
do
print(element)
end
table(表)
table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数字、字典等。
Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table 是不固定大小的,你可以根据自己需要进行扩容。
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。
table(表)的构造
构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组:
-- 初始化表
mytable = {}
-- 指定值
mytable[1]= "Lua"
-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存
当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。
-- 简单的 table
mytable = {}
print("mytable 的类型是 ",type(mytable))
mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
-- alternatetable和mytable的是指同一个 table
alternatetable = mytable
print("alternatetable 索引为 1 的元素是 ", alternatetable[1])
print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])
alternatetable["wow"] = "修改后"
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
-- 释放变量
alternatetable = nil
print("alternatetable 是 ", alternatetable)
-- mytable 仍然可以访问
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
mytable = nil
print("mytable 是 ", mytable)
以上代码执行结果为:
mytable 的类型是 table mytable 索引为 1 的元素是 Lua mytable 索引为 wow 的元素是 修改前 alternatetable 索引为 1 的元素是 Lua mytable 索引为 wow 的元素是 修改前 mytable 索引为 wow 的元素是 修改后 alternatetable 是 nil mytable 索引为 wow 的元素是 修改后 mytable 是 nil
Table 操作
以下列出了 Table 操作常用的方法:
-
table.concat (table [, sep [, start [, end]]])
:concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。
-
table.insert (table, [pos,] value)
:在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾. -
table.maxn(table)
:指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2之后该方法已经不存在了,本文使用了自定义函数实现)
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
tbl = {[1] = "a", [2] = "b", [3] = "c", [26] = "z"}
print("tbl 长度 ", #tbl)
print("tbl 最大值 ", table_maxn(tbl))
-
table.remove (table [, pos])
:返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。 -
table.sort (table [, comp])
:对给定的table进行升序排序。
模块与包
模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 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
由上可知,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
require 函数
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("<模块名>")
或者require "<模块名>"
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()
或者给加载的模块定义一个别名变量,方便调用:
-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
print(m.constant)
m.func3()
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。
require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有 LUA_PATH 这个环境变量,也可以自定义设置,在当前用户根目录下打开 .profile 文件(没有则创建,打开 .bashrc 文件也可以)。
C 包
Lua和C是很容易结合的,使用C为Lua写包。
与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。这个函数有两个参数:库的绝对路径和初始化函数。所以典型的调用的例子如下:
local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")
loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。
如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f() -- 真正打开库
一般情况下我们期望二进制的发布库包含一个与前面代码段相似的stub文件,安装二进制库的时候可以随便放在某个目录,只需要修改stub文件对应二进制库的实际路径即可。
将stub文件所在的目录加入到LUA_PATH,这样设定后就可以使用require函数加载C库了。
元表(Metatable)*
在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。
因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。
当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。
有两个很重要的函数来处理元表:
-
setmetatable(table,metatable)
: 对指定table设置元表(metatable),如果元表(metatable)中存在__metatable键值,setmetatable会失败 。 -
getmetatable(table)
: 返回对象的元表(metatable)。
以下实例演示了如何对指定的表设置元表:
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
--OR
mytable = setmetatable({},{})
--返回对象元表
getmetatable(mytable) -- 这回返回mymetatable
__index 元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
Lua查找一个表元素时的规则,其实就是如下3个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
- 判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。
__newindex 元方法
_newindex 元方法用来对表更新,__index则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
__call 元方法
__call 元方法在 Lua 调用一个值时调用。以下实例演示了计算表中元素的和:
-- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大值函数 table_maxn
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))
-- 以上实例执行输出结果为:
70
__tostring 元方法
_tostring 元方法用于修改表的输出行为。以下实例我们自定义了表的输出内容:
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
为表添加操作符
以下实例演示了两表相加操作:
-- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大值函数 table_maxn
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
-- 两表相加操作
mytable = setmetatable({ 1, 2, 3 }, {
__add = function(mytable, newtable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, table_maxn(mytable)+1,newtable[i])
end
return mytable
end
})
secondtable = {4,5,6}
mytable = mytable + secondtable
for k,v in ipairs(mytable) do
print(k,v)
end
__add 键包含在元表中,并进行相加操作。 表中对应的操作列表如下:
模式 | 运算符 |
---|---|
__add | + |
__sub | - |
__mul | * |
__div | / |
__mod | % |
__unm | - |
__concat | .. |
__eq | == |
__lt | < |
__le | <= |
协同程序(coroutine)*
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同是非常强大的功能,但是用起来也很复杂。
线程和协同程序区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
基本语法
方法 | 描述 |
---|---|
coroutine.create() | 创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用 |
coroutine.resume() | 重启coroutine,和create配合使用 |
coroutine.yield() | 挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果 |
coroutine.status() | 查看coroutine的状态 注:coroutine的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序 |
coroutine.wrap() | 创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复 |
coroutine.running() | 返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号 |
文件 I/O
错误处理
调试(Debug)
垃圾回收
Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
Lua 运行了一个垃圾收集器来收集所有死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
Lua 实现了一个增量标记-扫描收集器。 它使用这两个数字来控制垃圾收集循环: 垃圾收集器间歇率和垃圾收集器步进倍率。 这两个数字都使用百分数为单位 (例如:值 100 在内部表示 1 )。
垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。 增大这个值会减少收集器的积极性。 当这个值比 100 小的时候,收集器在开启新的循环前不会有等待。 设置这个值为 200 就会让收集器等到总内存使用量达到 之前的两倍时才开始新的循环。
垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。 增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。 不要把这个值设得小于 100 , 那样的话收集器就工作的太慢了以至于永远都干不完一个循环。 默认值是 200 ,这表示收集器以内存分配的"两倍"速工作。
如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ), 收集器的行为就像一个 stop-the-world 收集器。 接着你若把间歇率设为 200 , 收集器的行为就和过去的 Lua 版本一样了: 每次 Lua 使用的内存翻倍时,就做一次完整的收集。
垃圾回收器函数
Lua 提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理:
- **
collectgarbage("collect")
: **做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能: - **
collectgarbage("count")
: **以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。 - **
collectgarbage("restart")
: **重启垃圾收集器的自动运行。 - **
collectgarbage("setpause")
: **将 arg 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。 - **
collectgarbage("setstepmul")
: **返回 步进倍率 的前一个值。 - **
collectgarbage("step")
: **单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。 - **
collectgarbage("stop")
: **停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。
面向对象
我们知道,对象由属性和方法组成。
LUA中最基本的结构是table,所以需要用table来描述对象的属性;lua中的function可以用来表示方法。
那么LUA中的类可以通过table + function模拟出来。
至于继承,可以通过metetable模拟出来(不推荐用,只模拟最基本的对象大部分时间够用了)。
Lua中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有:
Account = {balance = 0}
function Account.withdraw (v)
Account.balance = Account.balance - v
end
这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,下面我们可以这样调用:
Account.withdraw(100.00)
一个简单实例
以下简单的类包含了三个属性: area, length 和 breadth,printArea方法用于打印计算结果:
-- Meta class
Rectangle = {area = 0, length = 0, breadth = 0}
-- 派生类的方法 new
function Rectangle:new (o,length,breadth)
o = o or {}
setmetatable(o, self)
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length*breadth;
return o
end
-- 派生类的方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
创建对象是位类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。(内存在对象初始化时分配。)
r = Rectangle:new(nil,10,20)
我们可以使用点号(.)来访问类的属性:
print(r.length)
我们可以使用冒号(:)来访问类的成员函数:
r:printArea()
继承
继承是指一个对象直接使用另一对象的属性和方法。可用于扩展基础类的属性和方法。
以下演示了一个简单的继承实例:
-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
o = o or Shape:new(o,side)
setmetatable(o, self)
self.__index = self
return o
end
-- 派生类方法 printArea
function Square:printArea ()
print("正方形面积为 ",self.area)
end
-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()
Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
o = o or Shape:new(o)
setmetatable(o, self)
self.__index = self
self.area = length * breadth
return o
end
-- 派生类方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
数据库访问
wowAddons开发指南
Lua 是一个小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。 Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。
魔兽世界、仙剑奇侠传五等的插件用的也是lua。
在wow的跟目录中
Cache 缓存
Data 魔兽主要数据
Errors 错误信息
Fonts 字体
Interface 接口(第三方插件模型等)
Logs 记录(聊天记录和战斗记录等)
Screenshots 截图(在wow中按prt可以截图存放)
Utils 工具(魔兽内置浏览器应该没有用)
WTF 配置
在Interface中建一个AddOns文件夹用来存放插件
插件的结构
WoW的插件由3个文件组成,分别是XXX.toc
/ XXX.xml
/ XXX.lua
XXX.toc中标注了关于插件信息的定义,以及需要加载的.xml的位置。
XXX.xml是插件的核心,包括界面、功能等元素都在其内。
XXX.lua是一种嵌入式脚本语言的源文件,在WoW中实现.xml调用的函数。
换句话说,.toc告诉WoW插件的名字和.xml的位置,.xml告诉WoW插件都有什么,怎样工作,具体事宜则交由.lua来处理。
XXX.toc
XXX.toc中标注了关于插件信息的定义,以及需要加载的.xml的位置。
TOC全称为Table of Contents就是目录的意思。
XXX.toc的位置在World of Warcraft\Interface\AddOns\XXX\XXX.toc。其中XXX为插件名称,必须保证完全一致,包括大小写。Windows系统不区分大小写,但WoW客户端区分(case-sensitive),在开发过程中一定要注意。
WoW只能读取1024字节/行,富裕出的部分将被忽略并且不报错。
## Interface: 适用的魔兽版本号
## Title: 显示的标题(默认语言)
## Notes: 显示的说明(默认语言)
## Title-zhCN: 特定语言的标题(简体中文)
## Notes-zhCN: 特定语言的说明(简体中文)
## Author: 作者(不显示)
## Version: 版本
## eMail: 如题
## UIType: 插件类型
## Dependencies: 依赖的插件
## RequiredDeps: 必须依赖的其他插件
## OptionalDeps: 可选倚赖
## SavedVariables: 统一存放的变量
## SavedVariablesPerCharacter: 按角色存放的变量
## LoadOnDemand: 1 (调用时加载)
## LoadWith: 当指定插件加载时才加载,前提是调用时加载
## DefaultState: disabled 默认状态
## Secure: 安全(功能未知)
# 注释1 dklasjfkasdj
Script.lua -- 脚本文件
% 注释2 dskajfklasdjfklsdaj
Layout.xml -- 布局文件
客户端识别标签##
开头
- Interface
## Interface: 70100
wow客户端版本(如果插件的Interface号和当前游戏客户端版本号不一致,那么游戏默认不会加载插件)
- Title
## Title: |cffC495DDXXX|rXXX`
## Title-zhCN: |cffC495DD插件|r插件
## Title-zhTW: |cffC495DD插件|r插件
Title表示插件的名称,也就是插件管理面板里显示的那个。支持多语言定义,默认为英语。其他语言要在Title后面加上后缀,就像上面那样。zhCN表示简体中文,zhTW就是台湾的繁体中文。前面2个小写是语言,后面2个大写是国家和地区。
还有个好玩的东西就是颜色,WoW支持字串形式的颜色定义。插件面板默认字体颜色是黄,你可以根据自己的喜好自行定义文本颜色,这里就用到颜色字串。颜色字串以"|c"开头"|r"结束。中间是16进制αRGB颜色代码和要显示的文本。比如我要将"我的插件"几个字显示为蓝色。那么就应该是:##Title: |cff0000ff我的插件|r
其中|c表示接下来的8位字符是颜色代码,以α(alpha透明度)、R(红)、G(绿)、B(蓝)顺序排列。前面2位ff表示被着色的文本完全不透明,但需要指出的是并非所有的地方都支持透明。之后就是着色的文本了,这里就是"我的插件",|r表示颜色结束,如果没有结束标记,那么WoW将颜色代码以后到这一行结束的全部文本都进行着色。
值得一提的是,这个颜色字串应用相当广泛,包括人物ID也可以,这也是为什么暴雪禁止使用符号和数字以及英文汉字混合的原因了,当然也是考虑到搜索的方便。
- Notes
只有Title和Notes标签支持其他语言,除此之外都要用英语书写。Notes包含插件的说明内容,就是对插件功能的简单描述,将出现在插件管理面板中鼠标经过的地方。
## Notes: XXX......
- Dependencies
## Dependencies: someAddOn, someOtherAddOn
## RequiredDeps: someAddOn, someOtherAddOn
## Dependancies[sic]: someAddOn, someOtherAddOn
Depend是依赖的意思。为了让插件更好地工作,某些插件开发者会使用第三方库或其他现成的插件作为基础和辅助,这么做的有点就是节约了开发成本,也使开发过程简便了许多,不足的地方就是,要让插件正常工作,系统必须确保所依赖的文件都要存在,否则,如果任何依赖的库或插件缺失,当前插件加载都会失败。依赖的名称即依赖插件的名称,也就是目录文件夹的名称。如果需要依赖多个库或插件,彼此用逗号","隔开。注意大小写一致。
注: Dependencies、RequiredDeps、Dependancies[sic]结果是一样的。
- OptionalDeps
## OptionalDeps: someAddOn, someOtherAddOn
可选依赖是当前插件为了实现某些附加功能而依赖的外部库或插件,但如果依赖的东西不存在,那么当前插件也可以正常工作,但是使用可选依赖的插件必须写明当依赖不存在的时候也可以工作。
- LoadOnDemand
## LoadOnDemand: 1
## LoadOnDemand: 0
从1.7开始,插件可以用命令来加载,而不用非得在用户第一次登录的时候加载。如果启用这一功能,此插件则必须在未来某个时候被另一个插件加载。这是为了避免加载一些特殊的不常用的插件而导致内存资源占用,非常有效。副魔助手(Enchantrix)就用到了这一特性。
- LoadWith
## LoadWith: someAddOn, someOtherAddOn
1.9新加的。和LoadOnDemand一起用,这使你的插件跟随某个插件一起被加载(通常是暴雪的UI模块,像Blizzard_AuctionUI)。
- SavedVariables
## SavedVariables: someVariable, someOtherVariable
从Interface版本2150开始有的,保存的变量是当前流行的存储不同人物角色的方式。这些变量在客户端启动或UI重载(reload)时被载入。SavedVariables标签现以取代RegisterForSave函数,后者已经不再受客户端支持。注意在OnLoad事件过程中,SavedVariables并未完全加载所以必须假设包含nil(空)值直到以插件文件名为参数的ADDON_LOADED事件被触发。
这比以前那个为保存注册变量的脚本强的多,因为即便你的插件被禁用或因为错误、版本不匹配等问题没有加载,SavedVariables依然会被保存。
- SavedVariablesPerCharacter
## SavedVariablesPerCharacter: somePercharVariable
这个标签和SavedVariables工作方式一样,只不过是给予每个角色创建的。以前这个标签只能用角色名来区分不同的角色,现在可以根据服务器和角色名一起来区分了。
- DefaultState
## DefaultState: enabled
## DefaultState: disabled
这里的enabled/disabled状态被写在WTF\Account{youraccount}\AddOns.txt里。并且这个文件会覆盖的DisabledAddOns.txt,后者为了保持兼容仍然会被老版本的加载。
- Secure
## Secure: 1
这个标签被添加到Blizzard_UI(1.11中作为默认UI一部分)。它的确切目的无从知晓,但一个可能就是它告诉客户端是否要为插件寻找一个签名。
- 非标准标签
用##标示还可以添加更多额外的信息,某些第三方插件甚至使用自行提供的信息。下面是一些常用的标签:
## Author: MyName --作者的名字,也可以是Email地址。
## Version: 1.0 --插件的版本号。可以是任何字符串,因为自动更新的工具会解析数字,所以最好是以数字版本开头。
## X-email: Author@Domain.com --任何以X-开头的标签。
## X-Date: 11-24-2016 --插件的发布日期。
这些标签和Title、Notes放在一起,并可用GetAddOnMetadata("addon", "field")来调用。
Ace2注视标签
和Ace不一样,Ace2直接从TOC文件提取插件元数据,特定的域(field)进行特定的处理,即Version和X-Date,那么你就可以用CVS和Subversion关键字例如$Rev$作为它们的值。
除了正经域以外,它还可以寻找下列自定义的域。
## X-eMail: frankcupid@hotmail.com --Email地址
## X-Website: http://wowace.com --插件的网站。
## X-Category: Raid --插件的Ace2目录。*
注: 此目录在AceAddon.lua中声明。
local CATEGORIES = {
["Action Bars"] = "Action Bars",
["Auction"] = "Auction",
["Audio"] = "Audio",
["Battlegrounds/PvP"] = "Battlegrounds/PvP",
["Buffs"] = "Buffs",
["Chat/Communication"] = "Chat/Communication",
["Druid"] = "Druid",
["Hunter"] = "Hunter",
["Mage"] = "Mage",
["Paladin"] = "Paladin",
["Priest"] = "Priest",
["Rogue"] = "Rogue",
["Shaman"] = "Shaman",
["Warlock"] = "Warlock",
["Warrior"] = "Warrior",
["Healer"] = "Healer",
["Tank"] = "Tank",
["Caster"] = "Caster",
["Combat"] = "Combat",
["Compilations"] = "Compilations",
["Data Export"] = "Data Export",
["Development Tools "] = "Development Tools ",
["Guild"] = "Guild",
["Frame Modification"] = "Frame Modification",
["Interface Enhancements"] = "Interface Enhancements",
["Inventory"] = "Inventory",
["Library"] = "Library",
["Map"] = "Map",
["Mail"] = "Mail",
["Miscellaneous"] = "Miscellaneous",
["Quest"] = "Quest",
["Raid"] = "Raid",
["Tradeskill"] = "Tradeskill",
["UnitFrame"] = "UnitFrame",
}
XXX.toc文件样例:
## Interface: 20300
## Title : My AddOn
## Notes: This AddOn does nothing but display a frame with a button
## Author: My Name
## eMail: Author@Domain.com
## URL: [http://www.wowwiki.com/](http://www.wowwiki.com/)
## Version: 1.0
## Dependencies: Sea
## OptionalDeps: Chronos
## DefaultState: enabled
## SavedVariables: settingName, otherSettingName
myAddOn.lua
myAddOn.xml
XXX.xml
UI xml文件基本格式
<Ui xmlns="[http://www.blizzard.com/wow/ui/"]
xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance"]
xsi:schemaLocation="[http://www.blizzard.com/wow/ui/] ..\FrameXML\UI.xsd">
<Frame name="MyAddon_Frame">
</Frame>
</Ui>
其中ui元素是整个Ui的根元素,下面的Frame元素是我们要添加的界面框体。我们可以这样理解,在魔兽世界中,所有的视觉界面元素都是一个Frame,比如窗口,按钮,文本框,他们都是具备特殊性质的Frame。一个Frame里面又可以包含多个Frame,例如一个窗口里面可以有几行文字,几个按钮。这样就可以构成任意复杂的界面了。
Frame可以有一些属性,来指定他的名字,父容器,大小,位置之类的信息。我们的Frame应该具有如下的属性:
Frame代码
<Frame name="HelloWorldTestFrame" parent="UIParent" hidden="false">
<Size x="300" y="150" />
<Anchors>
<Anchor point="CENTER" />
</Anchors>
</Frame>
其中name属性是指定这个Frame的名字,以便我们后面使用;parent属性指定了父容器,也就是说我们的Frame是被放在UIParent这个Frame里面的,UIParent是所有UI元素的父容器,你可以把它看作WoW的整个窗口;hidden属性决定了我们的窗口是否隐藏,为false即是不隐藏,直接显示出来;Size元素指定了窗口的大小;Anchors元素指定了窗口的位置,CENTER表示窗口应该被放在父容器的中心。
这样子的Frame只是一个透明的容器,没有边框,没有背景,于是我们应该加上一个窗口的背景。
加上背景的Frame代码
<Frame name="HelloWorldTestFrame" parent="UIParent" hidden="false">
<Size x="300" y="150" />
<Anchors>
<Anchor point="CENTER" />
</Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11" />
</BackgroundInsets>
</Backdrop>
</Frame>
Backdrop元素就是背景图专用的,bgFile指定了背景图片名,edgeFile指定了边框图片名,我们现在都用游戏内置的。BackgroundInsets元素指定了背景图的边距,这样我们可以把背景放在边框里面,而不会溢出。 现在我们的窗口有了背景,但是还没有Hello World!的文字。为了加入文字,我们还需要加入一些代码:
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Frame name="HelloWorldTestFrame" parent="UIParent" hidden="false">
<Size x="300" y="150" />
<Anchors>
<Anchor point="CENTER" />
</Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11" />
</BackgroundInsets>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<FontString inherits="GameFontNormal" text="Hello World!">
<Anchors>
<Anchor point="CENTER" relativeTo="HelloWorldTestFrame" />
</Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
</Ui>
我们的可视化元素,是被放进若干个Layer来渲染的,ARTWORK是其中一个Layer,位于背景Layer的上方。也就是说,被放进ARTWORK层的所有元素,都会被渲染在背景层的上方,盖住背景层的一切东西。层的渲染顺序是BACKGROUND,ARTWORK,OVERLAY。如果你想渲染一些东西在最上层,就放进OVERLAY层里。我们现在选择ARTWORK层当作我们Hello World文字的渲染层。 FontString就是可以画一些文字的地方,inherits是指所有未明确标注的属性都由GameFontNormal类继承,比如文字的字体、大小、颜色什么的,我们就不一一设置了,直接偷懒从GameFontNormal拿来。text属性就是我们要渲染出来的文字内容。Anchors元素继续用来指定位置。把上面的文件内容存入HelloWorld.xml中,就算搞定啦!
http://bbs.ngacn.cc/read.php?tid=2013286&forder_by=postdatedesc
网友评论