使用 Lua 的简单的魔兽世界插件





5.00/5 (2投票s)
使用 Lua 和魔兽世界 API 制作的战斗信息插件示例
引言
《魔兽世界》是一款客户端/服务器应用程序,部分游戏功能 resides 在服务器端,而另一部分游戏功能 resides 在客户端。大部分服务器端功能涉及由其他玩家共享的游戏环境。客户端功能涉及玩家的用户界面、服务器消息的呈现,以及将玩家行为消息发送到服务器。一些信息会以玩家角色(avatar 或 toon)所在的三维世界的渲染形式呈现,而另一些信息则以文本和/或图形消息的形式显示,以各种方式为玩家提供状态信息。
《魔兽世界》提供了一种机制,允许人们向游戏客户端添加自己的组件,即插件(addons)。大多数插件的创建目的是增强标准用户界面,以提供额外信息。有些插件旨在替换大部分标准用户界面组件,以提供更精简、更紧凑的控件集,或者替换一些标准用户界面组件,以提供更丰富的功能,满足某些活动的需求(例如,帮助玩家进行拍卖的拍卖行插件)。
Blizzard Entertainment 提供的用户界面大部分是用 Lua 编写的,而扩展、增强或替换现有客户端功能的插件也必须用 Lua 编写。存在一个相当庞大的插件程序员社区,有几个网站专门用于管理插件,这些网站为插件开发者提供了丰富的源代码和创意来源。安装的标准用户界面似乎是 Lua 代码的编译版本,但 Blizzard Entertainment 提供了一个工具包,《魔兽世界》界面插件工具包(World of Warcraft Interface Addon Kit),允许用户查看原始的 Lua 源代码和 XML 文件。该工具包可通过 Blizzard 技术支持论坛获取,搜索应能找到最新的链接。
有一些网站以及出版的书籍提供了相当多的信息。有关编写插件的深入讨论,可以查阅相关的书籍和网站。书籍通常提供各种网站所缺乏的讨论深度。另一方面,各种网站在提供 API 具体细节方面相当不错,并且可能提供比书籍更及时的信息。本文的目的是提供一个简单的《魔兽世界》插件示例,为初学者提供一个有指导意义的范例。
背景
用户界面插件大致可分为以下几类:
- 辅助战斗的插件
- 辅助收集(如采矿)的插件
- 辅助与其他玩家互动、管理公会的插件,以及
- 辅助拍卖行和非玩家角色(NPC)商人的插件。
战斗有几种类型,有专门的插件可以辅助以下战斗:玩家对战环境(PvE)战斗(通常是一到五名玩家组成的小队对抗人工智能)、玩家对战玩家(PvP)战斗(通常是更大的玩家群体对抗其他玩家)以及团队战斗(通常是大量的玩家对抗人工智能)。
随着《魔兽世界》游戏的发展,以及许多玩家创建插件来帮助完成特定操作,Blizzard 在了解玩家的游戏行为后,修改了 API 和 Blizzard 提供的用户界面。其中一些更改导致旧的插件不再有用,因为 Blizzard 已将这些功能整合到其客户端中。另一些更改导致旧的插件不再工作,因为 Blizzard 修改了 API,当 Blizzard 确定插件提供的功能与游戏理念相冲突时,这些插件就会失效。
Using the Code
本示例插件的功能简单明了。它只为玩家提供状态信息,这使得源代码更加简洁。显示状态还可以避免处理插件执行操作所需的许多考量。Blizzard Entertainment 的开发者多年来不断修改 API 和可用功能,以提供额外的保护措施,防止插件执行开发者认为应仅由玩家完成的许多操作。由于这是一个信息显示插件,因此用于减少软件代理(software agents)玩角色的可能性方面的规定并不适用。源代码包含大量注释,以帮助他人理解源代码。
这个示例插件是一种战斗信息类型的插件。它显示了一组简单的框架,显示玩家角色、玩家角色的宠物(如果存在)的生命值和法力值(法力、专注、怒气等),以及当前目标(此处的目标是指玩家角色选择的某个其他玩家角色、非玩家角色、野兽或其他游戏内的实体)的生命值和法力值。此插件还显示目标的仇恨等级,这表明目标是否愿意发起攻击。
此插件以及大多数插件的基本过程是:
- 初始化任何全局变量
- 注册玩家进入《魔兽世界》虚拟世界时的第一个事件
- 进入虚拟世界后,使用《魔兽世界》API 中提供的各种控件构建用户界面
- 注册更新用户界面所需的事件,以及
- 在接收到事件时更新用户界面
在本示例中有两个文件:main.lua,包含插件的 Lua 源代码;SimpleCombatHud.toc,包含插件的目录。由于此插件使用 Lua 创建用户界面,因此无需 XML 文件来描述用户界面窗口结构。.toc 文件的名称必须与文件所在的文件夹名称相同,并且这些名称(文件夹名称和 .toc 文件名称)必须与插件本身的名称相同。
插件的命名很重要,因为所有插件以及《魔兽世界》客户端的命名空间都是全局命名空间。程序员必须小心,在插件中使用尽可能少的全局变量,并且他们使用的变量应该使用唯一的名称。这就是为什么插件程序员会使用一个全局名称与其插件相同的 Lua 表变量,然后插件所需的任何持久数据都存储在该表中。次要的全局变量(如设置数据)会使用插件名称的派生名称,以减少与其他任何全局变量名称冲突的可能性。local
关键字对于临时变量很重要,可以减少全局命名空间的混乱,并可能避免与已有的变量冲突。
toc 文件
toc 文件包含一系列条目来描述插件,包括版本号、插件描述以及运行插件所需的文件列表。还可以添加注释,以便程序员向查看 toc 文件的人提供信息。一些插件网站使用 toc 文件中的信息作为向正在寻找特定类型插件的用户呈现的描述性信息的一部分。这些网站可能有特殊指令要求在 toc 文件中使用,以便网站解析以确定插件类型并对其进行分类。
下面是一个 toc 文件前几行的示例。Interface 行指示了插件所针对的《魔兽世界》客户端版本。如果指定的值是早期版本的《魔兽世界》客户端,插件将被禁用,除非玩家选择加载过时的插件。Notes 行提供了在《魔兽世界》插件管理对话框中显示的描述,Title 行上的内容也是如此。
通过在行首使用一个井号(#),可以在文件中添加实际的注释。
## Interface: 40000
## Title: A simple combat HUD
## Version: 1.0.0.1
## Author: Richard Chambers
## Notes: Provides a simple status bar showing the amount of health and
power of the player as a Heads Up Display.
...
toc 文件允许使用两个指令来保存变量,允许插件在玩家下线时保存状态信息,以便下次玩家上线时恢复到之前的状态。保存的变量是基于插件的,意味着这些变量及其值对玩家的所有角色都可用;或者基于角色的,意味着这些变量及其值仅对特定角色可用。
## SavedVariables: SimpleCombatHudDB
## SavedVariablesPerCharacter: SimpleCombatHudPerCharDB
...
main.lua 文件
main.lua 文件包含插件的 Lua 源代码。《魔兽世界》客户端是事件驱动的。存在许多事件和各种事件类型,因此插件必须注册它希望接收的特定事件。每个框架控件都有一个 OnEvent
方法,需要对其进行重写才能接收框架已注册的事件。因此,基本过程是:
- 创建框架
- 添加自己的
OnEvent
处理程序到框架 - 注册框架控件要处理的事件
为了使 Lua 源代码更易于阅读和理解,一种约定是创建一个事件处理程序,该处理程序具有一个简短的源代码主体,用于将事件消息分发到其他函数。事件处理程序被编写为特定事件的函数,事件分派器会检查是否存在该事件的函数。如果框架中存在与事件同名的函数,则事件分派器会调用该函数来处理事件。如果框架中不存在该名称的函数,则忽略该事件。下面使用一个未命名、匿名或 lambda 函数来重写框架的 OnEvent
方法,以将事件消息分发到框架的正确函数,从而显示了一个这样的 OnEvent
处理程序。
-- Create our main table for this addon
SimpleCombatHud = SimpleCombatHud or {};
-- Create the frame that we will use for our events
SimpleCombatHud.frame = CreateFrame("Frame", "SimpleCombatHud", UIParent);
SimpleCombatHud.frame:SetFrameStrata("BACKGROUND");
-- Override the OnEvent() method to dispatch events to our processing functions.
-- Notice first argument is self referring to the frame allowing use of colon (:) notation.
SimpleCombatHud.frame:SetScript("OnEvent",
function(self, event, ...) if self[event] then return self[event](self, ...) end end);
在以上述约定重写了框架的 OnEvent
方法后,我们现在可以通过执行以下两项来处理事件:
- 注册事件,以及
- 提供一个与事件同名的事件处理函数
这样做的一个好处是,在某些情况下,处理事件的函数可以以其他方式重用。在下面的 Lua 代码片段中,我们有一个事件处理函数,在某些情况下会使用另一个事件处理函数。此函数处理 PLAYER_TARGET_CHANGED
事件,通过检查是否存在目标单位。如果存在,则更新目标显示的生命值和法力值。如果不存在,则隐藏目标生命值和法力值显示框架。
function SimpleCombatHud.frame:PLAYER_TARGET_CHANGED()
if (UnitExists("target")) then
self:UNIT_HEALTH("target");
self:UNIT_POWER("target");
else
SimpleCombatHud.statusbar["target"].health:Hide();
SimpleCombatHud.statusbar["target"].power:Hide();
end
end
关注点
玩《魔兽世界》很有趣,虚拟世界广阔而有趣,尽管有时会重复。Blizzard Entertainment 提供的标准用户界面看起来很不错,对于一般游戏来说效果很好,尤其是搭配一些精心挑选的宏。然而,严肃的硬核玩家发现标准用户界面在很多方面都不够用。浏览各种《魔兽世界》插件网站,可以找到数百个处于不同修复和有用程度的插件。有些插件很小很简单,就像这个例子一样,而有些则很大很复杂,例如插件 Deadly Boss Mods,它通过警告为玩家提供了极大的帮助。
编写《魔兽世界》插件面临的问题是文档的缺乏。已经出版了许多书籍,有大量的源代码示例,以及多个论坛供人们提问和获得帮助。然而,文档的缺乏阻碍了插件程序员,尤其是在《魔兽世界》编程环境也在不断变化的情况下。随着开发团队通过补丁和扩展进行更改,插件必须适应这些更改。插件的大部分呈现内容基于各种事件,以及程序员如何使用 Lua 语言和《魔兽世界》API 将这些事件转化为呈现的信息。插件作者必须意识到,具有特定标签的事件可能并不与其表面上的关联相符。并且在《魔兽世界》早期版本中使用的事件在后续版本中可能不再有用。各种网站可以帮助确定什么是有用的,什么不是。那些随着新扩展和补丁的发布而得到维护和升级,并且被大量《魔兽世界》社区使用的插件,是插件程序员的有用资源。
在开发过程中,您很可能需要将变量和信息打印到聊天区域,以便测试发生了什么。一个非常简单的 debug
打印语句,如以下示例,可以帮助打印信息。在示例插件的许多地方,我使用了这个 Debug
函数来检查值并了解情况。在那些使用的地方,我将该函数注释掉了,以显示如何使用它来查看可能导致问题的根源。只需确保在源代码的某个区域使用完毕后删除调试调用。这不仅会造成严重的性能问题,还会给玩家带来巨大的困扰。大多数玩家希望插件只做其功能,不多做。
local Debug = function (str, ...)
if ... then str = str:format(...) end
DEFAULT_CHAT_FRAME:AddMessage(("Addon: %s"):format(str));
end
在开发和维护过程中对插件进行游戏测试是必须的。虽然 Blizzard Entertainment 提供了一个免费下载和试玩版的《魔兽世界》客户端,但存在一些限制,其中之一是禁止使用插件。所以,如果您有兴趣编写《魔兽世界》插件,您将需要成为一名订阅者。游戏测试需要包括所有不同的职业在各种情况下进行。在着手构建自己的插件之前,您应该先寻找类似的插件。很有可能您可以直接使用该插件的源代码,或者稍作调整即可实现您想要的修改。如果这些是有效的 mod,请联系插件的开发者,也许他们会为整个社区采纳您的修改。
使用此插件进行游戏测试的一个例子是,将其用于有宠物和没有宠物的角色。游戏测试以及 Debug 函数的使用使我能够收集各种体验,以检查插件是否提供了相当准确的信息显示。我需要在一系列环境中,使用各种职业来测试插件,然后调查异常行为。其中一种行为是,当选择某些非玩家角色(NPC)作为目标时,目标的法力值显示不正确。结果发现这是由于某些 NPC 的最大法力值是零,这在原始源代码中最终导致了除零错误。
热门插件的开发者通常会有一个机制,让那些觉得插件有用的人向开发团队捐款。Blizzard Entertainment 有一个插件政策,其中描述了一些关于插件的基本“可做”和“不可做”的事情。如果您访问《魔兽世界》网站并搜索“插件政策”,您应该能够找到它。基本原则是:
- 您不能为此收费,但可以接受捐款
- 插件不得包含广告(包括捐赠广告)或令人反感的内容
- 源代码必须公开可见且可供检查,并且
- 插件行为不得对游戏产生不利影响,且必须遵守使用条款和许可协议。这基本上意味着,对于大多数人来说,编写《魔兽世界》插件将是一项有趣且有益的努力,而不是赚钱的方式,至少不能在官方插件政策允许的范围内。
历史
- 2011 年 11 月 25 日 - 更新了 main.lua 文件,以纠正发现的缺陷,即在区域变更后,显示的生命值和法力值可能过时。