全局变量与局部变量

莫小仙...大约 12 分钟Lua脚本

全局变量与局部变量

今天来讲讲全局变量与局部变量。

全局变量

全局变量,就是一直有效的变量。在脚本的任何位置即拿即用。如:

if name1 then
  print(name1)
end

如果脚本就这3行,那么这里突兀出现的name1就是一个全局变量。

局部变量

局部变量,就是只在一个范围内有效的变量。出了这个范围,就无法再使用了。

局部变量使用前,需要先进行声明。在声明时所在的范围,决定了局部变量的有效范围。关于范围的问题,稍后细说。声明局部变量时,需要在变量名前面加上一个local关键字。如:

local name2
if true then
  print(name2)
end

这里第1行先声明了一个局部变量。2~4行则是使用了这个变量。

赋值

如果不对变量进行赋值,那么变量的值为nil。赋值的格式如下:

变量 = 值
对照上面两个例子,全局变量赋值如下:

name1 = 1
局部变量赋值,可以在声明的时候,也可以后来再赋值,如:

local name2 = 1

或者:

local name2
name2 = 1

至于局部变量具体使用哪一种赋值,通常受有效范围的影响。

变量可以重复赋值,后赋的值会覆盖掉之前的值。如:

local a = 10
print(a) -- 打印10
a = 20
print(a) -- 打印20

作用域

前面所说的有效范围,就是指的作用域。作用域可以分为三种,分别是最外层作用域、函数级作用域、结构体级作用域。

最外层作用域是最外层的范围。如:

local a = 10
print(a) -- 打印10

局部变量a在声明后有效,即第2行有效。

函数级作用域就是函数内的范围。如:

local function f ()
  local a = 10
  print(a)
end
f() -- 打印10
print(a) -- 打印nil

其中局部变量a仅在函数f内声明之后的位置有效,即第3行有效。而局部变量f(这个f是函数名,这里简单认为也是一个变量)是在最外层有效。

结构体级作用域就是在结构体内的范围。结构体包括分支语句(如if等)、循环语句(如forwhile等)。如:

if true then
  local a = 10
  print(a) -- 打印10
end
print(a) -- 打印nil

局部变量a仅在声明后的thenend之间有效,即第3行有效。

作用域单独的情况说完了,再来说说嵌套的情况。

最外层作用域

仅嵌套结构体有效。

local a = 10
if true then
  print(a) -- 打印10
end
function f ()
  print(a)
end
f() -- 打印nil

第1行在最外层定义了局部变量a。第3行,a在结构体中有效,因此输出了10。第6行,a在函数内无效,因此第8行调用函数后输出了nil

函数级作用域

嵌套函数与结构体有效。

function f ()
  local a = 10
  function f2()
    print(a)
  end
  f2()
  if true then
    print(a)
  end
end
f()  -- 打印10、10

第2行在函数f中定义了局部变量a。第4行在函数f2中打印了a。第6行调用了函数f2。第8行在结构体中打印了a。第11行调用了函数f。调用函数f后,从第2行还是执行,因为嵌套函数有效,所以在第6行打印了10。因为嵌套结构体有效,在第8行打印了10

结构体级作用域

最外层的结构体仅嵌套结构体有效。 函数内的结构体嵌套函数与结构体有效。 仅嵌套结构体有效情况:

if true then
  local a = 10
  function f ()
    print(a)
  end
  f() -- 打印nil
  if true then
    print(a) -- 打印10
  end
end

第2行在结构体中声明了局部变量a。第4行在函数中打印了a。第6行调用了函数f。因为嵌套函数无效,所以打印了nil。第8行打印了a,因为嵌套结构体有效,所以打印了10

嵌套函数与结构体有效情况:

function f2 ()
  local a = 10
  if true then
    function f3()
      print(a)
    end
    f3()
    if true then
      print(a)
    end
  end
end
f2() -- 打印10、10

第2行在函数中声明了局部变量a。第5行在函数f3中打印了a。第7行调用了函数f3。第9行在结构体中打印了a。第13行调用了函数f2。函数执行到第7行时,因为嵌套函数有效,所以先打印了10。执行到第9行时,因为嵌套结构体有效,所以打印了10

重名

也许有小伙伴会有疑问,局部变量还要记作用域之内的东西,记得头都大了,而全局变量几乎没有任何限制,我就只用全局变量岂不是简单多了。

如果脚本少,每次用不同变量,那没大问题。但是一旦全局变量出现重名的情况,那就是一场灾难,可能会出现莫名其妙的错误。

一种重名的情况是,之前用一个变量保存了一个数据,后来忘记这个变量使用过了,又用它保存另外一个数据。这样,这个数据就乱掉了。比如用level保存某玩家的金币数,然后又用level保存另一个玩家的金币数。当然,这种情况出现的可能性比较小。

更可能的情况,是多个脚本使用相同的全局变量。是的,全局变量是可以跨脚本的。一个脚本里的全局变量的值,在另一个脚本里可以获取到。这样,滥用全局变量就很有可能把另外一个脚本里的全局变量值给覆盖了。这样出现的情况就是,单独一个脚本执行,都是没问题。但是两个脚本一起执行,就会出问题。

如果在一个作用域中出现了变量重名(可能是全局变量与局部变量重名,也可能是局部变量与局部变量重名),那么该变量的值以此时最后出现的局部变量为准。如:

a = 10
if true then
  local a = 20
  print(a) -- 打印20
end
function f ()
  print(a)
  local a = 30
  print(a)
  if true then
    local a = 40
    print(a)
  end
  print(a)
end
f() -- 打印10、30、40、30
print(a) -- 打印10

第1行全局变量赋值。第3行声明局部变量并赋值。第4行的a的值以此时最后出现的局部变量为准,所以打印了20。6~15行定义了一个函数。第16行调用函数。在第7行函数中还没有声明a,因此是取全局变量,打印了10。第9行以局部变量为准,打印了30。第12行以最后的局部变量为准,打印了40。第14行时,已经不在值为40的变量的作用域中了,因此又打印了30。第17行时,因为当前的作用域中没有其他局部变量,就打印了全局变量的值10

回顾

也许有小伙伴对之前讲的全局变量与局部变量还是有些不太懂。没关系,这里再简单回顾一下。

全局变量,不用声明,在所有脚本的任何位置都有效。如:

a = 10
if true then
  print(a) -- 打印10
end
function f ()
  print(a)
end
f() -- 打印10

第1行给全局变量赋值。第3行打印出了值。第8行调用函数,在函数体中(第6行)也取到了全局变量的值。

局部变量,需要用local声明,仅在声明的作用域内有效。如:

local a = 10
if true then
  print(a) -- 打印10
end
function f ()
  print(a)
end
f() -- 打印nil

与上面的例子相比,加上了local。但是在作用域外的函数体中(第6行)无效,于是调用函数打印了nil

下面再不恰当地类比一下。

假如我们是资本家,我们现在要剥削一些打工人,让他们无偿为我们做事。此时,我们有两种选择。

一种是选择合同工(全局变量)。他们已经签了终身合同,可以随时随地(任何作用域)进行剥削(赋值与其他操作)。

一种是选择临时工(局部变量)。我们先要决定在哪个项目(作用域)中用,接着召一些临时工进来(local声明),然后才能进行剥削(赋值与其他操作)。在一个项目(作用域)结束后,合同到期临时工就离职了(变量无效)。

打工人的名字可能重名,当我们指派一个有重名的人去做事时,只有最后召进来的打工人会响应。一是因为要挣表现,二是因为之前的打工人都成长为老油条学会摸鱼了。

实操

如果还是没有记住,也没有关系。多用用,就能慢慢在实践中掌握。下面进入实操环节。

现在我们来做这么一个效果:一共两个队伍,房主一个队伍,其他玩家一个队伍。房主被击败3次后,其他玩家获得胜利;其他玩家累计被击败10次后,房主获胜。

简单分析一下,我们大概要做的就是:当玩家被击败时,获取玩家的队伍。根据玩家的队伍,判断是否达到失败条件。如果达到,则设置两队的胜负;如果没达到,则对应被击败次数加1。

根据分析,我们需要以下API:

玩家被击败事件 获取玩家队伍 设置队伍胜利或失败 下面开始查API。查找之法之前已经说过了,我这里就不再具体说了。

先在事件系统中找到玩家死亡事件:

根据文档描述,把注册事件的结构写出来(这里还是用匿名函数):

ScriptSupportEvent:registerEvent([=[Player.Die]=], function (event)
  
end)

接着在玩家管理接口中找获取玩家队伍的API:

根据文档说明,把获取玩家队伍补充上:

ScriptSupportEvent:registerEvent([=[Player.Die]=], function (event)
  local result, teamid = Player:getTeam(event.eventobjid)
  
end)

这里的第2行便是声明了两个局部变量并给它们赋值。result保存调用API的结果(成功还是失败),teamid保存玩家的队伍。玩家的队伍值由0~6,0代表无队伍,1~6分别代表红、蓝、绿、黄、橙、紫。

接下来我们需要判断队伍的死亡次数。

这里我们便需要两个变量来保存队伍的死亡次数。因为我们需要死亡次数一直有效,所以我们需要两个全局变量。我们给两个全局变量赋值:

dieTimes1 = 0 -- 红队死亡次数
dieTimes2 = 0 -- 蓝队死亡次数

dieTimes1表示房主队伍的死亡次数,dieTimes2表示其他玩家队伍的死亡次数。

然后写判断条件:

ScriptSupportEvent:registerEvent([=[Player.Die]=], function (event)
  local result, teamid = Player:getTeam(event.eventobjid)
  if teamid == 1 then -- 如果玩家是红队
    if dieTimes1 >= 2 then -- 死亡次数已经不少于2次,加上这次就至少3次
      -- 设置红队失败
      -- 设置蓝队胜利
    else
 -- 如果死亡次数不足
      dieTimes1 = dieTimes1 + 1
 -- 次数加1
    end
  elseif teamid == 2 then -- 如果玩家是蓝队
    if dieTimes2 >= 9 then -- 死亡次数已经不少于9次,加上这次就至少10次
      -- 设置蓝队失败
      -- 设置红队胜利
 
    else
 -- 如果死亡次数不足
      dieTimes2 = dieTimes2 + 1 -- 次数加1
    end
  end
end)

接着在队伍管理接口中查询设置队伍胜利/失败的API:

根据文档说明,把逻辑部分代码补全:

ScriptSupportEvent:registerEvent([=[Player.Die]=], function (event)
  local result, teamid = Player:getTeam(event.eventobjid)
  if teamid == 1 then -- 如果玩家是红队
    if dieTimes1 >= 2 then -- 死亡次数已经不少于2次,加上这次就至少3次
      Team:setTeamResults(1, 2) -- 设置红队失败
      Team:setTeamResults(2, 1) -- 设置蓝队胜利
    else -- 如果死亡次数不足
      dieTimes1 = dieTimes1 + 1 -- 次数加1
    end
  elseif teamid == 2 then -- 如果玩家是蓝队
    if dieTimes2 >= 9 then -- 死亡次数已经不少于9次,加上这次就至少10次
      Team:setTeamResults(2, 2) -- 设置蓝队失败
      Team:setTeamResults(1, 1) -- 设置红队胜利
    else -- 如果死亡次数不足
      dieTimes2 = dieTimes2 + 1 -- 次数加1
    end
  end
end)

逻辑不复杂,根据注释大致应该能够看懂。

为了便于管理,初始赋值的全局变量通常放在脚本最上面。于是,完整的代码如下:

dieTimes1 = 0 -- 红队死亡次数
dieTimes2 = 0 -- 蓝队死亡次数
 
ScriptSupportEvent:registerEvent([=[Player.Die]=], function (event)
  local result, teamid = Player:getTeam(event.eventobjid)
  if teamid == 1 then -- 如果玩家是红队
    if dieTimes1 >= 2 then -- 死亡次数已经不少于2次,加上这次就至少3次
      Team:setTeamResults(1, 2) -- 设置红队失败
      Team:setTeamResults(2, 1) -- 设置蓝队胜利
    else -- 如果死亡次数不足
      dieTimes1 = dieTimes1 + 1 -- 次数加1
    end
  elseif teamid == 2 then -- 如果玩家是蓝队
    if dieTimes2 >= 9 then -- 死亡次数已经不少于9次,加上这次就至少10次
      Team:setTeamResults(2, 2) -- 设置蓝队失败
      Team:setTeamResults(1, 1) -- 设置红队胜利
    else -- 如果死亡次数不足
      dieTimes2 = dieTimes2 + 1 -- 次数加1
    end
  end
end)

当然,想要达到脚本期望的效果,还需要在游戏中对队伍进行设置:

设置红队1人,其他为蓝队。

分配方式为顺序分配或随机分配。

如此,就完成了。如果有疑问,可以在下方留言。

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.14.7