函数(二)
函数(二)
通过上一节的讲解,可能小伙伴们对函数有了一定的认识,但可能还是不太明白。这很正常,因为我主要讲的不是脚本的基础知识点,而是讲怎么把想要的效果写出来。我觉得张雪峰老师关于应试技巧的有句话说得很好,就是在一道题我不会做的情况下把题做正确。我这里想讲的就是在只知道脚本大概的意思的情况下,把想要的脚本写出来。
不过有些小伙伴可能有强迫症,就是一定要弄清楚为什么要这么做之后才能继续看下去,不弄明白就看不下去。这里我放上函数的知识点链接,觉得有必要的可以去简单学习一下:
下面继续讲函数的相关内容。
参数
参数,可以简单理解为需要的条件。参数也是变量。一个函数,可以有任意多个参数。多个参数以“,”隔开。举几个例子:
function foo1 () end
function foo2 (a)
print(a)
end
function foo3 (a, b, c)
print(a + b, c)
end
第1行定义了一个没有参数的函数,该函数函数体为空的,没有做任何事情。
第2~4行定义了一个只有一个参数的函数,该函数打印了参数。
第5~7行定义了一个有三个参数的函数,该函数打印了前两个参数的总和以及第三个参数的值。
回顾一下上一节中用到的获取玩家的名字的API:
Player:getNickname(event.eventobjid)
其中event.eventobjid
就是一个参数,代表玩家的id。
函数调用
函数名加上一对括号则完成了函数的调用,格式如下:
函数名(参数)
括号中可以传递参数值。如果函数没有对应参数,而传递了值,则该值不会被用到;如果函数定义了参数,但是却没有对应传值(调用函数时括号内提供的东西少于函数定义中参数的个数),那么在函数调用时,函数体内未传值的参数的值为nil(代表空)。以上面讲参数时定义的函数为例,进行函数的调用:
foo1(100)
foo2() -- 打印nil
foo3(1, 2) -- 打印3 nil
--
表明这之后是注释(给读代码的人一段具有提示说明的文字),对代码没有任何影响。
第1行调用了函数foo1
。参数值为100,但是函数体是空的,没有用到。
第2行进行了函数foo2
的调用,但是没有传入参数,则调用时a的值为nil,打印为nil。
第3行调用了函数foo3
,但是没有传递第3个参数的值,则打印出了3 nil。
函数返回值
返回值,可以简单理解为结果。一个函数,可以有返回值,也可以没有返回值(没有返回值时也会有一个默认的返回值nil),具体取决于函数的作用。函数调用后就会产生返回值。举两个简单的例子。
1.函数不需要结果,如打印一下参数值:
function p (a)
print(a)
end
这里定义了一个打印函数。不需要返回值,直接调用:
p(12) -- 打印12
这样每次打印信息就不用多写4个字母了。
2.函数需要结果,如计算函数:
function plus2 (a, b)
return a + b
end
这里定义了一个两个数相加的函数。其中的return
表示返回结果。需要返回值,则调用如下:
local a = plus2(5, 3) -- a = 8
local
表明变量a是一个局部变量,具体用法我在下一节会讲,这里暂时忽略。最后结果是a变量的值会变为8。
与其他大多数语言所不同的是,lua可以有多个返回值,每个返回值以“,”隔开:
function multiResult ()
return 10, 20, 30
end
调用函数时,可以是:
local a, b, c = multiResult() -- a = 10, b = 20, c = 30
与参数类似,变量与返回值数量可以不一致:
local a, b = multiResult() -- a = 10, b = 20
local a, b, c, d = multiResult() -- a = 10, b = 20, c = 30, d = nil
回顾一下上一节中用到的获取玩家的名字的API:
local result, name = Player:getNickname(event.eventobjid)
这里Player:getNickname
就是有两个返回值,一个是函数调用结果result
(是否成功),另一个则是玩家的名字name
。
函数类比
函数讲了这么多,可能有的小伙伴还是不太理解。这里我再来打个比方。
比如我现在要做一道青椒肉丝,有很多个步骤。我怕每次根据记忆来做,年纪大了后记差了,就写了一个青椒肉丝做法(定义函数)。这个做法中,需要用到青椒(参数1)、里脊肉(参数2)、植物油(参数3)以及其他的一些配料等等。最后可以做出青椒肉丝(返回值)。
写成伪码,如下:
1.记录做法。(定义参数)
function 青椒肉丝做法 (辣椒, 肉, 油, 淀粉, ...)
辣椒洗净切丝
肉洗净切丝
腌制肉
...
return 青椒肉丝
end
2.按照做法炒菜。(调用参数)
可能我没有青椒,就用了红椒;喜欢吃牛肉,就用来牛肉;觉得橄榄油更健康,就放了橄榄油:
青椒肉丝做法(红椒, 牛肉, 橄榄油)
3.盛菜。(使用函数返回值)
菜做好了我们要吃这个菜,需要把它保存起来:
盘子 = 青椒肉丝做法(红椒, 牛肉, 橄榄油)
与之前将大象装进冰箱的例子相比,不同的是:将大象装进冰箱可以不需要返回值。当然,如果逻辑严谨一点,装进冰箱应该返回成功或者失败,比如冰箱装满了。
简单来说,函数大概就这么用。
匿名函数:
先回顾一下,前面讲过了函数定义的基本结构:
function 函数名 (参数)
函数体
end
而调用的时候就是:
函数名(参数)
但有些时候,一个函数可能只会用一次,就不再使用了。这时候如果也先定义再调用就会有点麻烦,而且也不直观。这时就可以使用匿名函数。
匿名函数,就是没有名字的函数。这里以“点石成金”的例子为例。还是先回顾一下“点石成金”的脚本:
local function Player_ClickBlock (event)
if event.blockid == 104 then
Block:placeBlock(410, event.x, event.y, event.z, 0)
end
end
ScriptSupportEvent:registerEvent([=[Player.ClickBlock]=], Player_ClickBlock)
之前讲过了,这里第7行也是一个函数的调用。这里函数的第2个参数Player_ClickBlock
就是一个函数类型。因为所有事件注册(第7行的意思就是注册一个玩家点击事件)所需要的函数参数只会用一次,所以我们可以将Player_ClickBlock
改写为匿名函数,改写后如下:
ScriptSupportEvent:registerEvent([=[Player.ClickBlock]=], function (event)
if event.blockid == 104 then
Block:placeBlock(410, event.x, event.y, event.z, 0)
end
end)
对比函数定义的基本结构,匿名函数只是去掉了名字,其他地方不变。与之前函数的定义相比较,使用匿名函数后,代码更紧凑,读起代码来也就更容易。
至于何时使用匿名函数,主要就看函数是不是只有一个地方会用到。那如果函数在多个地方被用到时,能不能使用匿名函数呢?当然可以,但那不是麻烦嘛。那样需要复制粘贴多次,而且函数修改起来也容易出错(可能有地方忘记修改了)。如果不理解,那没关系,有空多写写就明白了。
好了,函数的知识就讲这些。其他的如可变参数,可以去上面提到的链接中学习。下面来实操一下。
现在我们来做这样一个效果:如果有玩家哭泣,那么天就会下雨。
简单分析一下,我们需要以下API:
玩家动作表情改变事件 玩家哭泣动作的ID 改变天气为雨天的规则 这里顺带提一下,要分析出需要哪些API,是建立在你对游戏提供的API足够了解的情况下。如果你都没看过API,不知道有哪些API,当然也就不可能指定需要哪些API。换言之,对没有看过API的小伙伴来说,是不可能分析得出来的。所以,写脚本之前,需要先去看看API文档,了解一下能实现什么。
下面开始查API。
在API文档中,点击左侧事件系统,在右侧找到玩家事件:
点击跳转后,找到对应事件:
照着例子,我们可以把事件的结构搭建出来:
local function Player_PlayAction (event)
end
ScriptSupportEvent:registerEvent([=[Player.PlayAction]=], Player_PlayAction)
这次稍微调整下,我们改为匿名函数的方式:
ScriptSupportEvent:registerEvent([=[Player.PlayAction]=], function (event)
end)
接下来我们需要查询一下哭泣动作的ID。
左侧点击查询:
右侧点击动作:
找到哭泣:
下面便可以加上条件了:
ScriptSupportEvent:registerEvent([=[Player.PlayAction]=], function (event)
if event.act == 3 then
end
end)
条件结构就是if … then … end
,以后会具体讲解,现在想了解的可以查看地址:
https://www.runoob.com/lua/lua-decision-making.html
在前面找事件API时,我们发现事件里提供了两个参数:eventobjid、act。很显然第一个是玩家id,第二个是动作id。当然,如果小伙伴觉得不显然的话,可以都去试一下。
接下来就是下雨的效果了。
点击左侧游戏规则列表:
在右侧找到天气:
至于怎么写,参考文档中上面的例子:
大胆假设后,写成如下所示:
ScriptSupportEvent:registerEvent([=[Player.PlayAction]=], function (event)
if event.act == 3 then
GameRule.Weather = 1
end
end)
如此,便完成了。
不过,当我去游戏里试验的时候,发现没有效果。然后根据我的一番测试,发现天气规则是:0晴雨交错;1晴天;2雨天;3雷暴。但是,目前通过脚本只能改为0、1、2,修改为3无效。
于是,第3行代码应该修改为:
GameRule.Weather = 2
最后,如果学有余力的小伙伴,可以把玩家高兴后,天气转为晴天的效果加上。