Lua脚本开发踩坑记录:从基础到协程的实际应用

Lua脚本开发踩坑记录:从基础到协程的实际应用

这篇文章是我在游戏开发中使用Lua脚本时整理的一些笔记,从基础语法到协程应用,记录遇到的问题和解决方案。

Lua语言特性

核心特点

1
2
3
4
5
6
7
8
轻量级: 用标准C编写,仅百余KB,便于嵌入其他程序
可扩展: 由宿主语言(C/C++)提供功能,Lua调用
支持面向过程和函数式编程
自动内存管理
通用表(table)实现数组、哈希表、集合、对象
内置模式匹配和闭包(closure)
多线程支持(协同进程)
通过闭包和table支持面向对象关键机制

应用场景

  • 游戏开发(Cocos2d-x、Love2D等)
  • 独立应用脚本
  • Web应用脚本
  • 数据库插件(MySQL Proxy、MySQL WorkBench)
  • 安全系统(入侵检测)

基础语法

注释

1
2
3
4
5
6
-- 单行注释

--[[
多行注释
可以写多行内容
--]]

全局变量

Lua默认变量为全局变量:

1
2
3
4
5
6
7
> print(b)      -- 输出 nil
> b = 10
> print(b) -- 输出 10

-- 删除全局变量
b = nil
print(b) -- 输出 nil

变量存在条件:不等于nil即存在。

数据类型

类型 描述
nil 无效值,条件表达式中相当于false
boolean false和true
number 双精度实浮点数
string 双引号或单引号表示的字符串
function C或Lua编写的函数
userdata C数据结构
thread 协同进程
table 关联数组,索引可为数字、字符串或表

类型检测

1
2
3
4
5
print(type("Hello world"))      --> string
print(type(10.4*3)) --> number
print(type(print)) --> function
print(type(true)) --> boolean
print(type(nil)) --> nil

注意: nil比较时需加双引号

1
2
> type(X) == nil        -- false
> type(X) == "nil" -- true

布尔值

Lua将false和nil视为false,其他都为true(包括数字0):

1
2
3
if 0 then
print("0 is true") -- 会执行
end

数字类型

1
2
3
4
print(type(2))          --> number
print(type(2.2)) --> number
print(type(0.2)) --> number
print(type(2e+1)) --> number

字符串

长字符串:

1
2
3
4
5
6
7
8
html = [[
<html>
<head></head>
<body>
<a href="http://www.runoob.com/">菜鸟教程</a>
</body>
</html>
]]

字符串自动转换:

1
2
3
> print("2" + 6)        --> 8.0
> print("2" + "6") --> 8.0
> print("error" + 1) -- 错误:attempt to perform arithmetic on a string value

字符串连接:

1
2
> print("a" .. 'b')     --> ab
> print(157 .. 428) --> 157428

字符串长度:

1
2
3
> len = "www.runoob.com"
> print(#len) --> 14
> print(#"www.runoob.com") --> 14

表(Table)

表是Lua的核心数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 创建空表
a = {}

-- 使用字符串索引
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11

-- 遍历表
for k, v in pairs(a) do
print(k .. " : " .. v)
end
-- 输出:
-- key : value
-- 10 : 33

数组(默认从1开始):

1
2
3
4
5
tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
print("Key", key)
end
-- 输出 Key 1, Key 2, Key 3, Key 4

函数

函数可作为参数传递:

1
2
3
4
5
6
7
8
9
10
function testFun(tab, fun)
for k, v in pairs(tab) do
print(fun(k, v));
end
end

tab = {key1="val1", key2="val2"};
testFun(tab, function(key, val)
return key.."="..val;
end);

多返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

可变参数:

1
2
3
4
5
6
7
8
9
10
11
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))

变量与作用域

变量类型

  • 全局变量:默认情况下所有变量都是全局的
  • 局部变量:使用local声明,作用域为声明位置到语句块结束
  • 表中的域:表的字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

多变量赋值

1
2
3
4
5
a, b = 10, 2*x       -- 等价于 a=10; b=2*x

-- 交换变量值
x, y = y, x
a[i], a[j] = a[j], a[i]

变量与值数量不一致:

1
2
3
4
5
a, b, c = 0, 1
print(a, b, c) --> 0 1 nil

a, b = a+1, b+1, b+2
print(a, b) --> 1 2(b+2被忽略)

索引方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 方括号索引
t[i]

-- 点号索引(索引为字符串时的简化写法)
t.i

-- 等价于
gettable_event(t, i)

-- 实例
site = {}
site["key"] = "www.runoob.com"
print(site["key"]) --> www.runoob.com
print(site.key) --> www.runoob.com

控制流程

if语句

1
2
3
4
5
6
7
8
9
if (布尔表达式1) then
-- 布尔表达式1为true时执行
elseif (布尔表达式2) then
-- 布尔表达式2为true时执行
elseif (布尔表达式3) then
-- 布尔表达式3为true时执行
else
-- 以上都不为true时执行
end

while循环

1
2
3
while (condition) do
statements
end

for循环

数值for循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
for var = exp1, exp2, exp3 do
<执行体>
end

-- exp3为步长,可选,默认为1
for i = 1, f(x) do
print(i)
end

-- 递减循环
for i = 10, 1, -1 do
print(i)
end

泛型for循环:

1
2
3
4
5
-- ipairs迭代数组
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end

repeat…until循环

1
2
3
4
5
6
7
8
9
10
repeat
statements
until (condition)

-- 实例
a = 10
repeat
print("a的值为:", a)
a = a + 1
until (a > 15)

循环控制

语句 作用
break 退出当前循环
goto 跳转到标签位置

运算符

算术运算符

运算符 描述 实例
+ 加法 A + B = 30
- 减法 A - B = -10
* 乘法 A * B = 200
/ 除法 B / A = 2
% 取余 B % A = 0
^ 乘幂 A^2 = 100
- 负号 -A = -10

关系运算符

运算符 描述
== 等于
~= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

逻辑运算符

运算符 描述
and 逻辑与
or 逻辑或
not 逻辑非
1
2
3
4
5
6
A = true
B = false

print(A and B) --> false
print(A or B) --> true
print(not(A and B)) --> true

其他运算符

运算符 描述 实例
.. 连接字符串 “Hello “ .. “World”
# 返回长度 #”Hello” = 5

运算符优先级(从高到低)

1
2
3
4
5
6
7
8
^
not - (unary)
* / %
+ -
..
< > <= >= ~= ==
and
or

字符串操作

常用字符串函数

函数 用途
string.upper(arg) 转大写
string.lower(arg) 转小写
string.gsub(mainString, findString, replaceString, num) 替换
string.find(str, substr, [init, [end]]) 查找位置
string.reverse(arg) 反转
string.format(…) 格式化
string.char(arg) 数字转字符
string.byte(arg[, int]) 字符转数字
string.len(arg) 长度
string.rep(string, n) 重复n次

字符串操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 替换
> string.gsub("aaaa", "a", "z", 3);
zzza 3

-- 查找
> string.find("Hello Lua user", "Lua", 1)
7 9

-- 反转
> string.reverse("Lua")
auL

-- 格式化
> string.format("the value is:%d", 4)
the value is:4

-- 数字转字符
> string.char(97, 98, 99, 100)
abcd

-- 字符转数字
> string.byte("ABCD", 4)
68

模式匹配

1
2
3
4
5
6
7
8
9
-- gmatch返回迭代器
for word in string.gmatch("Hello Lua user", "%a+") do
print(word)
end
-- 输出:Hello, Lua, user

-- match只寻找第一个匹配
> = string.match("I have 2 questions for you.", "%d+ %a+")
2 questions

表操作

函数 用途
table.concat(table [, sep [, start [, end]]]) 连接数组元素
table.insert(table, [pos,] value) 插入元素
table.maxn(table) 最大正数key(Lua5.2已移除)
table.remove(table [, pos]) 删除元素
table.sort(table [, comp]) 排序

模块与包

创建模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- module.lua
module = {}

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

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

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

-- 公有函数调用私有函数
function module.func3()
func2()
end

return module

使用模块

1
2
3
4
5
6
7
8
9
-- 方式一
require("module")
print(module.constant)
module.func3()

-- 方式二:使用别名
local m = require("module")
print(m.constant)
m.func3()

协程(Coroutine)

协程特点

  • 拥有独立的堆栈、局部变量和指令指针
  • 与其他协程共享全局变量
  • 任意时刻只有一个协程在运行
  • 只有被挂起(suspend)时才会暂停

基本方法

方法 描述
coroutine.create() 创建协程,返回coroutine
coroutine.resume() 启动或恢复协程
coroutine.yield() 挂起协程
coroutine.status() 查看状态(dead/suspended/running)
coroutine.wrap() 创建协程,返回函数
coroutine.running() 返回正在运行的协程

协程示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
co = coroutine.create(
function(i)
print(i);
end
)

coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead

-- 使用wrap
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)

-- 生产者-消费者模型
co2 = coroutine.create(
function()
for i = 1, 10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) -- running
print(coroutine.running())
end
coroutine.yield()
end
end
)

coroutine.resume(co2) -- 1
coroutine.resume(co2) -- 2
coroutine.resume(co2) -- 3

print(coroutine.status(co2)) -- suspended

生产者-消费者实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
local newProductor

function productor()
local i = 0
while true do
i = i + 1
send(i)
end
end

function consumer()
while true do
local i = receive()
print(i)
end
end

function receive()
local status, value = coroutine.resume(newProductor)
return value
end

function send(x)
coroutine.yield(x)
end

newProductor = coroutine.create(productor)
consumer()

文件I/O

文件模式

模式 描述
r 只读,文件必须存在
w 只写,存在则清空,不存在则创建
a 追加写,文件内容保留
r+ 可读写,文件必须存在
w+ 可读写,存在则清空
a+ 可读写,追加模式
b 二进制模式

简单模式

1
2
3
4
5
6
7
8
9
10
11
-- 读取文件
file = io.open("test.lua", "r")
io.input(file)
print(io.read())
io.close(file)

-- 写入文件
file = io.open("test.lua", "a")
io.output(file)
io.write("-- test.lua 文件末尾注释")
io.close(file)

元表(Metatable)

元表用于定义两个表的操作行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 设置元表
setmetatable(table, metatable)

-- 获取元表
getmetatable(table)

-- 定义__add元方法
mytable = setmetatable({1, 2, 3}, {
__add = function(table1, table2)
for i = 1, #table2 do
table.insert(table1, table2[i])
end
return table1
end
})

以上是我在游戏开发中使用Lua脚本时整理的一些经验,涵盖了基础语法到协程应用的各个方面。