Unity 3-8 Lua编程
任务1&2&3:前言
课程内容:
Lua从入门到掌握
为之后的xLua和其他热更新方案打下基础
任务4:Lua简介
Lua是轻量小巧的脚本语言--无需编译,用标准c语言编写,开源
目的是为了嵌入应用程序中,为应用程序提供灵活的扩展和定制功能
可以很方便地与其他程序进行集成(如C++, C#, Java等)
-- 因为Lua无需编译,对于某些功能,如果不方便重新编写,可以使用Lua进行功能扩展
Lua应用场景:
游戏开发
独立应用脚本
Web应用脚本
扩展和数据库插件
安全系统(如入侵检测系统)
Lua和c#的区别:
Lua可以在几乎所有操作系统/平台运行
可以很方便的更新代码,代码更新后无需重新安装 -- 后续的热更新方案
不需要c#那样重新编译打包
c#只能在特定的操作系统中进行编译成dll文件,然后打包进安装包在其他平台运行
在移动平台上不能更新替换已有的dll文件,除非重新下载安装包
学习资料:
Programming in Lua
www.runoob.com/lua/lua-tutorial.html -- 菜鸟教程
www.luaer.cn -- Lua中国开发者
官网:www.lua.org
任务5&6:Lua的安装(SciTE && LuaDist) 和第一个Lua程序
http://www.runoob.com/lua/lua-environment.html
SciTE -- https://github.com/rjpcomputing/luaforwindows/releases,带IDE
LuaDist -- 无IDE
任务7:注释
-- 单行注释
--[[ 多行注释 ]]--
-- 单行注释 --[[ 多行注释 小技巧:在多行注释开头再加一个-,会使多行注释代码块生效 原因是第一行和最后一行都变成了单行注释 ]]-- ---[[ print("Execute") --]]
任务8:标识符命名规则和关键字
标识符:字母或_开头,后跟0或多个字母或_或数字
不推荐"_跟大写字母作为标识符",比如"_NAME",这是lua的保留字命名规则(Lua内部使用的变量名)
标识符区分大小写
任务9、21:全局变量和局部变量
默认情况下的变量都是全局的
全局变量不需要声明,给一个变量赋值后就相当于创建了这个全局变量,比如 a = 10
访问一个没有赋值的全局变量,此时不会报错,但是变量不存在,比如 print(b) 的结果为 nil
而如果需要删除一个全局变量,赋值为nil即可,如 a = nil,删除后则a不存在
局部变量的声明:local b = 10
局部变量的生命周期为:从声明开始,到所在语句块结束
如果是语句块是在最外围,则整个lua脚本文件的语句执行完后才被销毁
function f () a = 1 local b = 2 -- a为全局变量,b为局部变量 end f() -- 注意,如果这边没有调用函数f(),则上面函数定义中的变量是没有被创建的 print(a) -- 1 print(b) -- nil
函数也是可以通过local来修饰为局部函数的
局部变量和全局变量的重名规则:
有局部变量时局部变量优先
一般而言,访问局部变量的速度会比访问全局变量快,而且局部变量在生命周期结束后会被销毁释放内存空间
任务10~20:数据类型的简介
基本类型:
nil -- 只有一个值nil,表示无效值,条件表达式内相当于false
boolean -- true / false
number -- 双精度实浮点数
string -- "string" 或 'string'
function -- 函数
userdata -- 任意存储在变量中的C数据结构
thread -- 独立执行的线路,协程(非线程)
table -- 表(关联数组),索引为数字或字符串,{}表示一个空表
使用type可以获取变量或者值的类型,如
print(type("hellolua")) -- string print(type(10)) number print(type(1.1) number print(type(print)) function print(type(type)) function print(type(nil)) nil print(type(x)) nil
nil:nil类型只有一个值nil,表示无效值,比如当变量未被赋值时,即为nil值
给变量赋值nil后,表示将该变量销毁,释放内存
boolean:nil值视为为false,其他变量值都视为true,比如if(10)是true
string:
表示方法:"" 或 ‘’ 或 [[ ]] (表示多行)
"string"
'string'
[[str
in g ]]
字符串的拼接,不能用 + 号,只能用 .. ,如 print("a".."b") --> "ab" ; print(2..6) --> 26
print("2" + "6") --> 8 会自动转换为number类型
#表示字符串长度(字节数),如 s = "ab呀"; print(#s) --> 4
table:
构造方法及使用
table1 = { } -- 构造表达式,得到一个空表 print(table1) -- -> table:00A89600 print(table1.key1) -- -> nil table2 = {key1 = 100, key2 = "aaa"} print(table2.key1) -- ->100 print(table2["key1"]) -- ->100 table3 = {"111", "222", "hello"} print(table[2]) -- -> 222 -- Lua中没有数组,用table表示数组。 -- 在Lua中,索引一般是从1开始算的,不是0 -- table的索引 for key,val in pairs(table3) do print(key..":"..val); end -- -> 1:111 2:222 3:hello
table的大小不是固定的,可以很灵活的进行增删查改
-- 对于字典形式 tbl = {key1 = "aaa"} -- 修改 tbl.key1 = 123 -- 类型可以不相同 -- 增加 tbl["key2"] = "value" tbl.key222 = "value" tlb[10] = 1000 -- 删除 tbl.key2 = nil -- 此时进行遍历就不会遍历到key2了 -- 对于数组形式(实质上还是字典,只不过把自动索引作为了key) tbl = {"1", "a"} tbl[2] = "b" -- 修改 tbl[3] = "c" -- 增加 tbl[2] = nil -- 删除 -- 注意,这里的数组本质上还是键值对的形式 -- 比如这边把索引为2的删除后,遍历可得到 --> 1:1 3:c (索引不是连续的) -- 也同样可以添加键值对 tbl["key5"] = "value5" -- 删除整个表 tbl = nil
function:函数
不需定义返回值类型,但是可以返回任意类型返回值
函数体为 从 function name(argument) 到 end 之间
function f1(n) print(n) return n end
可作为一个类型作赋值操作
f2 = f1 -- 此时f2也为一个function类,函数内容和f1一样
可以作为参数传递
function func1(table fun) for key, value in pairs(table) fun(key, value) end end function func2(key, value) print(key..":"..value) end func1(table, func2) -- 遍历table并输出key,value
匿名函数:只能使用一次,不用定义函数名
-- 以上一段代码为例,调用的时候 func1(table, function (key, value) print(key..":"..value) end ) -- 遍历table输出键值对
thread:协同程序
不是线程
拥有自己独立的栈、局部变量和指令指针
可以共享全局变量和其他大部分东西
任意时刻,协程只能运行一个,只有被挂起 suspend 的时候协程才会暂停
userdata:自定义类型
用于表示由应用程序或C/C++创建的类型,可以将C/C++任意数据类型 (通常为struct和指针) 存储到lua变量中进行调用
任务22:多变量赋值
a, b, c = 1, 2, "c"
a, b = b, a -- 并不是简单的a = b; b = a;,而是很方便的交换了a和b的值 -- 结果为a = 2, b = 1
因为lua是将右边的值全部计算完后,再统一赋值的
a, b = 10, 20, 30 -- 30会被忽略
a, b, c = 10, 20 -- 未赋值的变量会初始化为默认值,即为nil
函数function也是可以直接返回多个值的
function test()
return 1,2
end
a, b = test()
任务23、24、26:流程控制
while循环:
while (condition) do
statements
end
condition的括号可加可不加
for循环:
数值for循环
for var = start, end, step do
statements
end
var从start到end (inclusive),每次增加step
step未指定时默认为1
泛型for循环
for key, value in pairs (table) do
statements
end
repeat unitl循环(和do while流程相同,逻辑相反):
repeat
statements
until (condition)
直到满足condition后,就跳出循环(而不是do while的继续循环)
if判断:
if (condition) then
statements
end
-- 注意,数值0表示true,nil值表示false
if (condition) then
statements
elseif (condition) then
statements
else
statements
end
任务27、28:function详解
[local] function name(arg1, arg2, ..., argn)
statements
[return value1, value2, ... valuen]
end
1. function本质为变量类型,因此可以作为数据直接赋值,也可以作为参数传递
2. 可直接返回多个值,可用多变量赋值的方式接收返回值(如果接收返回值的变量数不对,则遵循多变量赋值的规则)
如:func返回值为4个,调用处代码 a, b = func(),则后两个返回值就没有获取到了
3. 可变参数,传递的参数个数可以是多个
-- 定义时 function func(...) print(arg) -- 这边会将多个参数封装成一个table类型,以便使用 -- table中保存了所有传入的参数,最后一位保存了参数的总个数 local arg = {...} -- 此时args里保存的为所有传入的参数,不带最后一个总个数了 end -- 调用时 func(a) func(a, b)
任务29:运算符
数学运算符:
^ -- 求幂
% -- 求余(可对小数进行求余)
关系运算符:
~= -- 不等
逻辑运算符:
and -- 与
or -- 或
not -- 非
其他运算符:
.. -- 字符串连接
# -- 取得字符串长度
任务32、33:字符串常见操作
newstring = string.upper(oldstring)
newstring = string.lower(oldstring)
newstring = string.gsub(oldstring, "o", "r", num) -- 将str1中的 o 替换成 r, num 表示替换的最大次数,可以不传
index = string.find(str, "substr", num) -- 从num开始,搜索str是否有子字符串substr,返回子字符串开始的索引
newstring = string.reverse(oldstring)
str = string.format("%02d", month)
str = string.char(97, 98, 99) -- 对应ASCII码。abc
integer = string.byte("ABCD", num) -- 返回num个字符对应的值。不写num时默认为第一个字符
newstring = string.rep(oldstring, count) -- oldstring重复count次
string.gmatch()/ string.match() -- 正则表达式
任务35:多维数组
array = { {"a", "b"}, { "1", "2"}, {"xx", "yy"} }
print(array[1][1]) -- 输出a
任务36、37:迭代器函数
pairs 遍历 table,表中所有的key和value
ipairs 按索引遍历,从1开始,递增遍历,遇到nil值就停止
自定义迭代函数:
.......
......
......
......
......
.......
......
......
......
......
.......
......
......
......
......
.......
......
......
......
......
任务50~54:协同程序
Lua中协程和线程的区别有:线程是由CPU进行任务调度的,可以视为同一时间可以同时运行多个线程
而协程是在同一个线程中的,同一时刻只能在运行一个协程,需要代码控制多协程的协作,对协程进行挂起或继续等操作
基本用法:
-- 定义方法1 co = coroutine.create ( function (a,b) -- 这里是一个匿名函数 print(a) end ) -- 对应的调用 coroutine.resume(co, 20, 30) -- 定义方法2 co = coroutine.wrap ( function (a,b) -- 区别只有调用的函数不同,这边是wrap print(a) end ) -- wrap对应的调用,直接启动即可,不需通过resume() co(20,30) -- 挂起:在上面定义的匿名函数中,使用coroutine.yield()语句即可挂起协程 co = coroutine.wrap ( function (a,b) -- 区别只有调用的函数不同,这边是wrap print(a) coroutine.yield() print(b) end ) -- 恢复运行协程 -- 注意:当一个协程执行完毕return后,再进行resume并传参进去,并不能再次开始执行协程 coroutine.resume(co) -- 返回值 -- 假设上面的匿名函数的最后一句为 return a+b, a-b -- 调用/继续的时候接收返回值(如果没有执行到return语句,只会有第一个true/false的返回值表示该次调用/继续是否成功) res1, res2, res3 = coroutine.resume(co, 20,30) print(res1, res2, res3) -- 结果为true, 50, -10,其中第一个返回值表示resume成功与否 -- 如果想在挂起的时候有返回值的话,在yield中传递参数 coroutine.yield(a*b) -- 调用/继续resume的时候会得到额外的返回值 res1, res2 = coroutine.resume(co, 20, 30) -- res1 = true, res2 = 600
其他函数:
coroutine.status(co)
有三个值:
running:协程正在执行过程中
suspended:协程未开始或暂停挂起后
dead: 协程执行完毕后
coroutine.running()
当前正在运行的协程,返回值为协程的地址
如果当前没有正在运行的协程(包括未开始、挂起都不算正在运行),则输出nil
加大难度:协同程序内部和外部的数据交流
https://www.runoob.com/lua/lua-coroutine.html
function foo (a) print("foo 函数输出", a) return coroutine.yield(2 * a) -- 返回 2*a 的值 end co = coroutine.create(function (a , b) print("第一次协同程序执行输出", a, b) -- co-body 1 10 local r = foo(a + 1) print("第二次协同程序执行输出", r) local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入 print("第三次协同程序执行输出", r, s) return b, "结束协同程序" -- b的值为第二次调用协同程序时传入 end) print("main", coroutine.resume(co, 1, 10)) -- true, 4 print("--分割线----") print("main", coroutine.resume(co, "r")) -- true 11 -9 -- 此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数 print("---分割线---") print("main", coroutine.resume(co, "x", "y")) -- true 10 end print("---分割线---") print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine print("---分割线---") --[[ 第一次协同程序执行输出 1 10 foo 函数输出 2 main true 4 --分割线---- 第二次协同程序执行输出 r main true 11 -9 ---分割线--- 第三次协同程序执行输出 x y main true 10 结束协同程序 ---分割线--- main false cannot resume dead coroutine ---分割线--- ]]--
任务55~58:文件操作
I/O的简单模式:
file = io.open(filename [, mode])
mode:
r -- 只读,文件必须存在
w -- 只写,若文件已存在则清空内容后开始写入,若文件不存在则创建新文件开始写入
a -- 附加方式打开只写文件,若文件不存在则创建,若文件存在则从文件尾(保留EOF符)开始写入
r+ / w+ / a+:文件是可读写文件
b -- 针对二进制文件,进行二进制模式的打开
io.flush() -- 向文件中写入缓冲中的所有数据
-- 读取 file = io.open("name.txt", "r") io.input(file) io.read() -- 读取文件流中的一行数据 io.read("*l") --默认值,和io.read()一样,读取下一行,在文件尾EOF处返回nil io.read("*n") -- 读取一个数字 io.read("*a") -- 读取文件中的所有内容 io.read(9) -- 读取之后的9个字符 io.close(file) -- 写入 file = io.open(..., "a") io.output(file) io.write("new string") io.close(file)
I/O的完全模式:需要同一时间处理多个文件时
以file:的方式进行调用(注意是 : ),而不是io.的方式
file = io.open("xx.txt","a") file:read() file:write("xxxxx") file:close()
file:seek(optional whence, offset) -- 把光标移到对应位置后开始操作
任务59:垃圾回收机制
Lua采用了自动内存管理机制