ADDONS編寫普及(轉貼)

Home Home
引用 | 編輯 imiss
2005-10-20 09:59
樓主
推文 x0
難得一見的好文:)
對瞭解UI有一定程度的幫助唷

BY imiss

為了感恩原作者大大的辛勞,所以特此轉載此貼,讓更多的玩家一起加入UI製作者行列:)原文將不做任何修改

作者:WOWAR。英雄
前言
22號基本寫完。比預計的要快

這個不是數學書或者語文書。我不想也沒有那個能力寫成那樣……
所以請抱著看小說的態度來看。

因為ADDONS的編寫是一個整體。我實在無法分清哪個是要先說,哪個是要後說
所以有看不懂的地方就跳過去。接著看下面

前幾層樓寫的相對詳細些。後幾層樓更多的是提示。我還是希望看官能自己分析,自己動手研究

最後。短短的6層樓包含了我半年的心血,而且我語文課真的是沒好好去上
所以如果一時看不懂。請多讀幾遍 或者回帖指出,我會盡力解釋的

Addons (Add-Ons)
中文直譯:附件,附加 附加軟體。 俗稱:插件

解釋:他們是一些附加的檔,被放置在玩家 WOW 遊戲目錄下的 Interface 檔夾中。插件 使用暴雪提供的LUA和XML代碼(官方API函數介面)來擴充玩家可以使用的用戶介面功能。

插件是通過(暴雪提供的)LUA和XML檔(函數)構成的,並且也是通過暴雪的編譯機所解釋和執行的。因此,暴雪也不會封停任何使用插件的玩家。

StatusBar
在之前我們大概知道了ADDONS的一些基本概念

那麼現在。在各種類型的框體中挑一個StatusBar來說說

基本概念
StatusBar:是WOW中用來定義類似施法條。進度條之類的一種框體,
說白了就是可以根據某個數值,即時的改變條條的長短

一個StatusBar有3個重要的參數
1、最大長度 maxValue
2、最小長度 minValue
3、當前長度 Value

要動態的改變StatusBar就需要用腳本(Scripts)中的事件即時的設置當前長度(Value)
(這句話可能有點饒口。不過我就這點語文水準了…見諒見諒)

準備工作

首先根據我們之前的概念。一個TOC檔是必不可少的,編個first.toc
## Interface: 1600
## Title: 我的第一插件
## Notes: 真的是我的第一插件哦

然後我們當然得用XML來定義StatusBar這個框體,那麼編寫個first.xml
不過WOW並不知道我們寫了first.xml這個檔。我們得告訴他。所以在first.toc中加一句(紅色的部分)
## Interface: 1600
## Title: 我的第一插件
## Notes: 真的是我的第一插件哦
first.xml

好了 正式開始編寫first.xml

按照基本的XML檔格式 先寫好基本的嵌套
<StatusBar>
</StatusBar>

起個好名字
<StatusBar name="haomingzi">
</StatusBar>

注意紅色的那句。現在我們名叫haomingzi的StatusBar 會根據變數SVALUE自動改變長短了
(這裏我們用到了腳本。後面會詳細解釋的。先記得<Scripts>是腳本就可以了)
<StatusBar name="haomingzi">
          <Scripts>
                <OnUpdate>
                    haomingzi:SetValue(SVALUE);
                </OnUpdate>
          </Scripts>
</StatusBar>

現在我們把變數SVALUE設置為玩家的血量
那麼:
<StatusBar name="haomingzi">
          <Scripts>
                <OnUpdate>
                    haomingzi:SetValue( UnitHealth("player") );
                </OnUpdate>
          </Scripts>
</StatusBar>
OK。一個玩家的HP條就寫出來了

當然僅僅這幾行還遠遠不夠,繼續完善下
先把最大和最小長度定義好
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <Scripts>
                <OnUpdate>
                    haomingzi:SetValue( UnitHealth("player") );
                </OnUpdate>
          </Scripts>
</StatusBar>

這裏我們設置的是0到100。顯然玩家的血量肯定不會在0到100之內的。
那麼我們就要把他轉換為百分比
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <Scripts>
                <OnUpdate>
                    local SVALUE=( UnitHealth("player") / UnitHealthMax("player") )*100
                    haomingzi:SetValue(SVALUE);
                </OnUpdate>
          </Scripts>
</StatusBar>

設定下他的位置,比方把他放在螢幕的中間
(具體的如何設置位置。在樓下會講。現在只要知道紅色部分的代碼是設定位置就可以了)
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <Anchors>
                <Anchor point="center" relativeTo="UIparent" relativePoint="center"/>
          </Anchors>
          <Scripts>
                <OnUpdate>
                    local SVALUE=( UnitHealth("player") / UnitHealthMax("player") )*100
                    haomingzi:SetValue(SVALUE);
                </OnUpdate>
          </Scripts>
</StatusBar>

設定一下大小
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <Size>
                <AbsDimension x="70" y="8"/>
          </Size>
          <Anchors>
                <Anchor point="center" relativeTo="UIparent" relativePoint="center"/>
          </Anchors>
          <Scripts>
                <OnUpdate>
                    local SVALUE=( UnitHealth("player") / UnitHealthMax("player") )*100
                    haomingzi:SetValue(SVALUE);
                </OnUpdate>
          </Scripts>
</StatusBar>

當然他長什麼樣子我們還沒弄呢~
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
          <Size>
                <AbsDimension x="70" y="8"/>
          </Size>
          <Anchors>
                <Anchor point="center" relativeTo="UIparent" relativePoint="center"/>
          </Anchors>
          <Scripts>
                <OnUpdate>
                    local SVALUE=( UnitHealth("player") / UnitHealthMax("player") )*100
                    haomingzi:SetValue(SVALUE);
                </OnUpdate>
          </Scripts>
</StatusBar>
最後給頭尾加上最基本的<ui></ui>嵌套。就大功告成了
<ui>
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
          <Size>
                <AbsDimension x="70" y="8"/>
          </Size>
          <Anchors>
                <Anchor point="center" relativeTo="UIparent" relativePoint="center"/>
          </Anchors>
          <Scripts>
                <OnUpdate>
                    local SVALUE=( UnitHealth("player") / UnitHealthMax("player") )*100
                    haomingzi:SetValue(SVALUE);
                </OnUpdate>
          </Scripts>
</StatusBar>
</ui>

進階
StatusBar除了SetValue這個重要的命令以外。還有個SetStatusBarColor命令。是用來改變顏色的

比如還是上面的例子
我們現在想當HP超過50%的時候為綠色 低於50%的時候為紅色
那麼先定義一個函數 就叫haomingzi_OnUpdate把 用來實現上面的功能
function haomingzi_OnUpdate()
local SVALUE=( UnitHealth("player") / UnitHealthMax("player") )*100;
if SVALUE > 50 then
    haomingzi:SetStatusBarColor(1,0,0);
end
end

然後我們在腳本中調用這個函數就可以了
<ui>
<StatusBar name="haomingzi" minValue="0" maxValue="100">
          <BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
          <Size>
                <AbsDimension x="70" y="8"/>
          </Size>
          <Anchors>
                <Anchor point="center" relativeTo="UIparent" relativePoint="center"/>
          </Anchors>
          <Scripts>
                <OnUpdate>
                    haomingzi_OnUpdate();
                </OnUpdate>
          </Scripts>
</StatusBar>
</ui>
更多的框體
當然框體絕對不僅僅只有<StatusBar>這一中。還有諸如<Button> <Frame> <Texture> <FontString>等等等等
這裏就不一一解釋了,你隨便打開一個寫好的ADDONS都可以發現他們的身影。自己分析一下把

位置

這裏將說說 如何定義一個框體的位置

我們打開任意的一個編寫好的ADDONS的XML檔。多半會發現形如這樣的代碼
CODE:     [Copy to clipboard]
<Anchors>   <Anchor point="CENTER" relativeTo="Minimap" relativePoint="CENTER">     <Offset>         <AbsDimension x="55" y="-55"/>     </Offset>   </Anchor></Anchors>


這樣的代碼就是用來定義的位置的

數學課和物理課都上過把?(雖然我很討厭我們過去的數學老師 ><)
想知道任何一個物體的位置。只要有一個固定的參考物。再有與參考物體的相對座標。就可以了
---------------------------------------------------------------------------------------------------
先來點基本概念

我們用一個方框來表示框體,那麼

                TOP
  TOPLEFT     --------------------     TOPRIGHT
          |             |
  LEFT       |     CENTER     |     RIGHT
          |             |
BOTTOMLEFT   --------------------     BOTTOMLEFT
                      BOTTOM

應該有點頭緒了把?
之前那段代碼的意思就是 把位置定義在
小地圖的中心點和我們的框體的中心點X座標為55 Y座標為55的地方

如果看不明白這句話 不要緊 我們一句一句的來分析
------------------------------------------------------------------------------------------------------
具體分析

頭一句和最後一句
<Anchors>
</Anchors>
這個嵌套是告訴WOW:中間的代碼是定義位置

第二句和倒數第二句
  <Anchor point="CENTER" relativeTo="Minimap" relativePoint="CENTER">
  </Anchor>
這個嵌套就是告訴WOW 我們開始定義位置了。

先看第二句。
point="CENTER" 參考之前我畫的那張很醜陋的圖。
意思就是:要定義位置的框體(為了描述方便。以下簡稱為框體A)的中心點(CENTER)作為定義點。
(定義點這個名詞是我杜撰的。如果不明白。先接著往下看)

relativeTo="Minimap"
意思就是:給我們的框體A設置一個參考物(為了描述方便。以下把參考物簡稱為框體B)
在這裏。就是把框體B設置為小地圖(Minimap)

relativePoint="CENTER"
同樣的。也得給我們的框體B設置一個定義點
在這裏。就設置為中心點(CENTER)

現在解釋一下定義點這個我杜撰的名詞
為什麼要有定義點這個概念呢?
因為所有的框體都不是一個點。而是一個平面。而相對座標只能是點與點的座標。所以就必須在框體上找一個點來定義座標
而這個點。就是我所謂的定義點
至於一個框體的定義點可以設置為那些,參考上面我畫的那張醜陋的圖

搞明白了以上的概念。那麼中間的那段代碼
CODE:     [Copy to clipboard]
    <Offset>         <AbsDimension x="55" y="-55"/>     </Offset>


也就不難理解是什麼意思了。這正是設置2個定義點之間的相對座標
-------------------------------------------------------------------------------------------------------
進階
為什麼一向崇尚操作簡單的暴雪要把位置的定義弄的這麼複雜呢?
似乎我們只要變換2個定義點之間的相對座標。那麼無論我們把定義點怎麼設置 都可以達到同樣的效果
其實。是因為框體的大小有時候是不固定的。

比如我想實現這樣的效果:
在玩家血條的左邊顯示HP的具體數值
如果這麼定義位置:
CODE:     [Copy to clipboard]
<FontString name="HPText"><Anchors>   <Anchor point="CENTER" relativeTo="PlayerFrame" relativePoint="CENTER">     <Offset>         <AbsDimension x="55" y="0"/>     </Offset>   </Anchor></Anchors>     。   。   。   。


似乎可以到達效果,
可是HP有多有少。
當HP為3位數的時候。數值是在血條的左邊。但是但HP為4為數的時候。數值就超過了左邊擋主了部分血條。

所以。得這麼寫
CODE:     [Copy to clipboard]
<FontString name="HPText"><Anchors>   <Anchor point="RIGHT" relativeTo="PlayerFrame" relativePoint="LEFT">     <Offset>         <AbsDimension x="0" y="0"/>     </Offset>   </Anchor></Anchors>     。   。   。   。


這樣。無論HP為多少。數值的右側永遠都和血條的坐側對齊
---------------------------------------------------------------------------------------------------------------
PS:
當相對座標為0,0的時候。代碼可以簡化
比如剛才的代碼可以簡化為
CODE:     [Copy to clipboard]
<FontString name="HPText"><Anchors>   <Anchor point="RIGHT" relativeTo="PlayerFrame" relativePoint="LEFT"/></Anchors>



  。
  。
  。
注意:別漏看了第三句最後的那個反斜杠

腳本

看到這裏。我們對框體的定義應該有了很大的瞭解。現在定義一個自己的框體應該沒什麼難度了把?
在STATUSBAR部分我們提到了腳本。
腳本我個人覺得是ADDONS的精髓
弄懂了腳本部分。那麼去他的FLEXBAR 去他的DAB 去他的DUF 我們不需要了。我們自己就可以來做了

基本概念
什麼是腳本。通俗的說:腳本就是告訴框體在什麼時候執行什麼命令

同樣的
我們用<Scripts></Scripts>這樣的嵌套來表示代碼中腳本的部分

具體的舉個例子
還記得FLEXBAR或者DAB一個很實用的功能把?當滑鼠進入按扭的區域 按紐顯示 離開則隱藏
現在我們直接在ADDONS中寫(為了描述方便起見 以下只寫出代碼中我們需要注意的部分)
<BUTTON name="button_1">
    <Scripts>
          <OnEnter>
                This:Show();
          </OnEnter>
          <OnLeave>
                This:Hide();
          </OnLeave>
    </Scripts>
</BUTTON>

<OnEnter>這個嵌套就是滑鼠進入框體的區域需要執行的命令
<OnLeave>則是滑鼠離開框體的區域需要執行的命令
如何?是不是很簡單呢?

接著來。
FLEXBAR或者DAB還有很多神奇的功能。比如根據條件自動改變按扭的位置 透明度 縮放 等等等
如果我們直接在ADDONS中編寫也很方便
比如進入戰鬥狀態 自動出現按扭 反之隱藏
<BUTTON name="button_1">
    <Scripts>
          <OnLoad>
                this:RegisterEvent("PLAYER_ENTER_COMBAT");
                this:RegisterEvent("PLAYER_LEAVE_COMBAT");
          </OnLoad>
          <OnEvent>
                if (event == "PLAYER_ENTER_COMBAT") then
                    this:Show();
                elseif (event == "PLAYER_LEAVE_COMBAT") then
                    this:Hide();
                end
          </OnEvent>
    </Scripts>
</BUTTON>

<OnLoad>是框體被載入的時候需要執行的命令
這裏。我們給button_1這個框體註冊了2個事件:玩家進入戰鬥和玩家離開戰鬥

<OnEvent>是註冊的事件發生的時候需要執行的命令
這裏。我們用了一個判斷語句。
當事件為玩家進入戰鬥的時候 顯示按扭1 當事件為玩家離開戰鬥的時候 隱藏按扭1

同樣的
腳本和框體一樣 不可能僅僅只有我上面所說的那幾個。
更多的腳本需要你自己去發現。。我不想囉嗦了。

更多的驚喜
以上2個只是很簡單的例子。腳本中執行的命令還可以是相互調用。相互依存的來實現更多更複雜的功能
這時候。僅僅一個XML檔已經不能滿足我們的需要了。我們得在LUA檔中來編寫

LUA

如果你有耐心看完了上面的全部內容 並且親手去實驗了
那麼如何編寫一個XML檔應該了然與胸了把

當然一個精巧的ADDONS不可能僅僅只有XML檔而已。他還需要LUA檔

LUA檔當然就是用LUA格式寫的

具體的LUA的語法 限與帖子的篇幅,不能詳盡說明。好在現在網上的資料很多的

我只說幾個個人覺得很有用的部分

1。引用LUA和定義函數
先回頭看6樓的最後那段代碼
<BUTTON name="button_1">
    <Scripts>
          <OnLoad>
                this:RegisterEvent("PLAYER_ENTER_COMBAT");
                this:RegisterEvent("PLAYER_LEAVE_COMBAT");
          </OnLoad>
          </OnEvent>
                if (event == "PLAYER_ENTER_COMBAT") then
                    this:Show();
                elseif (event == "PLAYER_LEAVE_COMBAT") then
                    this:Hide();
                end
          </OnEvent>
    </Scripts>
</BUTTON>
我們可以把腳本的部分放到LUA中來寫。

首先我們新建一個BUTTON1.LUA文件
然後在XML檔裏面要告訴WOW 我們寫了BUTTON1.LUA文件
<Script file="BUTTON1.lua"/>
<BUTTON name="button_1">
    <Scripts>
          <OnLoad>
                this:RegisterEvent("PLAYER_ENTER_COMBAT");
                this:RegisterEvent("PLAYER_LEAVE_COMBAT");
          </OnLoad>
          <OnEvent>
                if (event == "PLAYER_ENTER_COMBAT") then
                    this:Show();
                elseif (event == "PLAYER_LEAVE_COMBAT") then
                    this:Hide();
                end
          </OnEvent>
    </Scripts>
</BUTTON>

定義函數的LUA命令是function
現在我們就把<OnLoad>和<OnEvent>這2個部分的命令定義為函數
function button_1_onload()
                this:RegisterEvent("PLAYER_ENTER_COMBAT");
                this:RegisterEvent("PLAYER_LEAVE_COMBAT");
end

function button_1_onevent(event)
                if (event == "PLAYER_ENTER_COMBAT") then
                    this:Show();
                elseif (event == "PLAYER_LEAVE_COMBAT") then
                    this:Hide();
                end
end

然後在XML檔中引用這2個函數
<Script file="BUTTON1.lua"/>
<BUTTON name="button_1">
    <Scripts>
          <OnLoad>
                button_1_onload();
          </OnLoad>
          <OnEvent>
                button_1_onevent(event);
          </OnEvent>
    </Scripts>
</BUTTON>
這樣原來的一個XML檔就被我們分成了2個檔 LUA和XML
也許就上面的那段簡單的代碼 我們還覺得這樣做並沒有什麼太大的意義
不過。當你寫了一段很複雜 很麻煩的代碼的時候。這麼做顯然有助與你簡化代碼和理清思路

2。代碼的本地化
因為WOW有很多國家的版本。所以一些變數的設置需要本地的語言。
比如能在中國使用的ADDONS,有時候並不能在美國使用。這時候我們就需要做一些本地化的工作

怎麼做?
我們把所有的需要使用當地語言的變數集中起來 在一個LUA檔中定義
(這個LUA檔。我們一般起名叫:localization.lua)
而且。WOW還提供了自動判斷語種的功能
這些都很簡單。隨便打開一個ADDONS的localization.lua自己看一看就明白 不囉嗦了

當然LUA的作用遠遠不止這些
畢竟LUA是一個很成熟的語言。熟練的運用將大大簡化我們的工作量
比如LUA的陣列功能。字串的判斷
更多的細節。可以在自己動手寫ADDONS的過程中慢慢摸索。

繼承

WOW已經幫我定義好了很多有用的框體
所以很多的時候。我們並不需要自己完全的重新定義
直接引用他們就可以了
這裏就要用到繼承這個概念

如何做?
繼承的命令是inherits

比如我想定義一個文字框體。他的樣子和顯示玩家的名字的文字的樣子是一樣的
那麼:
<FontString name="TEXT_FRAME" inherits="GameFontNormalSmall" >
<FontString/>

這樣 我們簡單的用了inherits="GameFontNormalSmall"命令
就把 TEXT_FRAME框體的大小 顏色 透明度 字體等等等等屬性全部搞定了

如果有不滿意的地方 還可以重新定義。
比如改變一下顏色
<FontString name="TEXT_FRAME" inherits="GameFontNormalSmall">
    <Color r="0" g="1" b="0"/>
<FontString/>
現在他就是綠色的咯

當然我們也可以定義自己的
這將大大有助於簡化我們的代碼。
還記得我以前寫的那個OPENDOOR嗎?
我在裏面一共定義了7個框體
其中有6個框體是按扭。並且他們很多部分都是公共的。

所以。如果我現在再來寫那個OPENDOOR 我會先寫一個父框 把6個按扭公共的部分全部寫進去
然後在一個一個的繼承就OK了。~

具體的父框的定義 不囉嗦了
大家可以隨便打開一個ADDONS 找到名字後面為Template的框體。那多半就是父框了。
動手分析一下把
(提示一點:在父框中的$parent就是要被繼承的子框的名字)

獻花 x0