提交問題刪除
提交問題
遊戲下載 創建公會 劍3獵手 經脈模擬器 遊戲論壇
專區首頁 新手專題 職業專欄 聲望專題 套裝獲取 副本專題 任務大全 插件下載
當前位置:劍俠情緣3遊戲專區 >> 插件 >> 正文
劍俠三插件入門教程(4):自動採集實例教學
劍俠三插件入門教程(4):自動採集實例教學
發佈時間:2011年11月01日 20:18:31    作者:開心遊戲網    人氣:40659    進入討論區
本篇教程將通過編寫一個簡單的自動採集插件來介紹劍三的「事件(Event)」這個概念。
自動採集插件的功能就是自動採集身邊的礦和草藥,當人物停下來的時候,如果身邊有可以採集到的礦和草,插件就是自動開始採集。

為了實現自動採集,我們的插件需要做這些工作:
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
  • AutoGather.luaAutoGather.ini暫時保持空白。

    在文章的開頭我說過,要實現自動,就要不斷的循環一些動作。學過編程的同學應該知道如何實現不斷的重複某些動作的方法吧?對了,就是死循環。在嵌入式編程中,一般都會看到主函數的最後有一個while(1){},各種需要重複執行的代碼就放在裡面,Windows編程的消息循環應該也是死循環吧(我沒學過win編程,不太懂)。

    但是,如果你在劍三中執行一個死循環會發上什麼事情呢?你可以自己試試看,在
    cube中執行一個死循環 while true do end,點執行之後看到效果了沒?是的,遊戲死掉了。
    為何會死掉呢?我介紹一下劍3Lua執行機制你就能明白了。劍3Lua腳本的執行並不是並行的,也就是說,腳本的執行並不是多任務的。劍3Lua引擎是基於「幀」的方式執行腳本的,簡單的說,要執行的代碼是放在「幀」裡面的,這一幀的代碼執行完畢後才會執行下一幀,劍三的客戶端一般每秒鐘會執行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
  • 第一行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 ("Interface/AutoGather/AutoGather.ini","AutoGather")
    Wnd.OpenWindow裡面的兩個參數應該一看就明白了吧,第一個參數是窗體文件路徑,第二個參數是窗體名,也就是AutoGather.ini的第一行那個名字。

    之後就可以定義OnFrameBreathe函數,我們在Wnd.OpenWindow的前面定義OnFrameBreathe函數:
    複製內容到剪貼板
    代碼:
    function AutoGather.OnFrameBreathe()
  • end
  • 為了測試是否能正常呼吸,我們在這個函數里加入測試語句:
    複製內容到剪貼板
    代碼:
    if GetLogicFrameCount()%16==0 then
  • OutputMessage("MSG_SYS","我在呼吸哦\n")
  • end
  • 於是現在AutoGather.lua的內容是這樣的:
     
    代碼:
    AutoGather={}
  • function AutoGather.OnFrameBreathe()
  • if GetLogicFrameCount()%16==0 then
  • OutputMessage("MSG_SYS","我在呼吸哦\n")
  • end
  • end
  • Wnd.OpenWindow ("Interface/AutoGather/AutoGather.ini","AutoGather")
  • 進入遊戲後如果看到聊天欄每秒刷一行字:「我在呼吸哦」,就表明你成功了。

    之後就刪掉那3行測試語句,然後繼續下一步吧。

     
    小知識:我們在遊戲中看到的東西,除了固定的地圖之外,只有三類,分別是PlayerNpcDoodad。顧名思義,Player是玩家,NpcNpc。但是Doodad呢?doodad這個詞的字面意思是小擺設。基本上遊戲中那些不會動的小物件都是doodad,包括了各種採集物,某些桌椅板凳,甚至主城裡房子上那塊牌子。我們今天要採集的草和礦,就都是doodad


    為了能採集草
    /礦也就是doodad,我們首先需要有一個視野內doodad的列表,但是很不幸,早期的劍三並沒有提供一個能直接獲得doodad列表的函數,所以那個時候收集doodad列表就要用到事件。當然,現在我們有了更便捷的方法,但是為了介紹事件,我先來講解一下這個以前的笨方法。

    事件這個概念肯定大家都懂,劍三在發生某些事的時候會產生一個事件,如果RegisterEvent註冊過這個事件,程序就會去調用你定義過的事件處理函數,如果這個事件帶有參數的話,遊戲會用arg0~arg9這幾個全局變量傳遞參數。

    這裡我們要用到2個事件:DOODAD_ENTER_SCENEDOODAD_LEAVE_SCENE,顧名思義,他們分別是doodad進入視野和doodad離開視野,他們使用arg0傳遞doodadID。利用這2個事件,我們定義一個列表,在doodad進入視野以後把它加進去,在doodad離開視野以後再刪掉,就能取得視野內的doodad列表了。
    為了實現它,先定義一個表來存放doodad列表,我們在AutoGather.lua開頭加入:
    複製內容到剪貼板
    代碼:
    AutoGather.DooList={}
    之後用RegisterEvent註冊兩個事件:
    代碼:
    RegisterEvent("DOODAD_ENTER_SCENE",function() table.insert(AutoGather.DooList,arg0) end)
  • RegisterEvent("DOODAD_LEAVE_SCENE",function()table.remove(AutoGather.DooList,arg0) end)
  • RegisterEvent的第一個參數是要註冊的事件,第二個參數是事件發生時要調用的函數,這裡放的是用function()直接定義的簡單函數,如果你的函數是在腳本中定義好的,那麼第二個參數直接放函數名就可以了,記住後面不要加上()例子:RegisterEvent("DOODAD_ENTER_SCENE",AutoGather.TestFunc)

    於是現在
    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)
  • 這段代碼我就不解釋了,看不懂就先去學好lua吧……



    之後我們繼續在OnFrameBreathe裡面加料,在採集之前,顯然要保證人物在站立狀態並且不在讀條,所以我們要加入判斷,如果人物不是站立狀態或者人物在讀條就返回:
    代碼:
    local player = GetClientPlayer()
  • if not player then
  •     return
  • end
  • if player.nMoveState ~= MOVE_STATE.ON_STAND or player.GetOTActionState() ~= 0 then
  •     return
  • end
  • 下面就是採集代碼了,這個代碼我也不解釋,這些相關函數的原型和用法都能在\ui\script\doodad.lua中找到。
    代碼:
    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.lua是這樣的:
    複製內容到剪貼板
    代碼:
    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)
  • 5
    0
    0
    0
    0
    0

    有料

    淚奔

    無聊

    XD

    掀桌

    KUSO
    0