Ace3魔獸世界插件開發之旅(一)- WelcomeHome

本文一步一步講解如何通過Ace3開發框架構建一個WelcomeHome插件。文中大部分內容翻譯自gamepedia,原文地址:https://wow.gamepedia.com/WelcomeHome_-_Your_first_Ace3_Addon
由於英文水平有限,有不對之處還望指正,謝謝!

準備工作

目前魔獸的版本是8.1.5,魔獸插件都在<World of Warcraft_retail_InterfaceAddOns>這個目錄下,所以我們先建一個目錄:WelcomeHome,當WOW在AddOns目錄下發現一個目錄時,它會去找這個目錄下跟目錄同名的TOC文件,這個TOC文件包含了本插件所有文件的清單,WOW會使用這個文件來加載這個插件。下面是我們這個WelcomeHome.TOC文件的基本骨架:

## Interface: 81500
## Version: 0.1
## Title: Welcome Home
## Author: xiaop
## Notes: 爐石的時候顯示歡迎信息.
Core.lua

這時候需要建一個空的Core.lua文件,用來存放插件的代碼。現在我們先讓它空着,這時候登錄WOW在人物選擇界面點擊插件可以看到WelcomeHome這個插件,雖然它啥也幹不了。

引入Ace3庫

要想讓我們的插件具備具體功能,我們需要引入Ace3相關的庫。Ace3使用了一個叫做“嵌入式庫”的概念,它允許模塊開發者在其他模塊加載了相同庫的時候不需要再複製一份代碼。我們可以在 http://www.wowace.com/addons/ace3/files/這裏下載最新的Ace3庫,然後解壓到插件目錄的Libs目錄下。本文需要用到以下的庫:

現在我們有了Ace3相關的庫,但是WOW並不知道如何加載他們,我們需要一個embeds.xml文件來告訴WOW需要加載哪些文件,於是我們新建一個embeds.xml文件,內容如下:

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
    <Script file="Libs\LibStub\LibStub.lua"/>
    <Include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
    <Include file="Libs\AceAddon-3.0\AceAddon-3.0.xml"/>
    <Include file="Libs\AceEvent-3.0\AceEvent-3.0.xml"/>
    <Include file="Libs\AceDB-3.0\AceDB-3.0.xml"/>
    <Include file="Libs\AceConsole-3.0\AceConsole-3.0.xml"/>
    <Include file="Libs\AceGUI-3.0\AceGUI-3.0.xml"/>
    <Include file="Libs\AceConfig-3.0\AceConfig-3.0.xml"/>
</Ui>

Script標籤表示需要引入的lua文件,Include標籤表示要引入的xml文件。需要注意的是LibStub必須先加載,因爲其他庫都依賴它。其他文件也需要注意使用順序,原則就是被依賴的庫需要先加載。
現在我們更新一下TOC文件,在core.lua之前引入embeds.xml文件,以保證在core.lua執行之前所有的庫已被加載並可以使用。

## Interface: 81500
## Version: 0.1
## Title: Welcome Home
## Author: xiaop
## Notes: 爐石的時候顯示歡迎信息.
## SavedVariables: WelcomeHomeDB
## OptionalDeps: Ace3
## X-Embeds: Ace3

embeds.xml
Core.lua

Hello World

下面我們編輯一下Core.lua文件,加入Ace3最基本的結構:

WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0")

function WelcomeHome:OnInitialize()
    -- Called when the addon is loaded
end

function WelcomeHome:OnEnable()
    -- Called when the addon is enabled
end

function WelcomeHome:OnDisable()
    -- Called when the addon is disabled
end

第一行使用NewAddon方法創建一個AceAddon類的實例,因爲我們會用到聊天窗口和斜槓命令,我們還混入了AceConsole類。
接下來是三個重寫的方法OnInitialize, OnEnable, 和 OnDisable。OnInitialize只會在UI加載的時候執行一次,後面兩個分別在插件被啓用和禁用的時候執行。
下面我們假設插件正在加載,我們想在聊天窗口打印一句“Hello World”,只需要在OnEnable方法中添加一行代碼:

function WelcomeHome:OnEnable()
    self:Print("Hello World!")
end

這樣,在我們重新進入WOW的時候就會在聊天窗口底部看到這句“Hello World”。

響應事件

這個插件的目標是在WOWer回到爐石所在區域的時候提示歡迎信息,那麼我們要如何知道WOWer已經回到爐石所在區域了呢?很簡單,我們只需要響應某一個ZONE_CHANGED事件即可,這個事件會在WOWer進入一個新區域的時候觸發。
WoW是事件驅動的,我們插件能做的事情都要依賴於各種事件,否則我們啥也幹不了。
爲了響應事件我們需要混入AceEvent庫,然後通過RegisterEvent方法監聽ZONE_CHANGED事件,在事件觸發的時候調用ZONE_CHANGED方法打印一句“You have changed zones!”在聊天窗口。Core.lua的代碼如下:

WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0", "AceEvent-3.0")

function WelcomeHome:OnInitialize()
    -- Called when the addon is loaded
end

function WelcomeHome:OnEnable()
    self:RegisterEvent("ZONE_CHANGED")
end

function WelcomeHome:ZONE_CHANGED()
    self:Print("You have changed zones!")
end

無需重啓遊戲,使用/reload命令即可重新載入修改後的插件。

使用WoW API

現在我們有一個方法可以在WoWer切換區域的時候被調用,那麼我們如何知道他現在在哪個區域,他的爐石又綁在哪裏呢?這個可以在WoW API中找到答案,

  • GetBindLocation 方法會返回子區域的名字,其中包含了爐石所在地
  • GetSubZoneText 方法返回WoWer當前所在子區域,如果當前沒有子區域,會返回空串。

下面修改代碼來看看效果,我們在切換區域的時候把上面兩個方法的返回值打印到聊天窗口中,代碼如下:

function WelcomeHome:ZONE_CHANGED()
    self:Print(GetBindLocation())
    self:Print("================")
    self:Print(GetSubZoneText())
end

這樣我們可以在切換區域的時候打印出爐石所在區域和當前的區域。效果如下:
image.png

聊天命令和配置

我們可以使用AceConfig來註冊一個option table,以支持開箱即用的斜槓命令。

WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0", "AceEvent-3.0")
local options = {
    name = "WelcomeHome",
    handler = WelcomeHome,
    type = 'group',
    args = {
    },
}
function WelcomeHome:OnInitialize()
    -- Called when the addon is loaded
    LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options, {"welcomehome", "wh"})
end

重載UI以後在聊天窗口輸入/WelcomeHome或者/wh,可以看到插件的提示信息(插件名字,描述, 可用命令等)。
下面我們添加一個讓用戶可以改變提示文字的命令,命令名叫msg,可以攜帶一個文本參數,使用get和set方法來操作底層變量,:

local options = {
    name = "WelcomeHome",
    handler = WelcomeHome,
    type = 'group',
    args = {
        msg = {
                type = "input",
                name = "Message",
                desc = "The message to be displayed when you get home.",
                usage = "<Your message>",
                get = "GetMessage",
                set = "SetMessage",
            },
    },
}
function WelcomeHome:GetMessage(info)
    return self.message
end

function WelcomeHome:SetMessage(info, newValue)
    self.message = newValue
end

function WelcomeHome:ZONE_CHANGED()
    self:Print("welcome msg is:",self.message)
    self:Print("================")
    self:Print("your heartstone set is : ",GetBindLocation())
    self:Print("================")
    self:Print("current subzone is : ",GetSubZoneText())
end

執行命令/wh msg helloworld,然後再切換區域的i時候就會彈出以下信息:
image.png

GUI和暴雪接口選項

我們應該不僅滿足於使用命令來實現插件,暴雪從2.4補丁開始重做了接口選項,可以將插件添加到遊戲的“插件”標籤。爲了做到這一點,我們需要稍微改變以下代碼的處理方式:

function WelcomeHome:OnInitialize()
    -- Called when the addon is loaded
    LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options)
    self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome", "WelcomeHome")
    self:RegisterChatCommand("welcomehome", "ChatCommand")
    self:RegisterChatCommand("wh", "ChatCommand")
    WelcomeHome.message = "Welcome Home!"
end
function WelcomeHome:ChatCommand(input)
    if not input or input:trim() == "" then
        InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
    else
        LibStub("AceConfigCmd-3.0"):HandleCommand("wh", "WelcomeHome", input)
    end
end

AddToBlizOptions方法返回一個frame,我們可以用這個frame來打開暴雪的接口選項。然後我們用ChatCommand方法來控制插件的行爲表現,如果輸入的內容爲空,則會直接打開暴雪接口選項,這樣我們就可以通過GUI來配置我們的插件,否則還是按照命令原來的顯示邏輯在聊天窗口中顯示。
以下是輸入/wh的效果:
image.png

讓提示消息更突出

我們可以使用UIErrorsFrame 來將提示消息顯示在屏幕的指定位置,簡單來說只需要添加一行代碼:

function WelcomeHome:ZONE_CHANGED()
    self:Print("welcome msg is:",self.message)
    self:Print("================")
    self:Print("your heartstone set is : ",GetBindLocation())
    self:Print("================")
    self:Print("current subzone is : ",GetSubZoneText())
    UIErrorsFrame:AddMessage(self.message, 1.0, 1.0, 1.0, 5.0)
end

效果如下:
image.png

在不同的session之間保存配置

目前我們的歡迎消息在我們退出遊戲以後就丟失了,WoW提供了一種在不同的session之間保存配置的方法叫做Saved Variables,而Ace的方法是AceDB,通過AceDB我們將提示消息持久保存。

local defaults = {
    profile = {
        message = "Welcome Home!"
    },
}
function WelcomeHome:GetMessage(info)
    -- return self.message
    return self.db.profile.message
end

function WelcomeHome:SetMessage(info, newValue)
    self.db.profile.message = newValue
end

function WelcomeHome:OnInitialize()
    -- 命令行的方式
    -- LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options, {"welcomehome", "wh"})
    
    -- GUI的方式    
    self.db = LibStub("AceDB-3.0"):New("WelcomeHomeDB", defaults, true)
    
    LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options)
    self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome", "WelcomeHome")
    self:RegisterChatCommand("welcomehome", "ChatCommand")
    self:RegisterChatCommand("wh", "ChatCommand")
    WelcomeHome.message = "Welcome Home!"
end

至此,這個插件基本就完成了,雖然功能比較簡單,但是是一個很好的入門項目.

完整代碼

  • WelcomeHome.TOC
## Interface: 81500
## Version: 0.1
## Title: Welcome Home
## Author: xiaop
## Notes: 爐石的時候顯示歡迎信息.
## SavedVariables: WelcomeHomeDB
## OptionalDeps: Ace3
## X-Embeds: Ace3

embeds.xml
Core.lua
  • embeds.xml
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
    <Script file="Libs\LibStub\LibStub.lua"/>
    <Include file="Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
    <Include file="Libs\AceAddon-3.0\AceAddon-3.0.xml"/>
    <Include file="Libs\AceEvent-3.0\AceEvent-3.0.xml"/>
    <Include file="Libs\AceDB-3.0\AceDB-3.0.xml"/>
    <Include file="Libs\AceConsole-3.0\AceConsole-3.0.xml"/>
    <Include file="Libs\AceGUI-3.0\AceGUI-3.0.xml"/>
    <Include file="Libs\AceConfig-3.0\AceConfig-3.0.xml"/>
</Ui>
  • Core.lua:
WelcomeHome = LibStub("AceAddon-3.0"):NewAddon("WelcomeHome", "AceConsole-3.0", "AceEvent-3.0")
local options = {
    name = "WelcomeHome",
    handler = WelcomeHome,
    type = 'group',
    args = {
        msg = {
                type = "input",
                name = "Message",
                desc = "The message to be displayed when you get home.",
                usage = "<Your message>",
                get = "GetMessage",
                set = "SetMessage",
            },
    },
}
local defaults = {
    profile = {
        message = "Welcome Home!"
    },
}
-- 保存在db中就不需要這個變量了
-- WelcomeHome.message = "Welcome Home!"

function WelcomeHome:GetMessage(info)
    -- return self.message
    return self.db.profile.message
end

function WelcomeHome:SetMessage(info, newValue)
    self.db.profile.message = newValue
end

function WelcomeHome:OnInitialize()
    -- 命令行的方式
    -- LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options, {"welcomehome", "wh"})
    
    -- GUI的方式    
    self.db = LibStub("AceDB-3.0"):New("WelcomeHomeDB", defaults, true)
    
    LibStub("AceConfig-3.0"):RegisterOptionsTable("WelcomeHome", options)
    self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("WelcomeHome", "WelcomeHome")
    self:RegisterChatCommand("welcomehome", "ChatCommand")
    self:RegisterChatCommand("wh", "ChatCommand")
    WelcomeHome.message = "Welcome Home!"
end

function WelcomeHome:ChatCommand(input)
    if not input or input:trim() == "" then
        InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
    else
        LibStub("AceConfigCmd-3.0"):HandleCommand("wh", "WelcomeHome", input)
    end
end

function WelcomeHome:OnEnable()
    self:RegisterEvent("ZONE_CHANGED")
end

function WelcomeHome:ZONE_CHANGED()
    self:Print("welcome msg is:",self.db.profile.message)
    self:Print("================")
    self:Print("your heartstone set is : ",GetBindLocation())
    self:Print("================")
    self:Print("current subzone is : ",GetSubZoneText())
    UIErrorsFrame:AddMessage(self.db.profile.message, 1.0, 1.0, 1.0, 5.0)
end
  • WelcomeHome目錄結構:
│  Core.lua
│  embeds.xml
│  WelcomeHome.TOC
│
└─Libs
    ├─AceAddon-3.0
    │      AceAddon-3.0.lua
    │      AceAddon-3.0.xml
    │
    ├─AceConfig-3.0
    │  │  AceConfig-3.0.lua
    │  │  AceConfig-3.0.xml
    │  │
    │  ├─AceConfigCmd-3.0
    │  │      AceConfigCmd-3.0.lua
    │  │      AceConfigCmd-3.0.xml
    │  │
    │  ├─AceConfigDialog-3.0
    │  │      AceConfigDialog-3.0.lua
    │  │      AceConfigDialog-3.0.xml
    │  │
    │  └─AceConfigRegistry-3.0
    │          AceConfigRegistry-3.0.lua
    │          AceConfigRegistry-3.0.xml
    │
    ├─AceConsole-3.0
    │      AceConsole-3.0.lua
    │      AceConsole-3.0.xml
    │
    ├─AceDB-3.0
    │      AceDB-3.0.lua
    │      AceDB-3.0.xml
    │
    ├─AceEvent-3.0
    │      AceEvent-3.0.lua
    │      AceEvent-3.0.xml
    │
    ├─AceGUI-3.0
    │  │  AceGUI-3.0.lua
    │  │  AceGUI-3.0.xml
    │  │
    │  └─widgets
    │          AceGUIContainer-BlizOptionsGroup.lua
    │          AceGUIContainer-DropDownGroup.lua
    │          AceGUIContainer-Frame.lua
    │          AceGUIContainer-InlineGroup.lua
    │          AceGUIContainer-ScrollFrame.lua
    │          AceGUIContainer-SimpleGroup.lua
    │          AceGUIContainer-TabGroup.lua
    │          AceGUIContainer-TreeGroup.lua
    │          AceGUIContainer-Window.lua
    │          AceGUIWidget-Button.lua
    │          AceGUIWidget-CheckBox.lua
    │          AceGUIWidget-ColorPicker.lua
    │          AceGUIWidget-DropDown-Items.lua
    │          AceGUIWidget-DropDown.lua
    │          AceGUIWidget-EditBox.lua
    │          AceGUIWidget-Heading.lua
    │          AceGUIWidget-Icon.lua
    │          AceGUIWidget-InteractiveLabel.lua
    │          AceGUIWidget-Keybinding.lua
    │          AceGUIWidget-Label.lua
    │          AceGUIWidget-MultiLineEditBox.lua
    │          AceGUIWidget-Slider.lua
    │
    ├─CallbackHandler-1.0
    │      CallbackHandler-1.0.lua
    │      CallbackHandler-1.0.xml
    │
    └─LibStub
            LibStub.lua
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章