剑网三插件入门教程(4):事件
来源:互联网 发布:ubuntu 不可用分区 编辑:程序博客网 时间:2024/04/27 19:03
本篇教程将通过编写一个简单的自动采集插件来介绍剑三的“事件(Event)”这个概念。
自动采集插件的功能就是自动采集身边的矿和草药,当人物停下来的时候,如果身边有可以采集到的矿和草,插件就是自动开始采集。
为了实现自动采集,我们的插件需要做这些工作:
1获得身边(视野内)的采集物信息。
2判断这些采集物是否可以采集(是否是矿/草?距离是否够近?)。
3如果满足条件,则开始采集。
由于要实现自动,所以以上步骤必须不断的进行。
在开始之前,我们先来建立插件的文件结构。先在插件目录Interface下面建立自动采集插件的目录AutoGather,然后在AutoGather下面建立3个文本文件:info.ini AutoGather.lua AutoGather.ini 。
看到AutoGather.ini了吧,这就是剑3的窗体文件了,我们这次就要用到它啦。别被窗体这两个字吓到了,在本例中,窗体其实是来打酱油的,你基本可以无视它。
Info.ini文件的结构我在上一篇教程中已经介绍过了,这里就不再赘述了,直接放上该文件内容:复制代码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加入如下内容就可以了:复制代码第一行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文件的末尾,也就是你定义的函数的后面)复制代码Wnd.OpenWindow里面的两个参数应该一看就明白了吧,第一个参数是窗体文件路径,第二个参数是窗体名,也就是AutoGather.ini的第一行那个名字。
之后就可以定义OnFrameBreathe函数,我们在Wnd.OpenWindow的前面定义OnFrameBreathe函数:复制代码为了测试是否能正常呼吸,我们在这个函数里加入测试语句:复制代码于是现在AutoGather.lua的内容是这样的:复制代码进入游戏后如果看到聊天栏每秒刷一行字:“我在呼吸哦”,就表明你成功了。
之后就删掉那3行测试语句,然后继续下一步吧。
为了能采集草/矿也就是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开头加入:复制代码之后用RegisterEvent注册两个事件:复制代码RegisterEvent的第一个参数是要注册的事件,第二个参数是事件发生时要调用的函数,这里放的是用function()直接定义的简单函数,如果你的函数是在脚本中定义好的,那么第二个参数直接放函数名就可以了,记住后面不要加上()。例子:RegisterEvent("DOODAD_ENTER_SCENE",AutoGather.TestFunc)
于是现在AutoGather.lua的内容是这样的:复制代码
列表有了,就可以开始往OnFrameBreathe()里面写采集代码啦,不过在这之前,先先给它加个开关:复制代码这段代码我就不解释了,看不懂就先去学好lua吧……
之后我们继续在OnFrameBreathe里面加料,在采集之前,显然要保证人物在站立状态并且不在读条,所以我们要加入判断,如果人物不是站立状态或者人物在读条就返回:复制代码下面就是采集代码了,这个代码我也不解释,这些相关函数的原型和用法都能在\ui\script\doodad.lua中找到。复制代码于是最终完成的AutoGather.lua是这样的:复制代码附上最终完成的插件:
AutoGather.rar(1.16 KB, 下载次数: 211)
PS:记得我前面说过这是个笨方法吗?其实现在剑三提供了获取视野内doodad的函数GetNearbyDoodadList(),只需要AutoGather.DooList = GetNearbyDoodadList()就可以获取doodad列表。有兴趣的同学可以自己改一下,怎么改我就不说了。
另外,其实可以很容易的在这个插件的基础之上实现自动庖丁、自动采集任务物品、只采集特定物品,这些相关的代码都能在doodad.lua里面找到,如果你能把这些功能写出来,那么你就已经是一个合格的插件作者了。
自动采集插件的功能就是自动采集身边的矿和草药,当人物停下来的时候,如果身边有可以采集到的矿和草,插件就是自动开始采集。
为了实现自动采集,我们的插件需要做这些工作:
1获得身边(视野内)的采集物信息。
2判断这些采集物是否可以采集(是否是矿/草?距离是否够近?)。
3如果满足条件,则开始采集。
由于要实现自动,所以以上步骤必须不断的进行。
在开始之前,我们先来建立插件的文件结构。先在插件目录Interface下面建立自动采集插件的目录AutoGather,然后在AutoGather下面建立3个文本文件:info.ini AutoGather.lua AutoGather.ini 。
看到AutoGather.ini了吧,这就是剑3的窗体文件了,我们这次就要用到它啦。别被窗体这两个字吓到了,在本例中,窗体其实是来打酱油的,你基本可以无视它。
Info.ini文件的结构我在上一篇教程中已经介绍过了,这里就不再赘述了,直接放上该文件内容:
- [AutoGather]
- name=自动采集
- desc=自动采集 by myself
- default=1
- version=0.5
- lua_0=Interface\AutoGather\AutoGather.lua
在文章的开头我说过,要实现自动,就要不断的循环一些动作。学过编程的同学应该知道如何实现不断的重复某些动作的方法吧?对了,就是死循环。在嵌入式编程中,一般都会看到主函数的最后有一个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加入如下内容就可以了:
- [AutoGather]
- ._WndType=WndFrame
- ._Parent=Lowest
- Left=0
- Top=0
- Width=0
- Height=0
- DragAreaLeft=0
- DragAreaTop=0
- DragAreaRight=0
- DragAreaBottom=0
- AnimateStartPosX=0
- AnimateStartPosY=0
- AnimateEndPosX=0
- AnimateEndPosY=0
- AnimateTimeSpace=0
- AnimateMoveSpeed=0
- ScriptFile=Interface\AutoGather\AutoGather.lua
- IsCustomDragable=0
- DragAreaWidth=0
- DragAreaHeight=0
- DummyWnd=1
- DisableBringToTop=1
- DisableBreath=0
- BreatheWhenHide=1
下面的._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文件的末尾,也就是你定义的函数的后面)
- Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
之后就可以定义OnFrameBreathe函数,我们在Wnd.OpenWindow的前面定义OnFrameBreathe函数:
- function AutoGather.OnFrameBreathe()
- end
- if GetLogicFrameCount()%16==0 then
- OutputMessage("MSG_SYS","我在呼吸哦\n")
- end
- AutoGather={}
- function AutoGather.OnFrameBreathe()
- if GetLogicFrameCount()%16==0 then
- OutputMessage("MSG_SYS","我在呼吸哦\n")
- end
- end
- 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开头加入:
- AutoGather.DooList={}
- RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
- RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
于是现在AutoGather.lua的内容是这样的:
- AutoGather={}
- AutoGather.DooList={}
- function AutoGather.OnFrameBreathe()
- if GetLogicFrameCount()%32==0 then
- OutputMessage("MSG_SYS","我在呼吸哦\n")
- end
- end
- RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
- RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
- Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
列表有了,就可以开始往OnFrameBreathe()里面写采集代码啦,不过在这之前,先先给它加个开关:
- AutoGather.bOn = false
- function AutoGather.OnFrameBreathe()
- if not AutoGather.bOn then
- return
- end
- end
- Hotkey.AddBinding("AutoGather", "切换开启状态", "自动采集",
- function()
- if AutoGather.bOn then
- AutoGather.bOn = fales
- OutputMessage("MSG_SYS","自动采集关闭\n")
- else
- AutoGather.bOn = true
- OutputMessage("MSG_SYS","自动采集开启\n")
- end
- end,
- nil)
之后我们继续在OnFrameBreathe里面加料,在采集之前,显然要保证人物在站立状态并且不在读条,所以我们要加入判断,如果人物不是站立状态或者人物在读条就返回:
- local player = GetClientPlayer()
- if not player then
- return
- end
- if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
- return
- end
- for _,dwID in pairs(AutoGather.DooList) do
- local doodad = GetDoodad(dwID)
- if doodad and doodad.CanDialog(player) then
- if doodad and doodad.nKind == DOODAD_KIND.CRAFT_TARGET then
- InteractDoodad(dwID)
- end
- end
- end
- AutoGather={}
- AutoGather.bOn = false
- AutoGather.DooList={}
- function AutoGather.OnFrameBreathe()
- if not AutoGather.bOn then
- return
- end
- local player = GetClientPlayer()
- if not player then
- return
- end
- if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
- return
- end
-
- for _,dwID in pairs(AutoGather.DooList) do
- local doodad = GetDoodad(dwID)
- if doodad and doodad.CanDialog(player) then
- if doodad and doodad.nKind == DOODAD_KIND.CRAFT_TARGET then
- InteractDoodad(dwID)
- end
- end
- end
-
- end
- RegisterEvent("DOODAD_ENTER_SCENE", function() table.insert(AutoGather.DooList,arg0) end)
- RegisterEvent("DOODAD_LEAVE_SCENE",function() table.remove(AutoGather.DooList,arg0) end)
- Wnd.OpenWindow("Interface/AutoGather/AutoGather.ini","AutoGather")
- Hotkey.AddBinding("AutoGather", "切换开启状态", "自动采集",
- function()
- if AutoGather.bOn then
- AutoGather.bOn = fales
- OutputMessage("MSG_SYS","自动采集关闭\n")
- else
- AutoGather.bOn = true
- OutputMessage("MSG_SYS","自动采集开启\n")
- end
- end,
- nil)
AutoGather.rar(1.16 KB, 下载次数: 211)
PS:记得我前面说过这是个笨方法吗?其实现在剑三提供了获取视野内doodad的函数GetNearbyDoodadList(),只需要AutoGather.DooList = GetNearbyDoodadList()就可以获取doodad列表。有兴趣的同学可以自己改一下,怎么改我就不说了。
另外,其实可以很容易的在这个插件的基础之上实现自动庖丁、自动采集任务物品、只采集特定物品,这些相关的代码都能在doodad.lua里面找到,如果你能把这些功能写出来,那么你就已经是一个合格的插件作者了。
- 剑网三插件入门教程(4):事件
- 剑网三插件入门教程(3):Hello JiangHu
- Chrome插件开发入门教程
- xcode插件入门教程
- 【Unity】ShaderForge插件入门教程
- 剑网三插件入门教程(2):在游戏中调试Lua代码
- xbmc视频插件开发入门教程
- xbmc视频插件开发入门教程
- 基于jquery插件开发入门教程
- Jquery树插件zTree入门教程
- Strumpy Shader Editor插件入门教程
- Jquery树插件zTree入门教程
- Atom插件开发入门教程(一)
- Atom插件开发入门教程(二)
- Atom插件开发入门教程(三)
- Atom插件开发入门教程(四)
- 前端表格插件 BootstrapTable 入门教程
- AngularJS入门教程10:事件处理器
- 黑马程序员——感悟:关于爱情、关于学习
- 剑网三插件入门教程(2):在游戏中调试Lua代码
- 剑网三插件入门教程(3):Hello JiangHu
- unity3d lightmap的assetbundle和动态载入
- bomblab
- 剑网三插件入门教程(4):事件
- Yale CAS实现原理及其基础协议
- 图像处理2——求负片
- toj1987 Faulty Odometer
- 自扩充的无锁(Lock-Free)并发环形队列算法
- 最新上海阿里巴巴笔试题分享
- 游戏服务器之逻辑服务器的资源分布图
- zigbee 网络如何设置PANID??
- vim常用命令总结