Login

程序员快速入手指南

作者:炽火 Zero ℃

原文发表于:猫扑魔兽板块实用资料区

引言

本文将以一般的软件编程的眼光审视魔兽插件。剥去其难解的外皮,让你有个清晰的大致了解。

(!) “一针见血”是技术性教学文章的最高境界 —— 炽火

魔兽插件的架构

插件工程 —— 每个插件就是一个项目(或称为工程)。目录位置: 魔兽目录\Interface\Addons\项目名\
工程描述 —— 即后缀名为Toc的文件。描述工程的必要信息,也是项目载入时的总入口。
屏幕布局 —— 布局描述使用XML文件。使用各种标签代表特定对象(控件),并描述相互位置及各种特征属性。
功能脚本 —— 脚本文件使用LUA语言。使用WoW提供的API函数编写代码实现各种具体操作。

TOC —— 工程描述文件

后缀名为toc的文件就是工程文件,包含工程的基本信息。主要包含以下内容:

   1 ## Interface: 适用的魔兽版本号
   2 ## Title: 显示的标题(默认语言)
   3 ## Notes: 显示的说明(默认语言)
   4 ## Title-zhCN: 特定语言的标题(简体中文)
   5 ## Notes-zhCN: 特定语言的说明(简体中文)
   6 ## Author: 作者(不显示)
   7 ## Version: 版本
   8 ## eMail: 如题
   9 ## UIType: 插件类型
  10 ## Dependencies: 依赖的插件
  11 ## RequiredDeps: 必须依赖的其他插件
  12 ## OptionalDeps: 可选倚赖
  13 ## SavedVariables: 统一存放的变量
  14 ## SavedVariablesPerCharacter: 按角色存放的变量
  15 ## LoadOnDemand: 1 (调用时加载)
  16 ## LoadWith: 当指定插件加载时才加载,前提是调用时加载
  17 ## DefaultState: disabled  默认状态
  18 ## Secure: 安全(功能未知)
  19 # 注释1 dklasjfkasdj
  20 Script.lua -- 脚本文件
  21 % 注释2 dskajfklasdjfklsdaj
  22 Layout.xml -- 布局文件
  • ‘#’ 和 ‘%’ 为行注释符号。

  • 而以##开头的注释行可就不光是好看的了,这些都是插件的工程描述标记,要是不注意插件根本就不会载入。

  • 示例中彩色的标记表示标准标记,每个标准标记都有特定功能。其它eMail, Author等为扩展标记,只有注释功能。

  • 工程标记之后的部分为包含文件列表,脚本文件和布局文件,数量随意,但每种至少要有一个:一个lua或一个xml。

(!) 包含文件列表中的顺序对应着载入顺序,非常要紧!!!!
(!) 如果要写中文,一定要将toc文件保存为UTF-8格式,否则无法正确识别。方法很简单:用记事本打开,另存为,选择编码就行;文件格式要选所有文件,否则会存成txt文件。

建议你多下几个插件,打开看看,参考一下。

XML —— 屏幕布局描述文件

在魔兽插件中使用XML来描述UI的屏幕布局。

  • 注释符号:<!-- 注释内容 --> 。

  • 如果要写中文注释,必须在XML文件开头添加一行:<?xml version="1.0" encoding="UTF-8"?>

XML文件主要内容

一个XML文件往往包含以下内容:

<!-- Ui标签是包含所有其它标签的根标签 -->
<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/
C:\Projects\WoW\Bin\Interface\FrameXML\UI.xsd">
        <Script file="localization.lua"/> <!-- 在此载入用于汉化的lua文件 -->
        <Frame name="zBar" parent="UIParent"> <!-- 一个Frame框架 -->
                <Scripts> <!-- 脚本部分,用于处理事件 -->
                        <OnLoad>
                                zBar_OnLoad();
                        </OnLoad>
                        <OnEvent>
                                zBar_OnEvent(event);
                        </OnEvent>
                </Scripts>
        </Frame>
</Ui>

Ui标签

<Ui>是最上级标签。其中的schema用来语法检查,如果你的XML编辑器支持还可以用来代码提示。否则对我们来说用处不大。可以简化为:

<Ui xmlns="http://www.blizzard.com/wow/ui/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
</Ui>

或者更加简化,不太规范但是可用:<Ui>布局内容</Ui>

Script标签

在Ui标签中最先包含的往往是<Script file="lua 文件名"/>表示在此处载入某lua文件。
注意这里也有顺序哦,如果发现变量、函数不存在,检查一下看是不是顺序问题。

(!) 这样一来我们有了两种方法载入lua文件。在xml中载入lua实际上不是非如此不可的,有时候没必要用xml的就直接在toc中放个lua,把xml省掉。这样做你必须在lua中有个入口,也就是有个裸语句(不在任何function中的)进行事件注册或者调用函数。

Frame标签

XML中最主要的一个标签是<Frame></Frame>。所谓Frame意指框架,就是用来包含其他东西的。
UIParent是最上层的框架,所有的UI都包含在其中。与之并列的是一个特殊框架World Frame,包括FPS、截图。
其它控件一般都是Frame的子类,都包含类似的属性和子标签:背景、贴图、边框、事件处理。如:<button> <slider> <statusbar> 等等。

(!) Frame标签就像一个透明的玻璃容器。你可以仅用它来装载其它部件,当然也可以给他添加贴图显示出来。
(!) 记得游戏中按下“Alt+z”的效果了么,被隐藏的就是UIParent。容器被隐藏了,里面装的东西也就透明了。

子标签和属性

<Frame name="名称,唯一" inherits="myFather继承" toplevel="true等级" parent="UIParent父标签" id="编号"
 movable="true可移动" enableMouse="true允许鼠标事件" frameStrata="HIGH层级" hidden="false隐藏" >
        <Size> <!--大小 -->
                <AbsDimension x="" y=""/> <!--绝对尺寸 -->
        </Size>
        <!-- 简化写法 -->
        <Size x="" y=""/>
        <Anchors> <!--锚点,用来标记相对位置 -->
                <Anchor Point="本控件的参考点" relativeTo="参考控件" relativePoint="参考控件的参考点">
                        <Offset> <!-- 偏移值 -->
                                <AbsDimension x="" y=""/>
                        </Offset>
                        <!-- 简化写法 -->
                        <Offset x="" y="" />
                </Anchor>
        </Anchors>
        <!-- 背景和边框 -->
        <Backdrop name="$parentBackdrop" bgFile="背景贴图" edgeFile="边框贴图" tile="不知道">
                <EdgeSize>
                        <AbsValue val="18"/> <!-- 边框缩放大小 -->
                </EdgeSize>
                <TileSize>
                        <AbsValue val="16"/>
                </TileSize>
                <BackgroundInsets>
                        <AbsInset left="5" right="5" top="5" bottom="5"/> <!-- 背景四周留白 -->
                </BackgroundInsets>
        </Backdrop>
        <Layers> <!-- 分层,主要放置贴图材质和字符 -->
                <Layer level="BACKGROUND"> <!-- 背景层 -->
                        <Texture name="$parentRed" file="贴图" hidden="true"/>
                </Layer>
                <Layer level="ARTWORK"> <!-- 艺术层 -->
                        <FontString name="$parent名字" inherits="Font" justifyH="CENTER"/>
                <Layer level="OVERLAY"> <!-- 覆盖层 -->
                        <Texture name="$parentShine" file="贴图" alphaMode="ADD透明模式" hidden="true">
                                <Anchors>
                                        <Anchor point="CENTER"/>
                                </Anchors>
                        </Texture>
                </Layer>
        </Layers>
        <Frames>
                 <!-- 嵌入其他框架 -->
        </Frames>
        <Scripts>
                <OnLoad>处理载入事件</OnLoad>
                <OnEvent>处理用RegisterEvent注册过的事件</OnEvent>
                <OnUpdate>更新事件</OnUpdate>
                <OnShow>显示事件</OnShow>
                <OnHide>隐藏事件</OnHide>
                <OnClick>单击事件</OnClick>
                <OnDoubleClick>双击事件</OnDoubleClick>
                <OnEnter>鼠标进入</OnEnter>
                <OnLeave>鼠标移出</OnLeave>
                <OnDragStart>拖放开始</OnDragStart>
                <OnReceiveDrag>接受拖放</OnReceiveDrag>
                <OnValueChanged>值改变(用于Slider滑块)</OnValueChanged>
        </Scripts>
</Frame>


  • inherits 和 parent 的区别

    inherits

    继承,表示本控件未标明的属性全部继承自某某控件(包括事件处理脚本)。

    parent

    父控件,表示依存关系,如果父控件隐藏了那么子控件也会隐藏。


  • Anchor锚点中的定位规则

    Point

    自身参考点

    relativeTo

    参考控件

    relativePoint

    参考控件参考点

    比如一个正方形的按钮,<Anchor Point="LEFT" relativeTo="$parent" relativePoint="RIGHT"/>
    意思就是说:我的左边对父控件的右边。效果就是按钮始终在父控件的右边。

UI对象的继承关系

UIObject
始祖,所有其他UI对象都由它继承。
LayoutFrame
布局框架,继承UIObject,主要有一些布局函数,(这是一个虚类不可创建实例)。

LayoutFrame的重要函数:GetParent() SetParent() GetAlpha() SetAlpha() SetWidth() SetHeight()

  • IsShown() 和 IsVisible() 的区别

    IsShown()

    是否显示,表示一种属性:如果父对象可见的时候是否会显示。

    IsVisible()

    是否可见,表示当前状态:当前对象在屏幕上有没有显示。

Frame

  • 框架,继承Layout Frame,最重要的UI对象,屏幕上可见的UI对象都是它的子类,一般用来在上面放置其他控件。
    Frame的子类;描述
    Button;按钮。
    EditBox;可编辑文本框。
    GameTooltip;鼠标提示
    ColorSelect;颜色选择器
    MessageFrame;信息框架
    Minimap;迷你地图,小地图、战场地图等
    Model;模型,用于显示3D动画。人物模型,冷却都是Model
    MovieFrame;影像框架,放电影的。。。不知道怎么用
    ScrollFrame;可滚动框架
    ScrollingMessageFrame;滚动信息框架,例如聊天窗口、战斗信息窗口
    SimpleHTML;简单超文本标记语言,应该是类似网页的可定义字体大小颜色式样、带图片的文本
    Slider;滑快,像系统设置中的UI缩放那个滑快
    StatusBar;进度条,像是施法条,血条等等

LUA —— 脚本代码文件

(!) 更深入的学习请阅读本站的其它文章:ProgrammingInLua, 魔兽Api索引

如果你有一门以上的语言基础,那么学习lua语言不是什么难事。

lua是一种灵活的语言,关于他的来源我并不知道多少,只谈一下自己的感受。基本语法和Basic类似,或者说更像是Asp的脚本语言吧。但同时他又借鉴了Java的长处,更加的智能化、灵活。写法多样又方便不同习惯的人上手。

lua语法很简单,相关资料也很多,我只列出需要注意的部分。

基本语法摘要

  • 行注释符号:“--”

  • 段注释符号:“--” “”;实际上还是行注释,而“” “”包围的部分编译器会认为是一行。

  • 行结束符号:用“;”或者不写都行,看你的习惯了,只要没有歧义就好

  • NULL空值:在这里是nil

  • 没有指针:和Java一样,没指针的意思就是所有变量都是引用。要注意不是拷贝的副本,改变引用变量原始变量会同时改变。

  • 变量作用域:默认是global(全局作用域),加上local前缀就是局部的了(文件内部作用域)。

  • 函数作用域:和变量一样默认是全局作用域。实际上函数也是变量。

  • 数组,集合:只有一种,类似于。。。我也不知道类似什么。写法也很随意,举例:

    Config = {
        attribute1 = "apple",
        attribute2 = false,
        ["attribute3"] = "animal",
        [1] = "Button1",
        ["2"] = "Cool",
    }
    关于数组的就看上面的“Lua脚本语言入门”,讲的很清楚。
  • 关于布尔变量:要注意的是只有nil和false表示false,其他一律为true(包括0),而false是要占用引用内存的,建议把false一律换成nil。

  • 关于布尔运算:lua里的布尔运算是很有趣的,也相当合理。注意运算的值并不是布尔值,而是其中一个操作数的值。

        x = a and b; -- 与运算,a为false则x等于a,a为true则x=b。
        x = a or b; -- 或运算,a为true则x等于a,a为false则x = b。
    是不是有点晕?其实很合理。如果你一时绕不清楚,就只要记住结果好了。
  • 布尔运算的妙用:我坚定地认为lua中的布尔运算实际上不是布尔运算,而是简化了的条件语句。比如:

    x = a and b;  --相当于:
    if (not a) then
      x = a;
    else
      x = b;
    end
    
    x = a or b;  -- 相当于:
    if (a) then
      x = a;
    else
      x = b;
    end
    
    -- 还有一种简化,常用于变量的初始化:
    x = x or a;   -- 相当于:
    if ( not x ) then
      x = a;
    end

对象

在lua里,框架和控件都作为对象存在。 要引用在XML中定义的框架或控件,使用如下语句:

frame = getglobal("FrameName");
对象的属性

属性在对象中就像集合成员一样,属性引用符号为:“.”,例如:frame.attribute1

对象实际上就是一个特殊集合!!

对象的属性同样可以用集合方式引用: frame["attribute2"]。函数调用也是一样,见下面说明。

函数

函数定义
类似Basic,例如:
function add(a,b)
  local ret = a + b;
  return ret;
end
覆盖函数
函数定义实际上是把一个变量指向一段代码。前面举的例子含义是:把变量add指向一段函数代码。 因此,我们可以很方便的覆盖(Override)Blizzard的系统函数,只要把系统函数变量指向自己定义的函数代码就可以了。 以覆盖上面的add函数为例,有几种方法:
-- 1. 把原函数变量指向新的函数代码段
old_add = add;  -- 保存原始的add指向的函数
add = function(a,b)   --  add 指向新的代码段
  if ( old_add(a,b) == 0 ) then return nil end
  return a + b
end
-- 2. 直接定义一个同名函数
function add(a,b)   -- 含义与第一种方法相同:改变add的值,使它指向新的代码段
  local ret = a + b
  if ( ret == 0 ) then return nil end
  return ret
end
对象的函数

函数在对象中有些特别,调用符号为冒号:“:”,例如:frame:Show();

函数实际上是一种特殊变量
它指向一段代码,更一般的调用符号和一般的对象属性相同,为 “.”。 而特殊符号“:”实际上是在调用的时候多加了一个参数“self”,指代调用者本身。例如:
Frame1:SetAlpha(0.5)
-- 相当于
Frame1.SetAlpha( Frame1, 0.5)
函数的默认参数
有一些函数参数比较特殊:对象事件处理函数,有一些有默认参数,例如
OnClick OnMouseDown OnMouseUp;默认参数 arg1
OnEvent;默认参数event
所以在定义这些函数的时候可以不写参数:
function zOnClick()
  if ( arg1 == "LeftButton" ) then
      doSomething()
  end
end

/!\ 但是要注意:如果函数定义里写了参数,那么调用函数的时候一定要写参数!!!!否则认为参数为nil。例如:

<OnClick>if ( not this.lock ) then zOnClick(arg1) end</OnClick>
之所以有默认参数存在是因为在动态改变事件处理函数的时候不能写参数,例如
button:SetScript("OnClick", zOnClick)
默认参数的原理

经过多方考证,这些默认参数均为全局变量。
实现默认参数得流程为:对象触发事件时会调用对应函数,在其中将全局变量即默认参数赋值。
例如xml中有如下描述:

<OnClick>zOnClick()</OnClick>
虽然不知道WoW虚拟机确切的处理方式,但我们可虚拟如下:
-- 事件触发时调用对象的事件处理函数
Object:OnClick("LeftButton")

-- 该处理函数定义模拟如下
function Object:OnClick(arg1)
    zOnClick()
end

-- [[
由于arg1 为全局变量,对象被调用时 arg1 被赋值为"LeftButton"
其后Object:OnClick()中又马上调用了zOnClick()插件处理函数,
其中并无被改变的机会,因此 arg1 被成功传递,看起来就像是默认参数了。
-- ]]

(!) 默认参数还包括 this,但是 self 不是全局变量。


插件开发资料类, 炽火

程序员快速入手指南 (last edited 2007-12-29 07:53:47 by localhost)