Lua编程语言给人的感觉是小巧,简洁,解释性语言,弱类型,主要用于游戏开发,嵌入式脚本开发。
此次学习源于写饥荒脚本,用饥荒学习Lua绝对是不错的一个实战。
首先在官网下载
Lua下载是一个压缩文件,解压即可用,但是需要先编译一下。
默认是没有可执行程序的,需要使用命令制作一下。
在当前src
目录下打开终端窗口,执行命令
make
make
之后生成lua
和luac
两个可执行程序
这样我们就可以用Lua
这个可执行程序执行我们编写的.lua文件了。
我使用的是IDE是Idea,Idea需要编写Lua需要下载一个插件:EmmyLua
使用这个插件可以高亮显示还有方法提示,可以Tab键自动补全。
安装后运行Lua文件的需要配置文件的解释程序。
这样整体的开发环境就搭配好了。
单行注释
-- Lua 的单行注释使用 `--`
print("Hello Lua")
多行注释
--[[
多行注释1
多行注释2
多行注释3
]]
print("Hello Lua")
和其他编程语言一样,Lua的标识符由字母、数字以及下划线组成,且不能以数字开头,不能是关键字
比如:__index
Lua作为弱类型语言,变量的定义不需要声明数据类型。
-- 整数变量 a
a = 1
-- 字符串变量 a
a = "Test"
-- 布尔类型 a
a = false
变量的作用域默认是全局,只有声明为local
的变量才是局部变量
弱类型语言的变量使用很随意,如果变量的数据类型使用错误,只有在运行时才能发现,体现的是规范大于约定的思想。
虽然变量的定义不需要声明数据类型,但是Lua大体上还是分为8种数据类型
nil代表空值,被赋值为nil的变量将清空,userdata和thread目前还没接触,其他类型只有nil和table的概念比较特殊。
注:Lua中的布尔值只有nil和false为条件表达式的否判断。
-- number类型也是真值
a = 1
if a then
print("a是ture")
end
-- 函数类型也是真值
function fn()
print("fn函数")
end
if fn then
print("fn函数只要不为nil也是真值")
end
1)算术运算符
+
加法-
减法*
乘法/
除法(真实运算,不会取整数,Java里边10/3=3,这里10/3=3.3333333333333)%
求余^
幂运算( 2^3=8.0)-
负号,对数字前面直接加取负数//
整数运算,这个才是同Java的除法运算,10/3=32)逻辑运算符
3)关系运算符
>
<
==
>=
<=
~=
注意Lua的不等于写法~=
这个还是挺新鲜的写法。
4)其他运算符
Lua 没有类似++
, --
的操作,循环时候一定要注意!
..
拼接字符串,同Java的 + 拼接字符串#
返回字符串或者表的长度print(#"Hello") -- 输出 5
tab = {1,2,3}
print(tab) -- 输出3
Lua循环和其他语言类似,只是需要注意基本的格式。
三种循环方式
-- 方式一 while 循环
a = 0
while a < 10 do
print("while循环"..a)
a = a + 1
end
-- 方式二 for 循环
for i = 1, 10 do
print("for循环"..i)
end
-- 方式三 repeat 循环
c = 0
repeat
print("repeat循环"..c)
c = c + 1
until c > 10
repeat … until 循环好比是Java中的 do…while循环,至少执行一次。
-- if 结构
if a>b then
return a
end
-- if elseif else 结构
if a > 90 then
print("优秀")
elseif a > 80 then
print("良好")
elseif a > 60 then
print("及格")
else
print("不及格")
end
循环和流程控制都没有特殊的,需要注意的是基本格式,Lua里边无论函数还是循环控制,都是以end结尾
Lua函数比较特殊,第一次遇到可以返回多个值。
-- 方式一
function f1()
-- do something
end
-- 方式二
local f2 = function(arg1,arg2,arg3)
-- do something
return arg1,arg2,arg3
end
Lua的函数修饰符只有local
定义是否为局部函数,参数可选。主要是可以类似函数f2,可以返回多个数据。
接收函数返回值的时候也可以多个变量一一对应接收。
function f3()
return 1,2,3
end
a,b,c = f3()
print(a,b,c) -- 输出 1,2,3
函数接收可变参数有个内置变量arg这个需要主要,他是一个表table
-- `...` 代表可变参数
function f4(...)
local arg = {...}
-- 遍历方式一
for k, v in pairs(arg) do
print(k,v)
end
-- 遍历方式二
for k, v in ipairs{...} do
print(k,v)
end
print(#arg) -- 定义 local = {...} 可通过 #arg 获取参数个数
print(select("#",...)) -- select("#",...) 获取可变参数个数
end
f4(1,2,3,4) -- 最后打印 4
-- 函数作为参数传递
function f5(func)
local ret = func
end
注:可变参数的获取方式和数据获取方式是重点
另外,ipairs和pairs的主要区别是ipairs遇到nil则会停下来,而pairs遍历不会。
for k,v in pairs(table) do … end 这是一种基本固定的遍历表的写法,类似Java中的增强for循环。
函数作为参数传递给下个函数时,函数内可以调用这个函数做一些数据处理
function max(a,b)
if a > b then
return a
else
return b
end
end
function printMax(a,b,maxFunc)
print(maxFunc(a,b))
end
printMax(2,3,max)
表是Lua中最常用的数据结构了,基本上数据的使用都是通过表,但我理解更像是一个Map容器,存储一些key,value的键值对。
-- 数组没有单独的表示方式,数组就是用表表示的,只不过没有key,默认key就是1,2,3...连续索引
-- 一对花括号就代表这是一个表,前面的数据类型有一个就是table表数据类型
tab = {}
-- 也可以初始化(数组)
fruits = {"apple","peach","banana"}
-- 这个更应该被认为是表,以指定的key-value形式存在
table_define = {
key1 = "val",
key2 = "val2",
key3 = "val3"
}
-- 或者给表增加数据
tab["key"] = "value"
-- 赋值 nil 就清空了一个表
tab = nil
-- 表的引用赋值给另一个
newFruits = fruits
fruits = nil -- newFruits 指向的表实际还在,这里只是清空了fruits的引用而已
注:Lua中的索引都是从1开始的,而不是0!
tab = {}
-- 默认插入表的末尾
table.insert(tab,"value1")
print(tab[1])
-- 插入指定索引位置
table.insert(tab,1,"value2")
print(tab[1])
fruits = {"apple", "peach", "banana", "orange"}
fruits[1] = nil
print(fruits[1]) -- nil
table.remove(fruits,2)
print(fruits[2]) -- banana
-- 默认移除表最后的数据
table.remove(fruits)
for k,v in pairs(fruits) do
print(v) -- banana
end
tab = {"Lua", "C#", "Java"}
-- 拼接数据元素
print(table.concat(tab)) -- LuaC#Java
-- 指定拼接的分隔符
print(table.concat(tab," ")) -- Lua C# Java
-- 指定分隔符和指定索引范围内数据拼接
print(table.concat(tab," ",2,3)) -- C# Java
tab = {"Lua", "C#", "Java"}
-- 调用table的排序方法
table.sort(tab)
这里才是最接近编程开发的概念了,将代码进行模块化管理,但是Lua并没有真正的包Module的概念,包也是通过表来实现的。
Module = {}
Module.name = "模块包"
Module.func = function()
print("文件module.lua中的函数func")
end
-- 可以通过local封装,只能通过方法调用
local address = "SZ"
Module.getAddress = function()
return address
end
local func_local = function()
print("local函数")
end
Module.getLocalFunc = function()
func_local()
end
包的引入使用关键字require
-- 两种写法,之类的module,是文件名,module.lua这个文件就被引入进来了
require "module"
require("module")
-- 引入包之后就可以访问包的内容了
print(Module.name)
Module.func()
print(Module.getAddress())
print(Module.getLocalFunc())
元表理解更像是定义元
那样,更微小的单元,他允许我们去定义一个表之外的行为,除了赠删改之外的动作。比如两个表进行相加。
myTable = {} -- 普通表
myMetaTable = {} -- 元表
-- 会返回普通表
myTable = setmetatable(myTable,myMetaTable) -- 将myMetaTable设置为myTable的元表
getmetatable(myTable) -- 返回 myMetaTable 元表
当查找普通表中的key对应值为没有时,则去调用此方法寻找对应的表中的数据。
理解更像是对普通表的一种扩展。
other = {key = 3}
t = setmetatable({},{__index = other}) -- 普通表是空的,元表存在元素 key = 3
print(t.key) -- 3
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。
mytable = setmetatable({ key1 = "value1" } , {
__index = function(mytable,key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
来源菜鸟教程
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
__newIndex针对不存在的key进行赋值时候调用
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)
__tostring使得print(table)时候可以输出字符串而不是表的引用
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)
协同程序实际上就是协同函数,让函数可以再执行到一半的时候挂起,然后再继续执行下面的代码,知道执行启动该协同函数才会继续执行协同函数肚饿代码。
local co
-- 生产者每生产一个就提供给消费者
function productor()
local i = 0
while true do
i = i + 1
print("生产者生产\t"..i)
sleep(1)
coroutine.yield(i)
end
print("无产品生产,生产者结束")
end
function consumer()
while coroutine.status(co) ~= "dead" do
print("消费者唤醒生产")
local status, value = coroutine.resume(co)
print("消费者消费\t"..value)
sleep(1)
end
print("消费者得知生产结束,消费结束")
end
function sleep(n)
local t = os.clock()
while os.clock() -t <=n do end
end
-- 创建生产者协同函数
co = coroutine.create(productor)
-- 开始消费
consumer()
注:Lua语言没有原生的线程睡眠函数,这里定义的也不是很懂,大概理解是os.clock返回程序运行时长,用时间差判断得到睡眠时间。
文件IO分为简单模式和完全模式,文件打开针对权限分为:
-- 以只读的方式打开一个文件
file = io.open("tmp.txt","r")
-- 设置io的输入文件是file
io.input(file)
-- 读取一行
line = io.read()
-- 关闭文件也要指定文件
io.close(file)
-- 以只读的方式打开tmp.txt文件
file = io.open("tmp.txt", "r")
-- 直接读取一行,不用io.input(file)指定输入文件了
line = file:read()
-- 关闭文件
file:close()
上面的read方法都是默认读取一行数据,可以指定read()函数的参数,代表不同的读取方法
*n
读取一个数字并返回,必须是数字*a
读取整个文件内容*|
读取一行(默认方式)测试时候好像一直报错,未知原因number
举例:file.read(4) 指定读取4个字符Lua的面向对象的使用应该才是最终极的形态了。
面向对象逃不开三个特征,封装、继承、多态。
对象
-- Lua 里边都是表数据,没有对象的概念,所以所谓类,对象都是模拟出来的一种形态
-- 定义一个Person类
Person = {
name = nil,
age = nil
}
-- 定义方法
Person.eat = function(self)
print(self.name.."在吃东西")
end
-- 调用
Person.name = "周杰伦"
Person.age = 42
Person.eat(Person)
-- 如果还有一个Person对象,则需要再次重复定义上面的Person,所以可以模拟一个类的构造方法出来
function Person:new(o)
local t = o or {}
setmetatable(t, {__index = self})
return t
end
-- eat 方法改进,后面编程都是以这种形式出现的居多
function Person:eat()
print(self.name.."在吃东西")
end
p1 = Person:new()
p1.name = "黄圣依"
print(p1:eat())
饥荒里边的源码都是Lua文件,采用ECS(Entity, Components, System)框架,即实体、组建、系统。
这种框架和面向对象不同,他更多的是面向数据;
实体理解就是对象,一个一个的实物,比如游戏里边的花、猪人,兔子等,而组件则是行为,比如花存在可采集的属性,所以Pickable.lua文件存在组件中代码。
而系统则是控制整个游戏的机制玩法。系统不关心组件存在什么行为,组件不关系实体具体是什么。
饥荒中类的定义
class.lua文件–基本上看完感觉上面的Lua都白学了,吐了。
-- class.lua
-- Compatible with Lua 5.1 (not 5.0).
local TrackClassInstances = false
ClassRegistry = {}
if TrackClassInstances == true then
global("ClassTrackingTable")
global("ClassTrackingInterval")
ClassTrackingInterval = 100
end
local function __index(t, k)
local p = rawget(t, "_")[k]
if p ~= nil then
return p[1]
end
return getmetatable(t)[k]
end
local function __newindex(t, k, v)
local p = rawget(t, "_")[k]
if p == nil then
rawset(t, k, v)
else
local old = p[1]
p[1] = v
p[2](t, v, old)
end
end
local function __dummy()
end
local function onreadonly(t, v, old)
assert(v == old, "Cannot change read only property")
end
function makereadonly(t, k)
local _ = rawget(t, "_")
assert(_ ~= nil, "Class does not support read only properties")
local p = _[k]
if p == nil then
_[k] = { t[k], onreadonly }
rawset(t, k, nil)
else
p[2] = onreadonly
end
end
function addsetter(t, k, fn)
local _ = rawget(t, "_")
assert(_ ~= nil, "Class does not support property setters")
local p = _[k]
if p == nil then
_[k] = { t[k], fn }
rawset(t, k, nil)
else
p[2] = fn
end
end
function removesetter(t, k)
local _ = rawget(t, "_")
if _ ~= nil and _[k] ~= nil then
rawset(t, k, _[k][1])
_[k] = nil
end
end
function Class(base, _ctor, props)
local c = {} -- a new class instance
local c_inherited = {}
if not _ctor and type(base) == 'function' then
_ctor = base
base = nil
elseif type(base) == 'table' then
-- our new class is a shallow copy of the base class!
-- while at it also store our inherited members so we can get rid of them
-- while monkey patching for the hot reload
-- if our class redefined a function peronally the function pointed to by our member is not the in in our inherited
-- table
for i,v in pairs(base) do
c[i] = v
c_inherited[i] = v
end
c._base = base
end
-- the class will be the metatable for all its objects,
-- and they will look up their methods in it.
if props ~= nil then
c.__index = __index
c.__newindex = __newindex
else
c.__index = c
end
-- expose a constructor which can be called by ()
local mt = {}
if TrackClassInstances == true and CWD~=nil then
if ClassTrackingTable == nil then
ClassTrackingTable = {}
end
ClassTrackingTable[mt] = {}
local dataroot = "@"..CWD.."\\"
local tablemt = {}
setmetatable(ClassTrackingTable[mt], tablemt)
tablemt.__mode = "k" -- now the instancetracker has weak keys
local source = "**unknown**"
if _ctor then
-- what is the file this ctor was created in?
local info = debug.getinfo(_ctor, "S")
-- strip the drive letter
-- convert / to \\
source = info.source
source = string.gsub(source, "/", "\\")
source = string.gsub(source, dataroot, "")
local path = source
local file = io.open(path, "r")
if file ~= nil then
local count = 1
for i in file:lines() do
if count == info.linedefined then
source = i
-- okay, this line is a class definition
-- so it's [local] name = Class etc
-- take everything before the =
local equalsPos = string.find(source,"=")
if equalsPos then
source = string.sub(source,1,equalsPos-1)
end
-- remove trailing and leading whitespace
source = source:gsub("^%s*(.-)%s*$", "%1")
-- do we start with local? if so, strip it
if string.find(source,"local ") ~= nil then
source = string.sub(source,7)
end
-- trim again, because there may be multiple spaces
source = source:gsub("^%s*(.-)%s*$", "%1")
break
end
count = count + 1
end
file:close()
end
end
mt.__call = function(class_tbl, ...)
local obj = {}
if props ~= nil then
obj._ = { _ = { nil, __dummy } }
for k, v in pairs(props) do
obj._[k] = { nil, v }
end
end
setmetatable(obj, c)
ClassTrackingTable[mt][obj] = source
if c._ctor then
c._ctor(obj, ...)
end
return obj
end
else
mt.__call = function(class_tbl, ...)
local obj = {}
if props ~= nil then
obj._ = { _ = { nil, __dummy } }
for k, v in pairs(props) do
obj._[k] = { nil, v }
end
end
setmetatable(obj, c)
if c._ctor then
c._ctor(obj, ...)
end
return obj
end
end
c._ctor = _ctor
c.is_a = function(self, klass)
local m = getmetatable(self)
while m do
if m == klass then return true end
m = m._base
end
return false
end
setmetatable(c, mt)
ClassRegistry[c] = c_inherited
-- local count = 0
-- for i,v in pairs(ClassRegistry) do
-- count = count + 1
-- end
-- if string.split then
-- print("ClassRegistry size : "..tostring(count))
-- end
return c
end
function ReloadedClass(mt)
ClassRegistry[mt] = nil
end
local lastClassTrackingDumpTick = 0
function HandleClassInstanceTracking()
if TrackClassInstances and CWD~=nil then
lastClassTrackingDumpTick = lastClassTrackingDumpTick + 1
if lastClassTrackingDumpTick >= ClassTrackingInterval then
collectgarbage()
print("------------------------------------------------------------------------------------------------------------")
lastClassTrackingDumpTick = 0
if ClassTrackingTable then
local sorted = {}
local index = 1
for i,v in pairs(ClassTrackingTable) do
local count = 0
local first = nil
for j,k in pairs(v) do
if count == 1 then
first = k
end
count = count + 1
end
if count>1 then
sorted[#sorted+1] = {first, count-1}
end
index = index + 1
end
-- get the top 10
table.sort(sorted, function(a,b) return a[2] > b[2] end )
for i=1,10 do
local entry = sorted[i]
if entry then
print(tostring(i).." : "..tostring(sorted[i][1]).." - "..tostring(sorted[i][2]))
end
end
print("------------------------------------------------------------------------------------------------------------")
end
end
end
end