YDWE官方博客

简单Lua教程

本文旨在让有 jass 基础的用户快速上手 Lua,因此需要一定的 jass 基础.当然如果你有其他语言的基础那更好

1.准备工作

最新版本的 YDWE

建议在配置中关闭预处理器,因为它和 Lua 的一个符号有冲突,当然你也可以选择回避使用这个符号.

2.入口

使用call Cheat("exec-lua: filename")来运行地图中名称为”filename”的 lua 脚本

你可以选择在外部编写该脚本后导入地图,也可以通过 YDWE 在编辑器内编写并自动导入地图

3.在 YDWE 内写 Lua 脚本

在 YDWE 的任何自定义代码区域输入以下内容

1
2
3
4
5
<?import("filename")[[

  script
      
]]?>

这段代码等价于你在地图内导入了一个名字为”filename”,内容为”script”的脚本

你可能迫不及待想要尝试写 Lua 了,不过在这里我要建议你在运行自己的 Lua 脚本之前,先单独运行一次以下代码(通过call Cheat("exec-lua: MU_console"))

1
2
3
4
5
<?import("MU_console.lua")[[

  require("jass.runtime").console = true
  
]]?>

这段代码的效果是打开一个控制台窗口,这样如果你的其他 Lua 代码出现了错误,控制台窗口中将会显示出错误原因和错误位置,非常便利.此外通过 Lua 的 print 函数,你也可以将各种内容显示在这里

4.hello world

没错,第一步是 hello world

1
print("hello world")

控制台窗口中将会显示”hello world”

5.Lua 的基本介绍

lua 是一个简单,高效,强大的脚本语言,其语法和 jass 有很多相似之处,因此有 jass 基础的话可以很快掌握 lua

lua 和 jass 最大的不同在于 lua 不需要定义变量类型,例:

1
2
3
4
local a = 1
if a == 1 then
  a = "等于 1"
end

在这个例子中,a 的类型分别为数字(number)与字符串(string)

lua 的注释符为 --

你也可以进行多行注释

1
2
3
--[[
 我已经被注释掉了
--]]

在 lua 中有以下几个类型:

  • nil 空值,任何变量在赋值前都是 nil,给变量赋值 nil 相当于摧毁该变量,类似于 jass 中的 null

  • boolean 布尔值,与 jass 一样包含 true 与 false

  • number 数字,lua 中的数字没有整数或实数之分,因此在 lua 中 5 / 2 == 2.5

  • string 字符串,与 jass 中的字符串基本相同

  • table 表,lua 中最强大的类型,他可以简单的当做数组或哈希表使用,更是 lua 构成复杂的高级功能的基础,这将在之后重点学习

  • function 函数,与 jass 中的函数不同,lua 中的函数也被视为一个值,你可以随时将它赋值给一个变量,或在其他函数中作为参数或返回值传递

  • userdata 自定义数据,你可以将其理解为 jass 中的 handle,lua 无法直接对其进行修改

lua 有以下几个保留字:

1
2
3
4
5
and break do else elseif
end false for function if
in local nil not or
repeat return then true until
while

与 jass 很像不是吗?

6.值之间的操作

运算

1
2
3
4
5
6
7
8
1 + 2 == 3
3 * 4 == 12
2 - 6 == -4
7 / 2 == 3.5
4 ^ 2 == 16
5 % 1.5 == 0.5
1 .. 1 == "11"
"6" + "7" == 13

lua 在必要的时候会自动转换类型,因此需要特别留意字符串是通过 .. 来连接的

此外 lua 还自带了 幂(^) 与 取模(%)

逻辑

1
2
3
4
5
6
7
8
9
10
11
if x == true then --1
  a = 1
elseif x then --2
  a = 2
elseif not x then --3
  a = 3
elseif x and y then --4
  a = 4
elseif x or y then --5
  a = 5
end

lua 的 if 结构与 jass 相似,只要注意 endif 要改成 end

第 2 个条件中,只有当 x 的值为 nil 或 false 才不成立,其他情况包括 0 或”“都是成立的

7.变量

lua 对变量进行操作时不需要 set 关键字

lua 使用全局变量无须事先声明,任何时候a = 10都是有效的.在 a 被赋值前,如果去获取 a 的值将返回”nil”

局部变量的声明方式类似于 jass:

1
local a = 10

局部变量可以在任何位置声明,影响范围由声明的位置而定,例如:

1
2
3
4
5
6
local a = 10
if a then
  local a = 20
  print(a)
end
print(a)

将依次显示 20 与 10

记住,lua 不需要写变量类型哦

你可以一次声明多个变量

1
2
3
4
x, y = 10, 20 --x = 10, y = 20
x, y , z = 10, 20 --x = 10, y = 20, z = nil
x, y = 10, 20, 30 --x = 10, y = 20, 30 被丢弃
x, y = y, x --x = 20, y = 10

8.字符串

lua 的字符串有 3 种符号,例:

1
2
3
4
5
text1 = "I'm hungry"
text2 = '"That loli seems delicacies!", I said'
text3 = [[
"呜喵"\n"呜喵"\n"呜喵"
]]

其中用[[]]表示的字符串将忠实的记录你输入的文字,忽略其中的任何转义符.如果你print(text3),控制台将显示

1
"呜喵"\n"呜喵"\n"呜喵"

需要注意的是,[[]]形式的字符串可能会与你的脚本产生一些冲突,例如

1
2
3
text = [[
  a = t[v[5]]
]]

你可以改写成如下的形式

1
2
3
text = [===[
  a = t[v[5]]
]===]

这样 lua 只会把拥有相同等号数量的括号认为是一组

同样的,你可以将 YDWE 内写 lua 的代码改成如下代码:

1
2
3
4
5
<?import(filename)[=====[

  script
  
]=====]?>

以回避一些冲突

9.简单循环

lua 有 3 种简单循环方式

① for 循环

最常用的循环方式

直接看例子

1
2
3
for i = 1, 5 do
  print(i)
end

将依次打印 1, 2, 3, 4, 5

你也可以指定循环的步长

1
2
3
for i = 1, 5, 2 do
  print(i)
end

将依次打印 1, 3, 5

需要注意的是,你无法通过修改 i 的值来控制循环

② while 循环

当条件成立时进行循环

1
2
3
4
5
local i = 1
while i <= 5 do
  print(i)
  i = i + 1
end

③ repeat 循环

当条件成立时退出循环

1
2
3
4
5
local i = 1
repeat
  print(i)
  i = i + 1
until i > 5

以上三种循环你都可以使用 break 来提前退出,常见的用法有

1
2
3
4
5
6
7
8
local i = 1
while true do
  print(i)
  i = i + 1
  if i > 5 then
      break
  end
end

需要注意的是,break 必须放在一段代码的最后面

1
2
3
4
5
6
7
8
9
while true do
  break --错误,后面还有代码
  local i
end

while true do
  do break end --正确.do XXX end 是一段完整的代码,break 确实放在了这段代码的最后面
  local i
end

10.简单的数据结构

lua 只有一种数据结构:table(表)

1
2
3
4
t = {} --创建一张表并赋值给变量 t

t[1] = 10
t[2] = 20

任何非 nil 的值都可以作为 table 的索引

1
2
3
4
5
6
7
t = {}

t["x"] = 10
t[true] = 20
t[1.23] = 30
t[t] = t
t[GetTriggerUnit()] = "呜喵"

在 lua 中,可以将t["x"]简写成t.x

1
2
t["x"] == t.x
t["1"] ~= t[1]

你可以在创建 table 的时候直接定义其数组内容,注意 lua 中的数组索引是从 1 开始的(jass 是 0),多个元素之间需要用逗号隔开

1
2
3
4
5
6
7
8
9
t = {1, 2, 3, 4, 5} --相当于 t[1] = 1, t[2] = 2, t[3] = 3, t[4] = 4, t[5] = 5
t = {x = 10, y = 20} --相当于 t.x = 10, t.y = 20

t = {
  1,        -- t[1] = 1
  x = 10,   -- t.x = 10
  y = 20,   -- t.y = 20
  2         -- t[2] = 2
}

通过嵌套 table,你可以很简单的构造出多维数组

1
2
3
t = {}
t[1] = {}
t[1][2] = "呜喵"

随着你对 lua 的深入,你可能会越来越多的使用这样的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hero = {
  name = "虐农先锋",
  hp = {500, 1000},
  mp = {200, 300},
  ["坐标"] = {50, 100},
  ["技能"] = {
      ["风暴之锤"] ={
          id = |A000|,
          mp = 75,
          cd = {10, 8, 6}
      },
      ["雷霆一击"] ={
          id = |A001|,
          mp = {90, 100, 120},
          cd = {15, 14, 13}
      },
  },
  ["移动速度"] = 270
}

11.函数的声明

lua 的函数声明有 2 种形式:

1
2
function func(a, b, c)
end
1
2
func = function(a, b, c)
end

这 2 种声明方式是完全等价的,都是声明了一个名字为”func”,参数为”a, b, c”的函数

显然这里的参数不需要填写类型,函数也不需要声明返回值的类型,顺便将 endfunction 改为 end 即可

从第二种声明方式中我们可以看出,lua 中的函数是可以赋值给变量的:我声明了一个新的函数,然后将其赋值给变量”func”

一个简单的函数重载:

1
2
R2I = math.floor --math.floor 是 lua 自带的取整函数
R2I(1.5) --会去调用 lua 的 math.floor 而不是 jass 的 R2I

既然是变量,那么他也就可以局部化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function func1()
  local func1 = function()
      return 1
  end

  local func2 = function()
      return 2
  end

  return 0
end

print(func1()) -- 0
print(func1()) -- 0
print(func2()) -- func2 不存在

lua 不限定函数的返回值,你可以返回或不返回;返回一个或返回多个;返回布尔或返回整数:

1
2
3
4
5
6
7
8
9
10
11
function func(a)
  if a = 1 then
      return 1
  elseif a == 2 then
      return 1, "wumiao"
  end
end

print(func(1)) -- 1
print(func(2)) -- 1  wumiao
print(func(3)) -- nil

需要注意的是,与 break 一样,return 必须放在一段代码的最后面

12.函数的调用

lua 调用函数显然是不需要写 call 这个关键字的,而且函数作为一个变量,只要他被赋值了你就可以去调用他,因此也没有声明顺序的问题

不过你依然需要关注一下局部函数的有效范围:

1
2
3
4
5
6
local func = function(a)
  a = a or 1
  if a < 10 then
      func(a + 1) -- 错误,函数"func"没有被声明
  end
end

正确的写法应该是

1
2
3
4
5
6
7
local func
func = function(a)
  a = a or 1
  if a < 10 then
      func(a + 1)
  end
end

lua 不关心你调用函数时的参数是否与函数声明时的参数一致,多余的参数被抛弃,不足的参数为 nil,类似于多变量赋值

1
2
3
4
5
6
7
8
9
func = function(a, b, c)
  print(a)
  print(b)
  print(c)
end

func(1) -- 1, nil, nil

func(1, 2, 3, 4) -- 1, 2, 3

同样的,一个拥有多返回值的函数也有类似的情况:

1
2
3
4
5
6
7
func = function()
  return 1, 2, 3
end

a, b, c = func() -- a = 1, b = 2, c = 3
a, b = func() -- a = 1, b = 2, 3 被抛弃
a, b, c, d = func() -- a = 1, b = 2, c = 3, d = nil

13.表的简单操作

jass 中是直接定义某个变量为数组的,而 lua 中则是将一个表赋值给一个变量,显然表也可以作为一个普通的对象进行传递

1
t = {[0] = 0, 2, 4, x = 10, y = 20, z = 30, 6, 8, 10}

这张表分为了 2 个部分,数组与哈希表.lua 对数组的定义是从索引 1 开始的连续元素,因此 t[0]是哈希表部分,而将 t[3]截断后,之后元素不再连续,此时的数组大小便为 2.我们可以通过符号#来获取数组部分的长度

1
2
3
4
print(#t) -- 5

t[3] = nil
print(#t) -- 2

有 2 种简单的方式来遍历表的数组部分

1
2
3
4
5
6
7
for i = 1, #t do
  print(i .. ":" .. t[i]) --显示 1:2, 2:4, 3:6, 4:8, 5:10
end

for i, v in ipairs(t) do
  print(i .. ":" .. v) --显示同上
end

第二种方式也是循环的一种,既然循环,他也可以用过 break 来提前退出

lua 也提供了 1 种简单的方式来遍历表的所有元素

1
2
3
for i, v in pairs(t) do
  print(i .. ":" .. v) --可能显示 1:2, 2:4, 3:6, 4:8, 5:10, x:10, y:20, z:30
end

在例子中我之所说是可能显示,是因为哈希表部分的遍历顺序是随机的,与你存储的顺序无关.不过他依然会按照顺序先遍历出数组的部分

lua 提供了 2 个常用的插入/移除函数:table.insert 与 table.remove

1
2
3
4
5
6
7
8
9
t = {1, 2, 3}

table.insert(t, 2, 4) --将 4 插入到 t[2]的位置,之前从 t[2]开始的元素都被挤得往后挪一个位置,变为{1, 4, 2, 3}

table.insert(t, 5) --在 t 的末端添加元素,变为{1, 4, 2, 3, 5}

table.remove(t, 2) --移除 t[3],之前从 t[4]开始的元素都向前移动一个位置填补空缺,变为{1, 4, 3, 5}

table.remove(t) --移除 t 末端的元素,变为{1, 4, 3}

Comments