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