Tag Archives: Tutorial

魔兽世界插件开发入门教程——3

一周就这么过去了
DTP居然没有面试
不知道出了什么问题
毕设也还没开始弄
此刻这个教程估计要真挖出大坑了
我尽量整体介绍完
细节部分如果你想了解
留言或者给我Email
我看到会尽早回复

本节目标

前两节我们已经实现了这个插件的最基本界面
这一节我们开始将插件功能的核心实现——Lua文件的编写
请建立一个QuestRepeat.lua文件,并加入到第一节的Toc文件最后的文件列表中

本节的代码你可以在此获得
[—-请猛击我—-]

代码讲解

 

QuestRepeat.lua整体结构

function initialize()
--插件一些变量的初始化
end

function QuestRepeat_OnLoad()
--插件框体加载成功事件的响应处理函数
end

function QuestRepeat_OnEvent()
--插件注册事件的处理函数
end

function Item_Check(name)
--计算角色包里以及货币栏里物品的数量
end

function QuestRepeatAllButton_OnClick()
--全部完成按钮的响应函数
end

function QuestRepeatCompleteButton_OnClick()
--完成一次按钮的响应函数
end

function QuestRepeatDecrement_OnClick()
--文本框数值减少按钮的响应函数
end

function QuestRepeatIncrement_OnClick()
--文本框数值减少按钮的处理函数
end

function QuestRepeatInputBox_OnTextChanged()
--文本框数值越界处理
end

上面我们可以了解到整个Lua的大致结构
各个函数的具体作用注释都说的很清楚

回忆下这个软件的功能
是要可以快速完成重复上缴物品的任务

那Lua文件至少要干三件事情:

1.检测NPC给的任务是不是可重复完成任务
2.如果是重复任务,检查我们的包裹里是否有足够的物品可以重复上缴,假如只能交一次,我们就没使用插件的必要
3.满足重复上缴的条件,我们怎样才能快速完成任务

实际上,即使上述3个事情我们都考虑好了,细节上还是有些麻烦的地方,比如:

4.任务上缴的过程中,如何中断
5.任务最后要完成或者用户选择中断的时候,如何初始化插件,恢复最初的状态
6.要是任务要求的不是我们包裹里的东西,而是货币面板上的虚拟代币我们要如何判断数量呢

在此基础上,考虑与上两节的界面的操作处理:

7.我们如何处理各种按钮响应,文本框的数值超过我们最大能上缴任务的次数时候要如何处理

也就是说,解决了这7个问题,最初级版本的QuestRepeat就完成了。
不过,这是考虑的时候才会分解成这样的几个问题,实际编写的时候,有时候一个代码块就可以一起解决掉几个问题
我们对相关联的问题放一起进行解答

问题1/2/6

function QuestRepeat_OnLoad()
	initialize();
	this:RegisterEvent("GOSSIP_CLOSED");
	this:RegisterEvent("GOSSIP_SHOW");
	this:RegisterEvent("QUEST_COMPLETE");
	this:RegisterEvent("QUEST_PROGRESS");
	--将下面这句注释取消可以测试进入游戏时插件是否加载成功
	--DEFAULT_CHAT_FRAME:AddMessage("QuestRepeat Enabled(Debugged by Tyreal)");
end

function QuestRepeat_OnEvent()
	--[[..这里省略与问题无关部分,请看源文件..--]]
	elseif (event == "QUEST_PROGRESS") then
		if (IsQuestCompletable() and iterator == nil) then
			n = GetNumQuestItems();
			i = 0;
			if (n ~= nil) then
				if (n ~= 0) then
					MAX_VALUE = 999;
					while i < n do
						i = i + 1;
						local name, texture, numItems, quality, isUsable = GetQuestItemInfo("required",i);
						value = floor(Item_Check(name)/numItems)
						if (value < MAX_VALUE) then 							MAX_VALUE = value; 						end 					end 				end 			end 			this:RegisterEvent("PLAYER_TARGET_CHANGED")--为上缴中断做准备,注册角色目标切换事件,后面解释 --以下代码是问题3的一部分 		elseif(iterator) then 			if (iterator > 0) then
				QuestProgressCompleteButton_OnClick();
			end
		end
--[[...--]]
end

--计算包里物品的数量
function Item_Check(name)
	Count = 0;
	for bag = 4, 0, -1 do
        local size = GetContainerNumSlots(bag);
        if (size > 0) then
		--遍历包裹的东西
		for slot = 1, size do
                local texture, itemCount = GetContainerItemInfo(bag, slot);
                	if (itemCount) then
		    	local itemLink = GetContainerItemLink(bag,slot)
		    	local _, _, itemCode = strfind(itemLink, "(%d+):")
		    	local itemName, _, _, _, _, _ = GetItemInfo(itemCode)
 		    	--判断是否包含该物品
	 	    	if (itemName ~= "" and itemName ~= nil) then
                        	if (itemName == name) then
                            		Count = Count + itemCount;
                        	end
                    	end
                	end
            	end
        end
    	end

	--TBC之后有些任务上缴需要货币,此处查询角色的货币数量
	local scrollFrame = TokenFrameContainer;
	local offset = HybridScrollFrame_GetOffset(scrollFrame);
	local buttons = scrollFrame.buttons;
	local numButtons = #buttons;
	local itemName, itemCount;
	local index;
	for i=1, numButtons do
		index = offset+i;
		itemName, _, _, _, _, itemCount = GetCurrencyListInfo(index);
		if (itemName == name) then
                	Count = Count + itemCount;
            	end
	end
    return Count;
end
  • 当我们选择了一个任务,进入任务说明页面,可以看见任务要求物品列表的时候,WoW会抛出一个QUEST_PROGRESS事件
    所以我们需要注册这个事件,可以在QuestRepeat_OnLoad()看到
this:RegisterEvent("QUEST_PROGRESS");--注册捕获QUEST_PROGRESS事件
  • 然后看QuestRepeat_OnEvent()中间的部分,我们会判断捕获的事件是不是QUEST_PROGRESS,是就进行处理
    最开始是判断我们是否有足够的物品,这是靠Item_Check(name)来完成的,后面的一点代码是给后面用
  • Item_Check(name)这是自己写的函数
    前半部分是检查包裹里物品数量
    后半部分是检查货币面板,这是对问题6的解答
    最后返回符合查询物品的数量,如果用户含有的数量大于任务要求数量,就可以重复上缴,这样问题2就完成了
  • 那问题1呢?其实WoW中的任务,只能交一次的任务,你几乎是不会拥有要求物品的数量达到可以交2次的时候,所以,当我们问题2帮我们算好了数量,知道可以交超过2次以上,我们就知道这是个可重复任务,问题1也就搞定了

实际上WoW也有类似的API实现这样的功能,你可以去搜索WowProgramming和WowWiki,不同的方法,一样的结果
WoW更新版本的时候,有可能会开放新的API或者封掉一些不合理的API,所以写插件的时候请留意自己用的方法会不会太bug,免得到时候要重新修改,虽然有时候循规蹈矩的,WoW大改的时候几乎大家都得改,比如2.0时代。

请学会查看WowProgramming和WowWiki这样的网站上的API说明,返回值,参数列表,作用,这些都是日后的基本功

DEFAULT_CHAT_FRAME:AddMessage(“”)
可以在WoW的聊天窗口输出字符
我们后面会用到这个来输出一些变量的值,用来调试插件
不要怀疑,就是这么原始
若想要高端开发环境,去看In-Game Addon Studio,不过这个跟我此刻讲解的开发流程就是两回事了,你可以了解看看

问题3/5

要快速完成任务
我最早的设想,设定完成的次数,点击一次“全部完成”,插件就不停的自动点击完成
可是这样的设计是违反BLZ的插件开发原则的
因为这样等于是类似外挂自动完成的功能
这么搞是不太安全的
何况BLZ根本就没开放自动选择NPC这个API(我已经翻烂了相关的手册,真没有,有的是BLZ自己官方插件才可以使用的API)
所以这是无法实现的

那退一步,NPC让玩家手工选择
但是任务的各步骤中的点击按钮插件来自动完成
查查API后,发现这是可行的
我也是基于这个思想来完成的

一起来看下代码

function QuestRepeat_OnEvent()
--[[..老规矩,省略问题无关部分..--]]
		elseif(iterator) then
			if (iterator > 0) then
				QuestProgressCompleteButton_OnClick();
			end
		end
	elseif (event == "QUEST_DETAIL") then
		if (iterator) then
			if (iterator > 0 ) then
				QuestDetailAcceptButton_OnClick();
			end
		end
	elseif (event == "QUEST_COMPLETE") then
		if (MAX_VALUE > 1) then
			QuestFrameCompleteQuestButton:Hide();
			QuestRepeat:Show();
			if(GetTitleText() == questName) then
				if(iterator) then
					if(iterator > 0)then
						QuestRepeatInputBox:SetNumber(iterator);
						QuestRepeatCompleteButton_OnClick();
					else
						initialize();
					end
				end
			else
				QuestRepeatInputBox:SetNumber(1);
				questName = GetTitleText();
			end
		else
			initialize()
			QuestFrameCompleteQuestButton:Show()
			QuestRepeat:Hide()
		end
	elseif (event == "GOSSIP_SHOW") then
		if (iterator) then
			if (iterator > 0) then
				if GetGossipAvailableQuests() then
					i = 1;
					for index, value in pairs({GetGossipAvailableQuests()}) do
--测试输出数据
--[[
DEFAULT_CHAT_FRAME:AddMessage("i="..i);
DEFAULT_CHAT_FRAME:AddMessage("index="..index);
DEFAULT_CHAT_FRAME:AddMessage("value="..value);
DEFAULT_CHAT_FRAME:AddMessage("questName="..questName);
DEFAULT_CHAT_FRAME:AddMessage("-------------------");--]]
						if( mod(index-1, 3) == 0 ) then
							if (value == questName) then
								SelectGossipAvailableQuest(i);
								break;
							end
						i = i + 1;
						end
					end
				end
			end
		end
	end
end

这段代码中,有个很重要的变量iterator,它控制着是否进行不间断的任务自动上缴,大于1时重复上缴,小于1停止

  • 刚最开始跟NPC对话的时候,会有个GOSSIP_SHOW的事件,我们判断是否进行QuestRepeatCompleteButton_OnClick()重复点击按钮进入下一步,如果是的话我们在这事件之后获取NPC存在多少个任务GetGossipAvailableQuests()并根据以前存储的questName自动选择对应的任务,不是就什么都不干,玩家手工选择任务
  • 接着QUEST_DETAIL事件,依旧判断iterator的值来进行自动按按钮
  • 然后是QUEST_PROGRESS事件,这主要是问题1/2/6的部分,但是最后面一直延续对iterator的判断
  • 最后是QUEST_COMPLETE事件,判断可完成的最大次数是否为1,为1就回复WoW默认完成界面并initialize()初始化插件,这里就要解决问题5;如果不是就判断是不是还未启动自动上缴过程,还未启动就记录任务名称,已经启动就继续显示本插件,并自动点击按钮。至此,问题3也解决了。

initialize()的代码很简单,就初始化3个变量

function initialize()
	MAX_VALUE = 1;--玩家可以完成任务的最高次数
	iterator = nil;--是否自动上缴任务控制变量
	questName = "";--自动进行的任务的名称
end

问题4

如果你认真研究完以上代码,差不多对整个流程有个印象了
接下来要解决的就是中断自动完成的问题
因为有时候我们交到一半就不想交了
有个中断的方法是必要

我后面的QuestRepeat有新的面板可以方便控制任务中断过程
关掉那个面板就等于退出本插件
但是现在这个最初级的版本没有那个面板
所以设置玩家目标改变的时候就取消自动上缴过程

function QuestRepeat_OnEvent()
--[[...--]]
	elseif (event == "PLAYER_TARGET_CHANGED") then
		initialize();
		this:UnregisterEvent("PLAYER_TARGET_CHANGED")
--[[...--]]

QuestRepeat_OnEvent()里面完成了很多事情,中断处理也是在这里完成的
由于我们一开始有注册了PLAYER_TARGET_CHANGED事件
所以这里响应之后我们可以调用initialize()进行插件状态初始化
然后注销事件PLAYER_TARGET_CHANGED,因为此时我们已经不需要它了

当然留着也不是不可以
只是这样游戏过程中你每切换一次目标,这里都会来初始化一次插件
没什么影响
丑陋代码,不严谨就是了

最后的问题7,相信你忍着看到这里,也可以自己看源代码解决了

最后唠叨

读完这三节,基本上你就可以使用Blizzard给的官方API加上Windows自带的记事本来编写插件了,至于有些插件用ACE之类的那些库编写的,都是在此基础上的衍生,具体资料可以自己查找

最近写这个教程的时候,终于知道什么叫心有余而力不足,事说多不多,但是时间总是感觉流失得太快,没有太多的耐心把这最后一节好好写仔细,只是草草做个了概括,而且这份代码是我最早写插件的时候从原作上修改的,并不是太规范,所以本教程如果说得不好的地方还请见谅。

有问题请留言,日后若有空,再重新翻新续写也不一定。

Tagged , , ,

如何为SyntaxHighlighter编写自己的Verilog Brush

这几天为了找个合适自己的代码高亮插件

由于自己过于龟毛

所以试用了绝大部分网上推荐的插件

一一尝试了之后

最后决定试用SyntaxHighlighter Evolved

至于之前WP-Syntax

老实说,这个的确很棒,代码支持列表也完全足够绝大部分开发者,包括电子开发人员,默认自带verilog等,只是颜色搭配很一般,加上不知道是不是我自己没设置好,我装了这个之后,

<pre>标签里面的内容都不能居中对齐。

还有另外一个……我忘记名字了,换掉原因多是因为颜色搭配以及排版问题

虽然SyntaxHighlighter Evolved也不见得就是完美

我装了TinyACE Advanced,并选择保留不删除<br />等标签,因为自己习惯手工打回车空行

但是这个时候贴在Evolved里的代码

这些标签就都不会删掉,每行代码都会多出一个这个<br />……

只好最后又禁用掉那个选项

求完美解决方案,或者告诉我怎么胡乱打空行不被wordpress吃掉就可以……

既然要使用SyntaxHighlighter Evolved

这个插件外观漂亮是漂亮,但是支持的语言实在不够

想着日后我需要贴Verilog HDL代码

网上又遍寻不到

于是就自己写了个

以下附上此次编写过程并给点资料方便大家自己写个

准备工作

虽然我们要写个JavaScript的脚本文件,但是实际上,你现在不必去学它就可以写出这个东西了

你唯一可能需要了解的一个东西就是Regular Expression,也就是正则表达式

这个讨厌又很神奇的东西,花个30分钟学学吧,基本的应用蛮快学的

以下是各种推荐资料,先记下,后面需要再回来看吧

 

  • 中文正则表达式教程http://deerchao.net/tutorials/regex/regex.htmhttp://www.cyut.edu.tw/~ckhung/b/gnu/regexp.php
  • 英文教程http://www.regular-expressions.info/
  • 你可以在这里进行在线测试,看看你的正则表达式到底合不合用http://www.gskinner.com/RegExr/

从零开始

请新建一个js文件,我这里取名为shBrushVerilog.js,只是为了跟插件的其他笔刷有个相同的命名规则,整齐而已

1.编写shBrushVerilog.js文件

SyntaxHighlighter.brushes.Verilog = function()
{
	var datatypes = '';//这里的具体代码请看源文件
	var keywords = '';//每种不同语言的关键字都不一样,需要参考语言手册
 	this.regexList = [
	{ regex: SyntaxHighlighter.regexLib.singleLineCComments,	css: 'color1' },			// one line comments
	{ regex: SyntaxHighlighter.regexLib.multiLineCComments,		css: 'color1' },			// multiline comments
	{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,		css: 'string' },			// strings
	{ regex: SyntaxHighlighter.regexLib.singleQuotedString,		css: 'string' },			// strings
	{ regex: /^ *`.*/gm,						css: 'preprocessor' }
	{ regex: /\b\d+\'?[oObBdDhH]?[0-9xXzZA-Fa-f]*/g,		css: 'value' }
	{ regex: /\$\w+/g,						css: 'functions bold' }
	{ regex: new RegExp(this.getKeywords(datatypes), 'gm'),		css: 'variable bold' }
	{ regex: new RegExp(this.getKeywords(keywords), 'gm'),		css: 'keyword bold' }
	];
}
SyntaxHighlighter.brushes.Verilog.prototype = new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes.Verilog.aliases = ['verilog'];

其实结构没什么好讲解的

你偷懒的话可以复制粘帖这一份代码然后进行修改

需要注意的地方是this.regexList之后的那些正则表达式对象需要根据语言的特性进行修改

默认风格的颜色参考请看这里:

  • comments, keyword, string, preprocessor, variable, value, functions, constants, script, color1, color2, color3

我的代码中

7-8行的注释因为verilog与c相同,所以可以直接这么套用原有的

9-10的string类型也有可以直接复用的代码

关键的是第11-13行

这里我只解释第13行的预处理字符中的正则表达式

regex: /^ *`.*/gm

  • ^ 意思是开头有可能含有0-无数个空格(记住^之后有空格哦)
  • 然后接着一个`字符,这是verilog中的预处理的字符
  • 之后的.*说明预处理字符之后含有0-无数个字符
  • g的意思是递归整个文件的匹配搜索
  • m的意思是多行匹配,如果不加上这个,比如:
    `timescale
    `timescale
    

    第二行是无法被匹配的

更多知识请自学正则表达式

我们写好这个文件之后,就可以把它加入到我们的插件中去

请编辑你的SyntaxHighlighter Evolved,在WordPress插件安装列表那里点击它的Edit选项

2.在一堆wp_register_script中间插入以下这句来注册我们新弄好的笔刷

wp_register_script( 'syntaxhighlighter-brush-verilog',
	plugins_url('syntaxhighlighter/third-party-brushes/shBrushVerilog.js'),
	array('syntaxhighlighter-core'),
	'20100420'
);
  • 函数的第一个参数是我们注册的笔刷名称,请保持“syntaxhighlighter-brush-你的笔刷名称”的格式
  • 第二个参数是我们笔刷的存放路径,请相应放个插件可以搜索得到的位置,最好就放在插件下
  • 照抄吧,我没去查这个的含义,现在快凌晨6点了,好困啊
  • 你的版本号,给自己个参考免得下次修改不知道哪个比较新

3.然后我们需要对我们的笔刷映射一个名称来代表它

请找到这个函数并在它下面添加

$this->brushes = (array) apply_filters( 'syntaxhighlighter_brushes', array(
/* ... */
'verilog' => 'verilog', //左边是日后我们要试用的标签名称,右边是笔刷名称
) );

4.最后,上传新笔刷文件到我们之前定义的目录下,然后贴段代码来试看看

怎么样,这样的效果满意不

你可以自己调整颜色搭配,考虑剔除掉某些关键字不着色都可以

Tagged

魔兽世界插件开发入门教程——2

自己开始写教程才知道有多麻烦
此刻再也不敢乱评价说某些书某些教程有多糟糕
干这个事情连排个版都像体力活
何况最近心里惦记着新加坡公司的面试
不是太有心思搞这个
可能会有挖个大坑出来
但是至少应该会讲完QuestRepeat最初级的版本功能实现。

本节目标

上次我们讲完了开发插件需要的3种文件中的必不可少的toc文件
这次,我们来看下开发操作界面用的XML文件
首先先看下QuestRepeat的界面
看到这个烦人的交布换声望任务了吧

    • 我们要用XML实现任务对话框最底下的那几个按钮

比如“全部完成”,“完成一次”,“取消”还有完成任务的次数的文本输入框等
至于右边的那两个窗口,我是用lua代码动态创建的,我只是尝试了用两种不同的方法来开发界面
在这里,我们只说XML,剩下的,日后再教

    • 这时候有人会问了,WoW原本的任务对话框的不是有个“完成任务”的按钮呢,那个哪里去了?

答案是我们把它隐藏了
因为我们要开发个插件,可以让我们点击一次“全部完成”,之后只需要一直右击NPC,插件就会自动不停的帮我们一直上缴物品完成任务,所以我们不需要那个游戏自带的完成按钮。

    • 可能有人又要问了,那日后如果是跟非重复任务的NPC对话时候怎么办,难道也显示这个重复任务的插件按钮?

所以,我们需要对NPC给的任务是不是重复任务进行判断
是的话,就隐藏默认按钮,然后显示我们的插件
不是重复任务的话,就把我们的插件隐藏,一切照旧

这里是一个只实现界面,不实现任何功能的插件代码,以供你参考温习这2节的内容。
[—-请猛击这里—-]

请解压到你的魔兽世界的Interface/AddOns下面
然后随便找个可以完成任务的NPC对话,看下那几个按钮是否有出现
忽视附加的那两个lua文件,我也几乎清空了所有代码,没有什么功能,这只是个框架
关键是学习第一节的Toc文件的编写,以及怎么用XML文件编写出一个界面出来

学习完, 请把这个测试学习的插件删掉,否则你永远都交不了任务……那就是活生生的杯具啊~

因为我们没有在那些lua文件中实现操作处理
系统不知道怎么响应按钮被按下等动作
你怎么点“全部完成”之类的按钮,也都是无法完成任务的

从零开始

自己编写,请新建一个XML文件,名字随意,只要记得在上一章你写的toc文件中加入即可
XML里的代码内容请参照给的范例

要至少写出以下这些结构了再放进WoW运行查看结果
没写完是看不到什么玩意的

下面我们来讲解下其中的各个标签的用途。

XML文件结构

QuestRepeat.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="localisation.lua"/>
	<Frame name="QuestRepeat" parent="QuestFrameRewardPanel" hidden="false">
		<Scripts>
			<OnLoad>
				QuestRepeat_OnLoad();
			</OnLoad>
			<OnEvent>
				QuestRepeat_OnEvent();
			</OnEvent>
		</Scripts>
		<Frames>
			<Button name="QuestRepeatOnceButton" inherits="UIPanelButtonTemplate" text="COMPLETE_ONCE">
				<Size>
					<AbsDimension x="90" y="22"/><!--x是按钮的宽度,y是高度-->
				</Size>
				<Anchors><!--该框体的对齐设置-->
					<Anchor point="RIGHT" relativeTo="QuestFrameCancelButton" relativePoint="LEFT">
						<Offset>
							<AbsDimension x="0" y="0"/>
						</Offset>
					</Anchor>
				</Anchors>
				<Scripts>
					<OnClick>
						QuestRepeatOnceButton_OnClick();
					</OnClick>
				</Scripts>
			</Button>

			<!--这里还有一堆别的按钮的实现代码,只讲解一个,其他就依葫芦画瓢,自己试着写看看-->

		</Frames>
	</Frame>
</Ui>
<Ui>标签

<ui>是最上级的标签

如果要用XML文件描述界面,这个标签一定要有,其中xmlns是命名空间,后面的xsi那些是用xsd文件来检查XML文档的结构,这几样东西是XML技术中的术语,不是太熟悉的童鞋可以去google温习下

需要指出的是,这个标签你可以直接这么复制粘帖到任意一次插件编写当中,你可以将这一行当约定好的规则使用

我对schema这个讨厌的东西不是太了解,还望有高手能指点下

<Script>标签

<script file=”localization.lua”/>

这个标签在我的插件源代码中是没有的,这里只是为了说明一些主要标签特地这么写而已,它的作用只是表示在此处加载一些lua文件而已

如果记性好的童鞋应该记得toc文件的最后也有文件加载列表,是的,我就是在那里加载的,所以这就意味着你可以不写这个标签,把加载文件的描述放在toc文件那里

<Frame>标签

<Frame>标签就是我们最重要的用来描述你插件界面的东西

    • name属性 —— 是我们插件要创建的图形界面那个窗体的名称
    • parent属性 —— 表示我们的插件要依附关系

这里表示依附在QuestFrameRewardPanel也就是任务说明窗口下,这样任务说明窗口显示跟隐藏的时候,我们的插件也会跟着一起显示跟隐藏

那我们怎么才能知道要继承什么窗体,或者游戏中的那个窗体叫什么名字?
呃……请下载个WinMPQ以及一个BLP图片格式的查看器,自己去搜索如何查看WoW的MPQ文件下的资源以及安装文件夹下的几个MPQ文件的作用,之后按照英文名,意识流般的第六感查找吧……

不要怀疑,我真就是这么干的
哪位大神知道人性化一点的方法,务必务必告知在下……

    • hidden属性 —— 表示我们的插件是默认要显示还是隐藏

true是隐藏,false是显示,日后可以根据不同情况更改状态,并不是一定就是一直显示或隐藏。

<Frame>底下的<Scripts>标签

包含的是插件的界面窗体在游戏系统触发的一些事件下的处理函数声明

    • <OnLoad> —— 描述的是框体只在被WoW成功创建之后触发的事件的处理函数

我们在它下面写下QuestRepeat_OnLoad(),意思是我们日后要用这个函数来响应该事件并进行一些处理。

    • <OnEvent> —— 描述该框体用来处理那些我们注册过要监控的事件的函数

跟上面的OnLoad事件不一样的是,这个是对我们注册过的所有想要监控的事件一起进行分类处理,OnLoad是一个窗体创建的时候一定会触发的一个事件,但是我们往往想要监控更多的东西,比如自己是否脱离战斗,是否可以完成某个任务,所以,一般需要独立写一个函数对这些进行分类处理,小插件的大体结构实现一般是靠这个函数来主导的。

上面说的有点绕,其实翻译的时候handler之类的名词就直接叫handler算了,不知道我这样翻译解释的更多,是否又对新手造成更多的困扰,所以给一个相同的文章给你们一起当参考:CWDG Wiki

我知道看到这里有些没基础的童鞋会不明白什么是事件,为什么要注册,为什么要使用这种机制来控制插件运作

容我在此废话多说几句小解释一下,更多的东西请自己另外google

其实现在的软件,包括操作系统,很多都在使用这种消息响应机制来设计。整个WoW的运作也是如此,比如一个插件加载会出现一个插件加载成功事件,你 跟一个NPC对话,游戏会抛出一个叫GOSSIP_SHOW的对话触发事件。

可是游戏触发了这个事件对我们有什么意义呢,比如我想监控自己的战斗记录,不停的记录我曾经出现的最高伤害数值,首先就要向WoW注册要捕获事件 CHAT_MSG_COMBAT_FACTION_CHANGE,这是战斗记录变化时会触发的事件,然后在去对应写个处理这个事件的函数,这样每次打出新 的伤害的时候,插件就会不停的比较、更新数值记录,这个日后我们写Lua文件的时候会慢慢再教怎么做。

此刻你只需要明白,事件及其响应机制就像你告诉WoW你要监控什么情况,WoW在那些情况发生的时候,会第一时间告诉你:“事情见鬼的来啦,赶紧干活啊!”, 这样你就可以在准确的时间开始干对应的活了

若想知道所有的事件处理函数的名称及其对应的框体,请猛击:WowProgramming-Script Listing

<Frame>底下的<Frames>标签

不要把两者搞混了哦,使用<Frames>可以使我们能在<Frame>下内嵌更多的框架,也就是各种界面资源。

我在<Frames>的下面可以开始写各种我们要建立的界面的东西,上述代码只贴了一个“完成一次”的按钮的实现部分,由于不好单独剥离解释,我直接在源代码中打了注释,直接看注释跟一些属性的英文名基本就知道它的用途。

我就不一行一行的解释代码,很多时候,这种看源代码自学的能力是要培养的。

问题1:我们怎么知道可以创建多少种框体?

请看下表

要创建某种框体,在<Frames>下添加对应的<框体名称>的封闭标签
不知道什么是封闭标签?请回去了解XML基础知识

Button —— 按钮
EditBox —— 可编辑文本框
GameTooltip —— 鼠标提示信息框
ColorSelect —— 颜色选择器
MessageFrame —— 信息框架
Minimap —— 迷你地图,小地图、战场地图等
Model —— 模型,用于显示3D动画,人物模型,冷却都是Model
MovieFrame —— 从来就没见过哪个插件用着这玩意,我不懂
ScrollFrame —— 带滚动条的框架
ScrollingMessageFrame —— 滚动信息框架,例如聊天窗口、战斗信息窗口
SimpleHTML —— 简单超文本标记语言,没用过,不懂
Slider —— 滑块,像系统设置中的UI缩放那个滑快
StatusBar —— 各种进度条

问题2:我们怎么知道框体有多少种可以设置的属性,比如长宽、或者是否可以有显示的文字?

请狠狠的抽这里:WowWiki-XML elements

这个WowWiki整理的非常详细,我第一次觉得它比WowProgramming好用,主要当初我在学校的时候,WowWiki经常被墙……很讨厌在这里找资料。

发现现在它不怎么被墙了,不过,翻墙工具还是要准备个的,例如Tor什么的,翻墙去看看youtube……

如果你觉得这么干做界面太辛苦了,可以给你介绍一个软件,能图形化的设计一个插件的界面
不过我并不爱用,因为我曾经试过,这个开发环境容易崩溃。现在过了这么久,说不定已经改善了
想当白老鼠的,请猛击这里:WoW UI Designer

总结一下

这一节我们讲解了如果使用XML文件来编写自己的插件界面
请和上一节的toc文件放在一起
自己验证看看效果

当你第一次编写完整,在魔兽世界里面看到你的插件界面的时候,你可以切换到桌面,一边修改XML文件,保存它,然后回到WoW,用我们上节曾经提到的

/console reloadui

刷新一下,再跟npc对话就可以查看新的结果。没错,我就是这么修改一次,刷新一次的……我这5年前的老爷机,重新载入一次那是何其漫长而销魂的等待……
就先说这些,那下次见吧~

Tagged , ,

魔兽世界插件开发入门教程——1

昨晚折腾网站折腾得太晚
到今天中午才睡觉
此刻果然全无睡意
刚好趁此时间整理下东西
借以排遣此般漫漫长夜

前言

记得当初我开始查找编写WoW的插件的资料时,在google里直接进行中文搜索,得到的资料极为稀少且多为重复内容,偶然间发现CWDG社区(CWoW Developer Group),那是我当时为数不多的中文资料来源。

若你要编写WoW的插件,请做好英语上的准备,因为大部分的资料都是来自于国外。你可以不在意四六级成绩,这种应试的英语考试也没什么用,但是至少英文的读写能力要能基本合格。

本文以我自己编写的QuestRepeat这个插件为例讲解

[查看插件功能及其基本介绍]

你可以在上面的链接,大概了解下整个插件的功能及界面,本插件是为了解决那些可以上缴物品的烦人重复任务开发的,有个印象之后,我们开始来讲解。

 

准备工作

首先编写插件的核心代码需要用到Lua语言

这里推荐一本叫《Programming in Lua》的电子书,中文名叫《Lua程序设计》,作者是Roberto Ierusalimschy,你可以在网上搜索到中文译本,看完前10章就可以,后面的高级特性可以暂时先不用了解

现在Lua的版本是5.2,记得几个月前我去投简历的时候,面试官问我写的插件lua用的是什么版本,当时就晕了自己只记得是5.X,平时不够高端很少留意这个,毕竟WoW对lua的版本要求并不多,而且5.2的版本也刚更新不久。不过这对我们编写插件并没太多影响,WoW给的官方API的变动才是影响最大的地方。

请偶尔多留意下这种细节,不要像我一样丢人现眼的被人误解作品不是自己所写,=.=!。

传言lua每更新版本都会伤筋动骨的大改,不过5.1转5.2的这个时候,我已不再关注WoW的插件开发,我也并不知道有多少影响,在此不多评价。

学程序是需要练习的,你可以在lua.org下载到Lua在windows平台下的整个开发环境的安装包,这个请自行查阅资料去使用。

然后你需要了解点XML相关的知识

不需要太多,http://www.w3school.com.cn/xml/index.asp这里你可以挑选着看点,大概知道一些规定就可以,毕竟XML在我们这只是用来描述界面结构,我们并不去开发网页

最后,开始编写插件

你只需要用windows自带的记事本就可以了,当然你若喜欢用Vim或者Emacs这类文本编辑器,你一定高端到不用我来废话。

请看完以下内容,并自己写一个toc文件,但是你还无法验证对错
先准备着,等第二节教程看完学会了下一节的东西,再一起验证。
如何使用记事本创建一个非txt扩展名的文件,这样的基本操作知识如果不会,请自行google。

技术讲解

插件整体结构

  • 插件工程 —— 其实就是插件的目录,用来存放以下三种文件。要调试或使用插件时,目录的存放位置: {你的魔兽安装目录}\Interface\Addons\{你的插件名}\
  • 工程描述 —— 后缀名为Toc的文件。描述插件工程的必要信息,也是项目载入时的总入口。
  • 屏幕布局 —— 布局描述使用XML文件。使用各种标签代表特定对象,并描述相互位置及各种特征属性,通俗的说,你的插件的图形界面就是用这个文件来描述的。
  • 功能脚本 —— 脚本文件使用LUA语言。使用WoW提供的API函数编写代码实现各种具体操作,插件的功能实现核心。

如下图所示,我自己编写的QuestRepeat插件就放在了”D:\Game\World of Warcraft\Interface\AddOns\QuestRepeat”

下面我们来看看其中的文件,如图所示,包含了上面讲到的3种文件,你可以使用windows自带的记事本打开编辑他们,我使用的是gVim,会有代码高亮等优点,你可以自己下载一个折腾看看。

值得一提的是,XML文件并不是必须的,因为早些时候的WoW版本,你的插件界面只能靠XML文件来描述,但是到了后面,又有了可以用lua动态创建窗口等资源的API,所以你可以完全抛弃XML全部使用lua+toc文件就可以写出一个插件。另外假如你的插件不需要界面,那XML文件自然是可以省略,不要怪我罗嗦,只是为了个初学者提个醒。插件只实现某种功能,比如过滤垃圾文字,可以不写界面。

Toc文件的结构

以我的插件为例
QuestRepeat.toc内容如下

## Interface: 30300
## Title: QuestRepeat
## Author: Tyreal Han
## Version: 2.3.2
## Notes: Enables quicker repeating of quests.
## Notes-zhCN: QuestRepeat 让一些可以重复完成的任务快速完成,比如交声望的任务。
## Notes-zhTW: QuestRepeat 讓一些可以重複完成的任務快速完成,比如交聲望的任務。
## DefaultState: Enabled
## SavedVariables: QR_VerControl, QR_RepDB

localisation.lua
QuestRepeat.lua
InfoFrame.lua
CalHonorFrame.lua
CalRepFrame.lua
QuestRepeat.xml
    • Interface ——插件适用的WoW的版本号

比如3.1.3的国服版本,就是30100,那些奋斗在wlk的3.3的国外版本,就是30300,以此类推。 还记得每次更新WoW之后在人物登录界面的那个插件列表看到一堆插件过期的显示么,就是靠这个来检测的,其实很多插件都是一直可以用的,你只需要用记事本打开这个toc文件,修改这里的数字就可以了。

    • Title —— 插件的名称,会显示在我们人物登录界面的那个插件列表里
    • Author —— 插件作者的名字,仅仅只是陈述下,但是不会显示出来
    • Version —— 插件版本号

不会界面中的列表显示出来,有时候我们安装了某些插件看到插件后面带了版本号,其实是作者在Title那边一起写的,这里其实只是给开发人员一个提示作用,所以版本号的格式,自己规定的而已,你可以从来都写1.0之类的一个版本,不改变,不过这样就是丑陋了点。

    • Notes/Notes-zhCN/Notes-zhTW —— 分别对应英文版/大陆版/台湾版客户端运行下的插件说明,可以只有一个Notes的英文版说明即可
    • DefaultState —— 默认状态,我一直填写Enabled

这个参数我没去详查,一直没什么影响,若你好奇,查得结果,请留言赐教

    • SavedVariables ——插件储存、读取变量设置

很有用的一个参数设定,我们使用插件的时候,插件经常有各种不同的设置,或者有些插件,比如QuestRepeat会保存任务获取的一些信息,那这些配置乃至任务信息要怎么存储到硬盘上呢,就是靠这个参数设定的这几个变量。
名称自己设定, 比如QR_RepDB,这是我用来保存任务信息的一个table,不知道table是何物的请回去重读《Programming in Lua》,游戏运行时候,当上缴完任务之后,QuestRepeat截获的一些任务奖励信息,会保存到QR_RepDB这个table变量中,然后等游戏重新加载,或者退出游戏的时候,这些变量的内容会被WoW的客户端写入

{你的WoW安装目录}\WTF\Account\{你的游戏帐号名称}\SavedVariables\{插件名称}.lua

当游戏下次运行的时候,插件会根据SavedVariables后面的的设定,并从中刚才的那个文件中读取出对应需要的数据。

由于WoW保存变量的时间是在游戏退出或者重新加载插件的时候,所以有些记录DKP的插件,若你的WoW异常出错被强制关闭,你本次活动的过程中又没有自己手动重新加载全部插件或者中途重启过游戏,那记录的所有信息将丢失,这也就是有些玩家抱怨说游戏出错后当时活动的DKP数据都不见的原因。你可以在聊天框输入

/console reloadui

这样就可以让数据写入硬盘了,不需要重启游戏,我们等会写插件也会经常用到这个命令来查看插件修改的结果,另外还有一些别的宏命令也一样可以实现同样功能,请自行google。

    • 最后,那些一行行的文件名称列表,是告诉WoW客户端加载你编写插件的文件的顺序

请根据你程序结构注意好顺序,变量的生命周期是有影响的。不过一般我们自己编写的插件只要不要太大型,自己应该都知道怎么安排顺序,这个造成混乱的机会不多。
若你想了解更多内幕,请猛击这里的资料:CWDG WiKi-UI代码的载入顺序

PS:localisation.lua是插件本地化文件需要的东西,比如我们要把一个英文版的插件翻译成中文版,修改这个文件就是。当然还有一些别的实现方法,这个是比较普通常见的一种。

总结一下

由上面的资料我们可以知道,编写一个插件需要为它建立一个文件夹,然后给文件夹也就是插件取个名字,在文件夹下面建立一个同名的.toc文件,按照规定填入那些规定的字段,整体描述下你的插件,SavedVariables以及最后面的文件列表,我们可以一边编写,一边添加,放空暂留个位置就可以。

当然,弄完toc文件你的插件还是无法在游戏中运行的,因为还没有写最关键的lua脚本代码,没事,先有个整体的了解,下一次我们会先讲下插件的界面描述XML文件,然后在那些界面的基础上开发插件的功能。

实际上我们设计的时候,是先设想要什么功能,然后根据功能定夺需要什么样的界面,但是实现它的时候,界面往往会先写出来,因为毕竟插件编写不是像平常写C++一样有个IDE可以先进行各种单元测试,插件需要先在游戏中看得到界面才能进行一些交互操作开发,我也是按照这种顺序来开发的,看起来很原始很低端又不那么高效,且在前期不好验证功能的可行性,假如你有更好的方法,希望能互相交流下。

下次见~

Tagged , ,