剑网三插件入门教程(4):事件

本篇教程将通过编写一个简单的自动采集插件来介绍剑三的“事件(Event) ”这个概念。
自动采集插件的功能就是自动采集身边的矿和草药,当人物停下来的时候,如果身边有可以采集到的矿和草,插件就是自动开始采集。

为了实现自动采集,我们的插件需要做这些工作:
1 获得身边(视野内)的采集物信息。
2 判断这些采集物是否可以采集(是否是矿/ 草?距离是否够近?)。
3 如果满足条件,则开始采集。

由于要实现自动,所以以上步骤必须不断的进行。

在开始之前,我们先来建立插件的文件结构。先在插件目录Interface 下面建立自动采集插件的目录AutoGather ,然后在AutoGather 下面建立3 个文本文件:info.ini AutoGather.lua AutoGather.ini
看到AutoGather.ini 了吧,这就是剑3 的窗体文件了,我们这次就要用到它啦。别被窗体这两个字吓到了,在本例中,窗体其实是来打酱油的,你基本可以无视它。
Info.ini 文件的结构我在上一篇教程中已经介绍过了,这里就不再赘述了,直接放上该文件内容:
  1. [AutoGather]
  2. name=自动采集
  3. desc=自动采集 by myself
  4. default=1
  5. version=0.5
  6. lua_0=Interface\AutoGather\AutoGather.lua
复制代码
AutoGather.lua AutoGather.ini 暂时保持空白。

在文章的开头我说过,要实现自动,就要不断的循环一些动作。学过编程的同学应该知道如何实现不断的重复某些动作的方法吧?对了,就是死循环。在嵌入式编程中,一般都会看到主函数的最后有一个while(1){} ,各种需要重复执行的代码就放在里面,Windows 编程的消息循环应该也是死循环吧(我没学过win 编程,不太懂)。

但是,如果你在剑三中执行一个死循环会发上什么事情呢?你可以自己试试看,在
cube 中执行一个死循环 while true do end ,点执行之后看到效果了没?是的,游戏死掉了。
为何会死掉呢?我介绍一下剑3 Lua 执行机制你就能明白了。剑3 Lua 脚本的执行并不是并行的,也就是说,脚本的执行并不是多任务的。剑3 Lua 引擎是基于“帧”的方式执行脚本的,简单的说,要执行的代码是放在“帧”里面的,这一帧的代码执行完毕后才会执行下一帧,剑三的客户端一般每秒钟会执行10 帧左右(这取决与你的插件数量和CPU 速度)。

这回明白了吧?你如果把死循环代码放到一个帧里执行,那么这一帧就永远不会执行完毕,所以游戏就卡死在这一帧了。所以,大家就要注意了,剑三的Lua 脚本编写有一个原则:代码的执行流程必须是有限的并且是可以预测的,而且流程要尽量的少。这样,你的插件才不会拖慢游戏的速度。

插件装多了游戏会变慢也是这个原因。这里我吐槽一下金山的服务器,实际上,剑三的服务端程序也是基于这种帧的执行机制的(其实从剑1 开始就是这样),但是服务端和客户端不一样,它的帧速是严格的16 / 秒,客户端有个函数能读取到服务端的逻辑帧( GetLogicFrameCount() ) 。正常情况下,服务器的脚本执行是没有问题的,但是到了阵营攻防的时候……尤其是双方几百人打到一起的时候,服务器就需要运算大量的数据,这时候,1/16 秒执行完一帧就有点费劲了……为了保持16 / 秒,这时候服务器就会开始丢东西,于是我们就会发现[ 郭炜炜] 释放了技能[ 郭炜炜之怒] ,我们被全地图锁足……以上只是我猜的,猜错了也别喷我哦。

明白了这个帧的机制,我想很多同学就懂了:只要使我们的脚本在每一帧都执行一遍,不就可以实现无限循环了吗。于是,下面我就开始介绍它的实现方法啦。

先介绍一下OnFrameBreathe() ,其实是一个窗体事件函数,如果你在一个脚本中打开了一个窗体,并且这个窗体是可呼吸的,那么剑3 的引擎每一帧都会调用这个脚本的OnFrameBreathe() 函数。简单点说,如果在AutoGather 的脚本中定义了AutoGather.OnFrameBreathe() 函数,并且用Wnd.OpenWindow 打开了AutoGather 窗体,那么每一帧AutoGather.OnFrameBreathe() 函数都会执行一次。请注意:OnFrameBreathe()的调用频率并不是固定的,它取决于你的cpu速度以及其他因素,一般来说是每秒10次左右,但绝不是想当然的16次/秒。

为了使用OnFrameBreathe() ,我们必须构建一个窗体。别被窗体这个词吓到,其实你不需要有任何剑3 的窗体控制的知识。只需要编辑AutoGather.ini 加入如下内容就可以了:
  1. [AutoGather]
  2. ._WndType=WndFrame
  3. ._Parent=Lowest
  4. Left=0
  5. Top=0
  6. Width=0
  7. Height=0
  8. DragAreaLeft=0
  9. DragAreaTop=0
  10. DragAreaRight=0
  11. DragAreaBottom=0
  12. AnimateStartPosX=0
  13. AnimateStartPosY=0
  14. AnimateEndPosX=0
  15. AnimateEndPosY=0
  16. AnimateTimeSpace=0
  17. AnimateMoveSpeed=0
  18. ScriptFile=Interface\AutoGather\AutoGather.lua
  19. IsCustomDragable=0
  20. DragAreaWidth=0
  21. DragAreaHeight=0
  22. DummyWnd=1
  23. DisableBringToTop=1
  24. DisableBreath=0
  25. BreatheWhenHide=1
复制代码
第一行AutoGather 是窗口名
下面的._WndType=WndFrame 表示这是一个窗体
._Parent=Lowest 这一行表明这个窗体是在最底层的
下面那一堆xxx=0 表示窗口大小为0 。也就是说,这是一个在最底层的、不可见的隐形窗口(因为我们只需要用它呼吸不需要让他露脸)。
ScriptFile=Interface\AutoGather\AutoGather.lua 这个要指向插件lua 脚本的路径(实际上 这行不写也没事)
IsCustomDragable=0 ; 禁止自定义界面拖动(shift+u 那个)
DragAreaWidth=0 ; 可拖动宽度范围0
DragAreaHeight=0 ; 可拖动高度范围0
DummyWnd=1
DisableBringToTop=1 ; 禁止移动到上层
DisableBreath=0 ; 允许呼吸
BreatheWhenHide=1 ; 在窗体隐藏后继续呼吸

把那堆东西写进AutoGather.ini 保存之后,这个窗体就创建好了,之后我们要在AutoGather.lua 里面打开它。
打开AutoGather.lua 写入这一行(注意:这一条语句最好放在lua 文件的末尾,也就是你定义的函数的后面)
  1. Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
复制代码
Wnd.OpenWindow 里面的两个参数应该一看就明白了吧,第一个参数是窗体文件路径,第二个参数是窗体名,也就是AutoGather.ini 的第一行那个名字。

之后就可以定义OnFrameBreathe 函数,我们在Wnd.OpenWindow 的前面定义OnFrameBreathe 函数:
  1. function AutoGather.OnFrameBreathe()
  2. end
复制代码
为了测试是否能正常呼吸,我们在这个函数里加入测试语句:
  1. if GetLogicFrameCount()%16==0 then
  2. OutputMessage("MSG_SYS","我在呼吸哦\n")
  3. end
复制代码
于是现在AutoGather.lua 的内容是这样的:
  1. AutoGather={}
  2. function AutoGather.OnFrameBreathe()
  3. if GetLogicFrameCount()%16==0 then
  4. OutputMessage("MSG_SYS","我在呼吸哦\n")
  5. end
  6. end
  7. Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
复制代码
进入游戏后如果看到聊天栏每秒刷一行字:“我在呼吸哦”,就表明你成功了。

之后就删掉那3 行测试语句,然后继续下一步吧。

小知识:我们在游戏中看到的东西,除了固定的地图之外,只有三类,分别是Player Npc Doodad 。顾名思义,Player 是玩家,Npc Npc 。但是Doodad 呢?doodad 这个词的字面意思是小摆设。基本上游戏中那些不会动的小物件都是doodad ,包括了各种采集物,某些桌椅板凳,甚至主城里房子上那块牌子。我们今天要采集的草和矿,就都是doodad


为了能采集草
/ 矿也就是doodad ,我们首先需要有一个视野内doodad 的列表,但是很不幸,早期的剑三并没有提供一个能直接获得doodad 列表的函数,所以那个时候收集doodad 列表就要用到事件。当然,现在我们有了更便捷的方法,但是为了介绍事件,我先来讲解一下这个以前的笨方法。

事件这个概念肯定大家都懂,剑三在发生某些事的时候会产生一个事件,如果RegisterEvent 注册过这个事件,程序就会去调用你定义过的事件处理函数,如果这个事件带有参数的话,游戏会用arg0~arg9 这几个全局变量传递参数。

这里我们要用到2 个事件:DOODAD_ENTER_SCENE DOODAD_LEAVE_SCENE ,顾名思义,他们分别是doodad 进入视野和doodad 离开视野,他们使用arg0 传递doodad ID 。利用这2 个事件,我们定义一个列表,在doodad 进入视野以后把它加进去,在doodad 离开视野以后再删掉,就能取得视野内的doodad 列表了。
为了实现它,先定义一个表来存放doodad 列表,我们在AutoGather.lua 开头加入:
  1. AutoGather.DooList={}
复制代码
之后用RegisterEvent 注册两个事件:
  1. RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
  2. RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
复制代码
RegisterEvent 的第一个参数是要注册的事件,第二个参数是事件发生时要调用的函数,这里放的是用function() 直接定义的简单函数,如果你的函数是在脚本中定义好的,那么第二个参数直接放函数名就可以了,记住后面不要加上() 例子:RegisterEvent("DOODAD_ENTER_SCENE",AutoGather.TestFunc)

于是现在
AutoGather.lua 的内容是这样的:
  1. AutoGather={}
  2. AutoGather.DooList={}
  3. function AutoGather.OnFrameBreathe()
  4.   if GetLogicFrameCount()%32==0 then
  5.     OutputMessage("MSG_SYS","我在呼吸哦\n")
  6.   end
  7. end
  8. RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
  9. RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
  10. Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
复制代码

列表有了,就可以开始往
OnFrameBreathe() 里面写采集代码啦,不过在这之前,先先给它加个开关:
  1. AutoGather.bOn = false
  2. function AutoGather.OnFrameBreathe()
  3.     if not AutoGather.bOn then
  4.         return
  5.     end   
  6. end
  7. Hotkey.AddBinding("AutoGather", "切换开启状态", "自动采集",
  8.     function()
  9.         if AutoGather.bOn then
  10.             AutoGather.bOn = fales
  11.             OutputMessage("MSG_SYS","自动采集关闭\n")
  12.         else
  13.             AutoGather.bOn = true
  14.             OutputMessage("MSG_SYS","自动采集开启\n")
  15.         end
  16.     end,
  17. nil)
复制代码
这段代码我就不解释了,看不懂就先去学好lua 吧……



之后我们继续在OnFrameBreathe 里面加料,在采集之前,显然要保证人物在站立状态并且不在读条,所以我们要加入判断,如果人物不是站立状态或者人物在读条就返回:
  1. local player = GetClientPlayer()
  2. if not player then
  3.     return
  4. end
  5. if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
  6.     return
  7. end
复制代码
下面就是采集代码了,这个代码我也不解释,这些相关函数的原型和用法都能在\ui\script\doodad.lua 中找到。
  1. for _,dwID in pairs(AutoGather.DooList) do
  2.     local doodad = GetDoodad(dwID)
  3.     if doodad and doodad.CanDialog(player) then   
  4.         if doodad and doodad.nKind == DOODAD_KIND.CRAFT_TARGET then
  5.             InteractDoodad(dwID)
  6.         end
  7.     end
  8. end
复制代码
于是最终完成的AutoGather.lua 是这样的:
  1. AutoGather={}
  2. AutoGather.bOn = false
  3. AutoGather.DooList={}

  4. function AutoGather.OnFrameBreathe()

  5.     if not AutoGather.bOn then
  6.         return
  7.     end   
  8.     local player = GetClientPlayer()
  9.     if not player then
  10.         return
  11.     end
  12.     if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
  13.         return
  14.     end
  15.    
  16.     for _,dwID in pairs(AutoGather.DooList) do
  17.         local doodad = GetDoodad(dwID)
  18.         if doodad and doodad.CanDialog(player) then   
  19.             if doodad and doodad.nKind == DOODAD_KIND.CRAFT_TARGET then
  20.                 InteractDoodad(dwID)
  21.             end
  22.         end
  23.     end
  24.    
  25. end

  26. RegisterEvent("DOODAD_ENTER_SCENE", function() table.insert(AutoGather.DooList,arg0) end)
  27. RegisterEvent("DOODAD_LEAVE_SCENE",function() table.remove(AutoGather.DooList,arg0) end)

  28. Wnd.OpenWindow("Interface/AutoGather/AutoGather.ini","AutoGather")

  29. Hotkey.AddBinding("AutoGather", "切换开启状态", "自动采集",
  30.     function()
  31.         if AutoGather.bOn then
  32.             AutoGather.bOn = fales
  33.             OutputMessage("MSG_SYS","自动采集关闭\n")
  34.         else
  35.             AutoGather.bOn = true
  36.             OutputMessage("MSG_SYS","自动采集开启\n")
  37.         end
  38.     end,
  39. nil)


复制代码
附上最终完成的插件:
AutoGather.rar (1.16 KB, 下载次数: 211)


PS: 记得我前面说过这是个笨方法吗?其实现在剑三提供了获取视野内doodad 的函数GetNearbyDoodadList() ,只需要AutoGather.DooList = GetNearbyDoodadList() 就可以获取doodad 列表。有兴趣的同学可以自己改一下,怎么改我就不说了。

另外,其实可以很容易的在这个插件的基础之上实现自动庖丁、自动采集任务物品、只采集特定物品,这些相关的代码都能在doodad.lua 里面找到,如果你能把这些功能写出来,那么你就已经是一个合格的插件作者了。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值