为什么需要字节码?
一般而言,字节码更利于计算机理解和执行,但不利于人类理解,而其对应的脚本语言则正好相反。
了解Jass字节码有什么好处?
魔兽有一个特殊的限制条件,单次虚拟机执行不能超过30万个字节码,一旦超过就会强制终止执行。如果你用YDWE来测试的话,YDWE会告诉你“超过了字节码限制”,就是这个意思。当然这个并不需要你深入地了解字节码。
在I2C/C2I还可以用的年代,code转为的整数,实际上就是字节码的序号,所以你可以用I2C(C2I(function XXX) + 5)这种方法来从函数中间开始执行。这就需要深入地了解字节码了。
无论如何,Jass字节码都是深入了解Jass的必备知识。
由于Jass字节码并不需要给人类阅读,所以它并不像Jass那样存在可读文件的格式,只有二进制格式。为了便于我们理解Jass字节码,我做了一个Jass的反汇编器,它可以将Jass字节码(二进制格式)转为可读文件的格式。它看起来是这样的
|
|
这个是BJ函数BJDebugMsg对应的字节码。和Jass不同,Jass里一行可以做很多操作,而Jass字节码一行只能做一个操作。这个不同的操作,我把它称之为操作码,在Jass字节码中,一共有41个操作码,它涵盖了Jass里的所有可进行的操作。不过在介绍操作码之前,还得先理解几个概念。
寄存器
寄存器大概可以理解为一个公用的临时变量。Jass字节码里一共有256个寄存器,分别以r00,r01,…,rFF命名。
寄存器的读和写比变量快很多,所以大多数的操作码都是和寄存器打交道,而不是变量。
调用栈
调用栈和寄存器类似,不过它更像是一个数组。调用栈的作用是在函数调用方和被调用方直接传递函数的参数。
全局变量和局部变量
在Jass字节码的层面上,全局变量和局部变量只在定义时有区别,在读写时没有区别。在读写时,Jass字节码会用变量名先从局部变量里查找变量,有则使用它,没有则取全局变量里找。
操作码
41个操作码我会逐一介绍一遍,以便你能对Jass字节码有全面的了解。
操作码:一元运算符
一共有3个一元运算符,分别是i2r、not、neg。一元运算符就是输入只有一个参数的运算,例如Jass里的not和-,分别对应Jass字节码的not和neg。i2r没有对应的Jass运算,在Jass里整数到实数是支持隐式转换的,也就是说一个函数的参数要求是实数,你传一个整数也是可以的,而在Jass字节码里,Jass编译器就会帮你添加一行i2r的操作码,帮你把整数转为实数。
一元运算符的参数是一个寄存器,它会将这个寄存器里的值进行一次运算,然后再把运算的结果放回这个寄存器。例如
操作码:二元运算符
一共有13个二元运算符,分别是and、or、eq、neq、le、ge、lt、gt、add、sub、mul、div、mod。二元运算符的参数是三个寄存器,它会将第二第三个寄存器里的值作为输入进行一次运算,然后再把运算的结果放回第一个寄存器。例如
|
|
二元运算符和Jass的对应关系
and | or | eq | neq | le | ge | lt | gt | add | sub | mul | div | mod |
---|---|---|---|---|---|---|---|---|---|---|---|---|
and | or | == | != | <= | >= | < | > | + | - | * | / | % |
注意:取余运算(%)在1.29才被加到Jass里,但Jass字节码的取余运算(mod)是一直存在的。
操作码:移动运算符
移动运算符一共有7个,我为了简单起见,都统一使用mov,但每个mov的格式是不同的。
mov的含义是将一个值从某个地方原封不动地搬运到另一处地方。
1> 寄存器之间的移动
|
|
2> 寄存器到变量
|
|
注意:这里不区分全局变量和局部变量,它们的字节码是一样的。下同。
3> 变量到寄存器
|
|
从变量移动值到寄存器需要执行值的类型,jass字节码不信任变量上记录的类型信息,它总是以自己记录的类型信心为准,这就是为什么变量到寄存器需要类型,而寄存器到变量则不需要。下同。
4> 寄存器到数组变量
|
|
5> 数组变量到寄存器
|
|
6> 函数到寄存器
|
|
也就是将这个函数的code存到寄存器里
7> 字面值到寄存器
字面值的意思就是你在Jass里直接写出来的值,例如1、1.0、true、false、”字符串”等
|
|
值得一提的是nothing,nothing也一个值的类型,在Jass里不存在nothing这种值类型,但Jass字节码里有。Jass唯一出现nothing的地方数函数的参数和返回值定义,实际上Jass字节码也是用于此。
操作码:新建变量
新建变量操作码有3个,分别是local(局部变量)、global(全局变量)、const(常量全局变量)。常量全局变量只用于编译时,在运行时和全局变量没有任何区别,理论上并不需要在字节码里区分常量全局变量和全局变量,但Jass字节码里为什么有,原因不明。
|
|
另一个有趣的地方在于,Jass变量数组的属性也混在值类型里的,也就是说整数和整数数组是两个不同的类型。所以你能看到在定义j时,你把它的类型定义为整数数组,但在使用时(例如mov),用的类型却是整数。也许我在文本格式的字节码里去掉这种歧义更好,不过至少目前我是原汁原味地保留。
操作码:跳转
和跳转相关的操作码有4个,分别是label、jmp、jt、jf。
label是一个占位符,没有实际的作用。
jmp,执行这个操作码就会跳转到指定的label。
jt,如果寄存器里的值为true,就会跳转到指定的label。
jf,如果寄存器里的值为false,就会跳转到指定的label。
操作码:类型定义
一共有两个操作码,分别是type和extends。含义可以参考common.j最前面的几行。不过在Jass字节码里,它们实际上没有任何实际作用。
操作码:函数调用
和函数调用相关的操作码一共有9个,分别为func、endfunc、popn、push、pop、arg、call、return。其中call有两个,一个是call Jass函数,一个是call native函数,native函数就是不存在于Jass里的函数(也就是CJ函数),但我们不太需要关注其中的区别。
func、endfunc只是标记一个函数的开始和结尾,但没有任何实际的用处。
push将寄存器里的值移动到栈顶,并使栈顶的计数+1。
pop将栈顶里的值移动到寄存器,并使栈顶的计数-1。
popn将栈顶计数-n。
例如
|
|
arg将新建一个局部变量,并将栈顶计数-n的值移动到这个局部变量里
|
|
call 一次函数调用。
return 从函数调用返回。
说了这么多理论,来看一个实际的例子吧
|
|
函数参数传递的方式,就是利用栈。在调用函数前,将参数依次push到栈里,调用完毕后,用popn清理栈里的参数。对于被调用的函数,利用arg操作码读取栈里的值,并创建局部变量。
函数的返回,Jass字节码里函数返回值都放在r00寄存器里,所以只需要在调用函数后,从r00寄存器就可以取到返回值。
细心的童鞋可能已经发现了,我们根本不需要pop,实际上魔兽生成的Jass字节码里函数调用也确实没用上pop。大家可以看看前面BJDebugMsg的例子,里面就有用到pop的地方,由于操作码已经介绍完毕,所以大家可以试着阅读BJDebugMsg的例子,并理解pop到底在干什么,再回来看下面的内容。
魔兽生成的字节码
Jass字节码可以说也是一门语言,上面介绍的算是Jass字节码的一些语法。但就像每个人写的Jass可能都不同一样,Jass字节码不同的人也可以写出不同的东西来。所以我们现在来研究下魔兽它写的Jass字节码是什么样的。
首先Jass字节码有256个寄存器,这个数量可谓相当的多。为什么魔兽需要这么多寄存器呢,这就涉及到寄存器分配算法了。当我手写Jass字节码时,我经常会不停复用某些寄存器,例如
因为我知道什么时候r01的上一个值我不需要了,这时我就可以复用r01。同样地,一个合理的寄存器分配算法也是如此,这样就可以用很少的寄存器来做到你想做的事情。然而魔兽并非如此,它并不管寄存器里的值神时候失效,而是在所有的寄存器里循环使用。除去用于返回值的r00,一共255个寄存器,当用完255个寄存器后,上一轮的寄存器还在用的可能性几乎为零。所以每次魔兽需要新的寄存器时,它都会用当前寄存器的下一个。
不但如此,魔兽生成的字节码甚至不会在两个操作之间共享寄存器。考虑这行代码,会生成怎么样的字节码
|
|
通过上面的学习,我相信很多人都可以轻易的写出这样的字节码
但魔兽生成的字节码是这样的
|
|
为什么会这样呢,我们可以分析分析我们手写的字节码有何难点在里面。对比add这一行,我们的第一个操作数是用r01,而魔兽是用r03。原因就在于此,魔兽它无法计算出第一个操作数的寄存器是什么。由于第二个操作数可能是一连串复杂的运算,并非寄存器-2就是第一个操作数的寄存器。所以魔兽把操作数push到栈里,在用二元运算符前,再把它pop出来。你会发现魔兽生成的字节码里,所有的二元运算符前都会有一个pop的操作,原因就在于此。
我们可以做些什么?
我们什么都不可以做。我们可以了解它,但我们无法对它做任何改变。大多数的脚本语言都会开放字节码,也就是运行时支持直接执行字节码,你不满意官方实现生成的字节码质量,你可以自己写一个编译器来生成自己的字节码,甚至你也可以定义另一个语言进而编译成相同的字节码。但这并不包括Jass。很早之前我曾经说过,希望blz可以开放字节码,让魔兽支持运行包含Jass字节码而非Jass的地图,这样魔兽的生态才能迎来新的变化。拾YDWE的牙慧,加那些blzapi,在我看来毫无意义。
]]>为什么需要新的格式?
w3x格式的地图有诸多的缺陷,首要的一点是w3x格式是blz的私有格式,过于封闭,使用w3x格式意味着你无缘这个世界上的大多数的工具,只能使用非常有限的几种工具,例如WE、MpqEditor、HKE等,这实在是太可怕了。此外,w3x格式实际上是blz针对地图运行时优化的格式,完全没有考虑过地图编辑时的感受;例如将多个小文件打包为大文件以提高硬盘读写的性能,这种特性在编辑时几乎是没有用的,至少它是大大牺牲了地图的可编辑性。
而新格式的目标,正是尽可能地移除私有格式,让你可以尽可能享用这个世界上各种优秀的通用工具来编辑管理你的地图。请记住一点,使用新格式,YDWE不再是你编辑地图的唯一手段。这也是我最希望看到的效果。
新的格式有什么功能?
新格式(以下把它称为Lni格式)它首先是一个文件夹,而非一个文件。这意味着,你不再需要YDWE里的输入管理器(但你依然可以用),我相信Windows的资源管理器会有你想要的一切功能。此外,Lni格式还会对你地图的文件作一个简单的分类
Lni格式除了移除了mpq的私有格式外,还将物编和触发的格式改为了文本格式。你甚至可以用文本编辑器来修改物编和触发。
多人协作编辑地图
多人合作是一个不可避免的问题。也许你只会些制作地图的某些环节,比如脚本、触发、数值、地形、特效、美术等。一个人要精通这些所有的环节非常的困难,即是全都精通,当你想要制作优质的地图时,还是需要多几个人来为你分忧。然而w3x格式的地图多人协作简直就是噩梦。基本上都是同一时间只能有一个人在编辑地图,通过QQ群的方式告知其他人不要改地图我在改之类的。
Lni格式可以让你充分利用各种成熟的版本管理工具,大大改善多人协作的体验。以主流的git为例,现在有很多免费的git托管服务,使用起来非常简单。
https://github.com/ 目前世界上最大的git托管服务提供商,YDWE的代码也是使用它里管理。缺点:没有中文界面,实际上都是非常简单的英文;不支持免费的私有项目,也就是说你的地图会被所有人看到。
http://www.gitlab.org/ 缺点:没有中文界面;没有github稳定。优点:支持私有项目。
http://git.oschina.net/ 缺点:国内的服务提供商。优点:中文界面,支持私有项目。
至于git的使用,要说的东西很多,支持git工具也很多,你可以自行从网上学习更多的知识。
让你的地图不再丢失
使用git托管服务之后,还有一个好处是,你的地图不再会丢失。地图储存在服务器上,硬盘损坏、U盘丢失、不小心误删、保存错误找不到备份,这些事情都不再会发生。只要有网络和YDWE,你就可以随时开始编辑地图,以及随时保存你的工作结果。
如何开始使用Lni格式?
两种方法
在Lni格式的根目录,会有一个’.w3x’的文件,用YDWE打开它就是打开Lni格式的地图。
Lni格式的未来
最后
我希望所有的人都来使用Lni格式来编辑地图,你将会拥有一个全新的视角看待你的地图。
]]>接下面的时间,我会把主要精力放在文档和示例上,以帮助大家迅速了解它。这一切的工作都可以从这里看到。幻想编辑器使用的语言的lua,如果你曾经在YDWE的lua引擎上学习过lua,相信会很容易上手。不会lua也不要紧,幻想编辑器也提供了触发编辑器,只是目前触发编辑器还不是很完善。
其他也没什么要说的了,因为我想说的内容都在文档里。
什么时候可以用上?
据我所知是本月内。
YDWE还会更新吗?
最近YDWE上也发生很多事。比如暴雪更新了1.29ptr,我已经在其他场合说过,YDWE不会支持1.29。这本来也不是值得一说的东西,因为YDWE连1.28都没支持。
说到更新,如果有关注我在贴吧动态的人应该知道,YDWE即将要支持新的地图格式。简单来说新的地图格式就是一个文件夹,并且会把地图内的文件按模型/声音/数据/脚本等规则分放在不同的文件夹内。除了最终发布时需要打包为w3x之外,新的格式能给你带来非常多的好处,尤其是在多人协作上更是质的飞跃。我强烈推荐大家都来使用新的地图格式。
另外即将加入1.32的功能还有测试地图自动slk优化。我见过有些地图loading一次要几分钟,所以那些作者不过使用测试地图的功能而是先保存地图,用slk优化工具优化后再测试。如果你也有同样的问题,那么这个功能将能节约你大量的时间。
此外,如果你的地图loading要几分钟,很可能保存也需要几分钟。这确实很糟糕,我也在计划解决这个问题。WE的代码实现,说实话很糟糕,对此我也无能为力。不过我在计划尽可能地跳过WE的编译流程,转而由YDWE来实现。尤其是Jass的代码生成。也许YDWE以后会有一个完全脱离WE的地图构建器。
以上就是YDWE未来的更新计划,我认为任何一点都比支持1.29更有意义。暴雪的人或许该来这里学习下,如何更新WE。:)
]]>replay的关键在于所有行为都是确定且可重现的。对于随机函数math.random很简单,lua提供了设置随机种子的函数math.randomseed,相同的随机种子总是产生相同的随机序列,我们只需要保证replay使用了相同的随机种子即可。
但是lua还有一个随机行为,考虑以下代码
|
|
你会发现每次的输出都是不一样的。lua遍历表的次序取决于key的hash值,而lua的很多数据类型的hash都是随机的,这包括了string、table、userdata等。而且它并不取决于math.randomseed指定的随机种子,所以不能很简单地解决它。
既然pair有随机性,那么我们来写一个没有随机性pairs吧。
|
|
这是一个根据key排序次序遍历的pairs,但是这个pairs也有几个问题
现在的输出是a、b、c、d…,但我更希望是看起来是随机的次序,这样行为看起来会和原版的pairs更像。这就需要在排序比较函数里加上一个hash函数。然而由于lua无法高效地访问字符串,所以如果hash函数不用c实现的话,效率低得可怕。
|
|
hash函数需要保证没有冲突。由于第一次遍历是随机的,所以两个hash值相同的项遍历的次序也是随机的。
其实lua的table在插入时,就做了类似我们上面sortpairs所做的事情,对key取hash然后排序插入表,pairs所做的就是按次序遍历了一遍排序的结果。只是lua的这个hash函数带有随机性,所以才让pairs带有随机性。lua的hash函数在ltable.c中
|
|
这里面有随机性的是LUA_TSHRSTR、LUA_TLNGSTR、LUA_TLIGHTUSERDATA、LUA_TLCF和default。
先说字符串,字符串在计算hash时引入了一个随机种子,这个随机种子和math.randomseed不一样,在lstate.c里的lua_newstate初始化。
|
|
|
|
为了保证这个随机种子的随机性,lua引入了多种随机变量来保证随机性。所以最好的办法就是重写makeseed,或者把随机种子从lua_newstate的参数传入。因为lua_newstate只需要在lua_State初始化时调用一次,所以随机种子从lua_newstate的参数传入是个不错的方案。
这样我们就可以通过传入的随机种子来改变pairs遍历字符串key时的次序了。
hash的关键在于相同的值,总是能得到相同的结果。由于lua不知道userdat的d具体含义,无法和string那样根据值来计算hash。不过值得庆幸的是lua里同一个userdata只有一个副本,所以我们只需要在userdata初始化时给它一个全局唯一的ID,就可以作为它的hash值了。
首先要修改userdata的数据结构,加一个hash值,这个在lobject.h中
|
|
然后在初始化时,给一个全局唯一的ID。
最后需要修改mainposition函数,userdata属于default分支里。需要注意的是,我们并没有修改所有的GCObject,所以计算hash只能计算我们已经修改过的GCObject(所有的userdata)。
其实在修改userdata的时候,我们已经给所有的GCObject对象添加了hash值,所以table和thead我们也可能略作修改就可以支持,只是thread的初始化函数是lua_newthrad而不是luaC_newobj。
而lightuserdata和c function并不是GCObject,修改起来比GCObject麻烦一点,并且需要使用的情况很少,所以我就不改了。lua function虽然也是GCObject,但是在lua层面上,c function和lua function是没有区别的,为了统一也一并不作支持。
最终的实现,可以参考我YDWE中的lua副本。
除了replay的支持外,确定行为的代码对于帧同步的多人游戏也尤为的重要。魔兽就是一个帧同步的游戏。简单来说,所有的客户端都会跑同一份代码,定时校验不同客户端之间的状态是否一致,不一致就会导致掉线。如果同一份代码同样的输入却得到不同的输出,就很容易出现状态不一致从而掉线了。
]]>这次用的是gitment,也就是用github的issues作为评论系统,所以之前的评论数据也没法使用了。前一段时间,也因为某些不可描述的原因把网站的dns服务商从dnspod换成了godaddy。现在弄个网站真是越来越麻烦了。
因为之前的评论丢失了,所以这里统一回复下几个问题吧。
如果你遇到bug,并且想让我帮忙解决。那么务必请你详细地描述清楚这个bug产生的详细过程。另外如果你能附上演示地图,那么你得到帮助的概率会增加60%。你可以换位思考一下,如果有玩家给你留言说,“你的图玩不了”、“玩几分钟就掉线了”、“我碰到很严重的bug”,你除了对他的不幸遭遇表示同情,还能做些什么呢。
更新ydwe中遇到最大的问题还是精力的问题。而金钱上我唯一的支出只是vps+dns+域名,大概每年500+100+100不到1000元。对于我的经济能力来说,还是没任何问题的。
]]>“网易云跟帖”可以直接导入“多说评论”的数据,所以迁移的过程还是比较简单的。只是碰到一个小问题,在多说导出的数据中,url的最后是没有’/‘的,但实际博客中的url最后都带了’/‘,网易无法识别这种情况。所以我只能手动帮网易去掉了这个’/‘。
|
|
因为换了博客引擎,所以主题也只能换了一个,为了配合这个简洁风格的主题,我还特意做个了一个像素风的logo。这个主题可能比较简陋,也可能没原来的好看,但我觉得挺符合我目前心境和YDWE目前的状态的。内容比较外观重要不是吗?
]]>转换后可能无法正常游戏。
这个错误的原因是你的地图没有完整的 (listfile) 文件,虽然工具尝试搜索了导入列表(war3map.imp)但文件依然不全。
转换前的地图与转换后的地图内容可能存在差异。
编辑器中你可以将地图的游戏数据设置为“1.07”、“自定义”或“最新版本”,这3种游戏数据在实际运行时会产生差异。例如:游戏数据设置为“1.07”时,无论用哪个版本的魔兽游玩此地图,冰霜巨龙的攻击类型均为“穿刺”;而将游戏数据设置为“最新版本”后,你使用1.07版本游玩,冰霜巨龙的攻击方式为“穿刺”,但你使用1.24版本游玩时,攻击方式却会变为“法术”。当你将地图进行SLK转换时,地图的版本便会固定在某个版本的数据。考虑到绝大部分地图都不是对战地图,游戏数据不应该根据魔兽版本的变化而变化,因此我们总是将地图固定在1.07版本。
每个对象都有一个4位的ID,你可以在编辑器中按下Ctrl+D来查看它们。当地图没有SLK化时,魔兽可以正确区分类型不同但ID相同的对象(如单位 H000 和魔法效果 H000),也能正确区分字母相同但大小写不同的的对象(如英雄单位 H000 和普通单位 h000)。然而一旦当地图转换为SLK格式后,魔兽遍无法区分这些情况了,这些对象的文本可能会发生错乱。
魔兽会将长文本在符号 } 处截断,例如你有个技能说明为“造成100点{火焰}伤害”,在魔兽中实际看到的则是“造成100点{火焰”。本工具会在转换地图时会尝试让这段文本正确显示,但在少数情况下还是需要修改这个符号才能正确显示。
SLK是一个适合游玩的格式,OBJ则是适合编辑的格式。魔兽加载SLK格式的速度要远快于加载OBJ格式的速度,这也是将地图转换为SLK格式的主要目的。然而在某些情况下,部分OBJ格式的数据无法转换为SLK格式,它们会继续以OBJ格式的形式留在你的地图里,从而拖慢地图的速度。一个优秀的地图作者应该尽量减少OBJ格式的数据。
部分对象(如技能)可以有多个等级,你可以定义一个数据在不同等级时的数值。不过SLK格式最只支持1到4级的数据(装饰物可以支持到10级),超过4级的数据就只能保留为OBJ格式。
部分SLK格式的字符串数据(如模型路径)如果可以转换为数字,那么魔兽将无法正确识别此字符串。为了保证该数据正确生效,我们只能将其保留为OBJ格式。
前文说过,当对象的ID冲突时他们的文本内容可能会发生错乱。目前我们会将这些冲突对象中类型相同的对象进行处理,将部分文本数据保留为OBJ格式以保证他们的文本不会错乱。
由于转义符的原因,魔兽无法正确识别一个既包含符号 , 又包含符号 “ 的SLK格式文本,所以这些文本也只能保留为OBJ格式了。
转换为SLK格式时,无论是用到的还是没用到的对象都会保存到地图里。而简化的目的则是将没有用到的对象删掉,将用到的对象留下。为了达成这个目的,我们会通过引用链来分析地图中有哪些对象是有用到的(即“引用分析”),这个分析过程比较复杂,因此我们提供了详细的报告供作者确认引用分析是否正确。特别说明的是,引用分析无法分析动态引用(例如根据输入的聊天信息创建任意单位)。如果你发现有些你需要的对象被意外删除了,你需要通过别的方式表明你对它的引用,例如:创建一个无用的触发器,将你需要的对象在里面提到一下。
当你使用了以“市场”(nmrk)为模板的单位后,魔兽便会定期向这个单位添加“可被市场出售”的物品,因此所有设置了“可被市场出售”的物品都会被留下。
在进行引用分析时,我们发现某个需要留下的对象根本不存在。一些常见的情况有:
物编中按住shift填写的对象ID错误。注意,即使你填写了“ALOC”或是“Aloc123”,编辑器中依然会显示为“蝗虫”,但实际上该值是无效的。
建议: 删掉无效引用,改正错误引用。
引用了一个没有定义仅有文本的对象。
报告一下地图中一共有 YYY 个对象,其中 XXX 个对象由于没有引用到而被删除。
地图中有 XXX 个默认对象被保留了下来。这里的默认对象指的是魔兽自带的,且完全没有修改过的对象。之后的报告会显示部分对象以及它们的引用来源。事实上绝大部分保留下来的默认对象并不会也不希望被用到,因此你需要想办法切断引用链。一个最代表性的例子:青蛙被某个马甲单位引用,这个马甲单位是从兽族苦工上复制出来的。具体引用链为:马甲单位-可建造建筑 –> 风暴祭坛-训练单位 –> 暗影猎手-技能 –> 妖术-变形单位 –> 青蛙。显然你需要把这个马甲单位的 可建造建筑 清空,因为它引用了一万个无用的默认对象。
地图中有 XXX 个自定义对象被移除了。这里的自定义对象包括魔兽自带,但被你修改过的对象。当地图历经多个版本的变迁后难免会出现以前有用但后来被废弃的对象,你可以参考该报告来移除不需要的对象,从而加快地图开发效率。
部分文本中可以使用诸如
OBJ数据里有一些无效的数据,一般是因为曾经使用过奇怪的工具生成过物编,或是使用的编辑器里有一些错误设置。
编辑器会将长文本保存到 war3map.wts 文件中,而这正是导致地图加载速度缓慢的主要原因。使用此工具转换地图时会尽可能的将长文本放回到脚本或物编里,但由于有字符数限制,一些过长的文本还是只能保留在wts里。
事实上slk工具的主要用途是将编辑器生成的地图转化为方便游戏的地图,而编辑器生成的地图肯定会有完整的(listfile)文件,因此老狼和W2L在优化地图时可以说是必定成功的。
游戏数据设置方面,选用最新数据的地图是不适合slk化的,因为他的作用是会让数据根据你魔兽版本而改变,而slk化会把你地图的数据固定在某一个版本,那么所谓的最新版本数据就没有意义了。我们认为大部分的人是对这个选项的作用不了解而误用了最新数据,所以我们会把slk的地图都强制为1.07的数据。
体积方面,W2L生成的地图要相对大一些,主要是因为其他工具会去掉脚本里的注释与空格。但这一些差异在现在的环境下可以忽略不计了。
耗时方面,W2L作为一个使用脚本语言编写的工具速度却完爆另外2个C++写的工具,不禁让人产生“他们到底在做什么”的疑问。不过看到同样是C++写的模型压缩,W2L的模型压缩只花了不到2秒,而另外2个工具花了近20秒,对这个疑问也就释然了。
由于南瓜头无法优化万能属性,在本次测试中我将地图里的万能属性文件删掉了,因此会和实际情况有所差异,不过保证了这次测试的相对公平。
W2L的简化程度明显高于南瓜头和老狼,不知为何南瓜头和老狼会保留全部的自定义对象,而W2L会把确实没有用到的自定义对象删除。那些没有用到的自定义对象会报告在“详情”里,作者可以通过该报告来决定删除以后不会再用到的对象,以加快地图编辑的效率。
作为一个后来者,发现并修复前者的问题是相对简单的一步,而发现自己的问题就要困难的多。期待你的反馈!
我们省略了很多选项,并不是因为没有实现这些功能,而是会根据你地图的情况自动判断是否需要它们。
还有很多功能对地图的提升有限,因此我们暂时只提供了一些基础和高效的功能。
欢迎提出你的想法!
我们希望告诉作者如何让你的地图被我们的工具优化的更好,因此我们提供了很多有用的报告。
SLK的简化功能是通过引用搜索来实现的,只有当一个对象不可能出现在游戏中时我们才会将其移除。然而事实上有大量的对象被保留了下来,这是因为你地图的数据告诉我们他们确实有可能出现在游戏中,例如:
你有个马甲单位可以建造祭坛,祭坛可以召唤暗影猎手,暗影猎手可以学习妖术,妖术可以将一个单位变形成螃蟹。
所以当你在报告中看到“螃蟹 被保留(XXX马甲引用了它)”时请不要太惊讶,如果你确信不会也不希望出现某个玩家控制了该马甲单位并在游戏中变出一只螃蟹的情况,你知道该怎么做。
以龙珠中的龟派气功为例,可以分解为这几个阶段
1.双手放在腰间
2.高喊“龟派气功”,蓄力一会
3.双手向前推,发出冲击波
4.双手收回
实际上不少动漫里的大招,都会经历同样的这几个阶段。
如果用魔兽的施法流程,这是如何对应的呢?蓄力显然是持续施法,而1、4则对应了前摇和后摇。但3却无法包含在这三个阶段里,首先他不能是后摇,因为冲击波会在34之间放出。所以他只能属于施法阶段,但是从逻辑上他又不能属于施法阶段。比如龟派气功的威力由施法时间决定,显然你不能把3的时间也算在内了。所以3得是一个新的施法阶段,它在持续施法之后,但却不能像后摇那样随意取消,准备地说它更像前摇。
我们再回头看下龟派气功和暴风雪这种魔兽传统的持续施法有什么区别。暴风雪的效果出现在施法阶段,在施法阶段结束后,就收手了,所以施法后面接后摇。但龟派气功在施法阶段并没有产生效果,而是在施法阶段后,出手产生效果,再收手。这就是他们的区别。
##buff的事件
buff有5个事件,分别是添加事件、删除事件、叠加事件、完成事件和心跳事件。添加事件和移除事件就是在buff被添加和被删除时触发的事件,如果你要做这样一个技能
你只需要做一个buff,在buff的添加事件里,给buff的所有者增加50点攻击力,在删除事件里,给buff的所有者减少50点攻击力,并设置buff的持续时间为10秒,那么这个buff就做好了。
当单位上已经有一个同名buff,而又再次添加这个buff时就会触发叠加事件(在添加事件之前)。如果叠加失败,则不会触发添加事件。同样是之前的例子,如果你第一次使用之后3秒,再次使用这个技能,那么你希望发生什么事情?
这是最常见的四种buff的叠加模式。我们先来考虑更普遍的问题,实际上buff的叠加可以分为两类,共存模式和独占模式。共存模式即是所有同名buff都会共存,独立作用;独占模式即是同名buff在同一时间只会存在一个。情况1~3实际上都是独占模式,情况4是共存模式。情况1,实际上就是再次使用技能什么事情都没有发生,也就是说新buff不能覆盖旧buff;情况2,实际上就是再次使用技能会刷新buff的数据,也就是说新buff会覆盖旧buff。
在新buff添加到单位身上时,会依次触发单位身上同名buff的叠加事件,你需要在事件中返回新旧buff的优先级谁大谁小。(优先度的概念下面还会用到,现在你可以简单地理解为需要返回是新buff可以覆盖旧buff还是新buff不可以覆盖旧buff)所以,情况1和情况2的叠加事件,你只需要简单地返回false和true,就能达到效果。而情况3,实际上是让新buff失败,但同时更新了旧buff的数据。(顺便说一句情况2还有更优的解,让新buff失败,但把新buff的数据复制到旧buff上)而情况4是共存模式,你只需要把buff设置为共存模式,那么所有同名buff就会共存,互不干涉。
buff设定的时间到期后,会依次触发完成事件和删除事件,而被主动删除时,则只会触发删除事件。一般完成事件是用来做一些必须达到指定时间才能触发的效果。心跳事件是指在buff在持续时间内,会定期触发的事件,比如用来做流血、中毒之类的效果。
##buff的叠加模式
在叠加事件中,我已经提到了buff有两种叠加模式,共存模式和独占模式。我们来看一个更复杂的例子,在魔兽中,鞋子的移动速度是不能叠加的,只取最高值。如果我们要用buff来做,要怎么做呢?显然我们的目的是要让同名buff只有效果最高的有效。首先考虑用共存模式还是独占模式,虽然buff只有最高的有效,但是考虑到如果最高的那个buff被移除后要让次高的buff生效,所以我们不能用独占模式,必须要让所有的同名buff都留下来。那么答案就出来了,我们需要在添加事件、删除事件和覆盖事件中,遍历单位身上的同名buff,找到效果最高的移动速度,用它来修改单位的移动速度。接下来还有另外一个问题,因为单位身上有多个同名的buff(但实际上只有一个起作用了),我们还必须让生效的那个buff显示图标,而其他的不显示;亦或者再做一个纯显示用的buff,实际效果的buff都不显示。
是不是听起来有点复杂?所以对于这种buff,X-Editor里有更简单的做法。之前我有说过,覆盖事件实际上是比较新旧buff的优先级。独占模式下,只有优先级高的buff能留下来。而共存模式下,无论什么优先级都会留下来,但是所有的buff都会按优先级由高到低排序,我们可以把移动速度等价于buff的优先度,那么所有的同名buff就会按照增加的移动速度由高到低排列。此外X-Editor的buff还有一个参数,可以指定最多允许几个buff同时生效,共存模式下默认为无限个,但我们可以把它设置为1,这样就只有优先度最高的那个buff有效了。
|
|
考虑这种情况,有个buff你希望他是独占模式的,但是如果有两个相同的英雄时会出现什么情况呢,有可能两个相同英雄的buff就会相互覆盖。有时候是合理的,有时候却是不合理的。在X-Editor里,所谓的同名buff,实际上指的是buff的名字相同且buff来源也相同,所以两个相同英雄的buff之间不会相互影响。但如果你需要让不同来源但名字相同的buff被当作同名buff处理,你需要为这个buff指定为全局的buff,这样才会变成名字相同即为同名buff。
##模版buff
我们再来讨论流血、中毒等dot类的buff的做法。首先所有的dot都需要能独立作用,所以只能用共存模式,但共存模式有几个缺点,一是同名buff很多时浪费资源,二时大多数情况下我们只希望看到一个buff图标,所以我们可能需要做一个假buff来显示图标。实际上dot类的buff,我们也可以用独占模式做。我们需要把dot每一跳的伤害提前算好存在一个数组里;在叠加事件中,把新buff的每一跳伤害存到对应的数组位置上;在心跳事件中,我们只需要把数组中的对应的伤害直接拿出来用即可。这就是用独占模式来做dot的方法,不过我要说的并不是这个。我们可以看到dot并不是一个buff,而是一类buff,同一类buff有很多代码是相同的,在X-Editor中,会有很多这样的buff模版,你可以很简单地做出一个独占模式的dotbuff。(共存模式的dotbuff,太过于简单不需要模版)
此外在X-Editor中,光环和法球都是属于模版buff。光环就是一个会定时把自身添加给周围单位的buff,这个buff在非所有者身上时,只会持续很短的时间并且失去传染的能力。这是魔兽里圣骑士专注光环的完整实现
|
|
从WE的发展历程来看,模拟在越来越多地被使用。原因我归纳了下有这几点
所以现在和今后,我相信模拟会被越来越多的人使用。现在模拟的问题在于,开发的门槛太高,对很多人来说,从零开始做一个全模拟的系统过于困难。不过我相信随着时间的推移,这个问题一定会得到解决的。
说回正题,正如大家已经知道的那样,我正在做一个新的编辑器,为了方便描述,先把他称之为X-Editor,对应的游戏称之为X-Game。在X-Game中所有的技能都会用‘模拟’的方式来实现,这虽然会牺牲一点性能,但我认为这是值得的。同样地,现在有很多游戏公司使用WE来做原型设计,比如梦三国(不过那个是抄袭不是设计,哈哈),他们的方法一般是在WE上做好一张图,然后再移植到他们的编辑器/游戏上去,这个过程一般都是人工的。有没有更简单一点的方法呢,答案是有的,这也是YDWE在最近两年内一直在努力的方向,而Lua引擎正是这个连接魔兽和其他游戏的桥梁。届时,你可以用YDWE的Lua引擎做一张地图,然后代码可以在X-Editor上复用。
最后做个广告,现在90%以上的游戏公司会使用Lua来开发游戏,如果你想从事游戏相关的工作Lua是门必修课。如果你已经对WE很熟悉,用YDWE来学习Lua那真是再好不过了,希望YDWE真的可以帮到你们。
]]>得知我要走的原因,很多人不能理解,明明在11已经有开辟好的大片疆土,为什么还有去未知的世界冒险。可能我就是这么一个喜欢挑战的人,编辑器也是我一直想做的事情。两年前,我曾经说过有若干个编辑器在开发或者筹备中,然而除了DotA2编辑器都死掉了,而DotA2编辑器也一直难产中,我曾经做过的准备和计划一个都没用得上。所以当这个机会来临的时候,我又岂会轻易放过,与其等别人做,不如自己来做。当然这一切才刚刚开始,所以现在还没有什么可以跟你们说的。
最后说句题外话,最近DotA2的编辑器更新了一个版本,又有人来问我怎么看了。一直以来我都被贴着’DotA黑’这样的标签,好吧我承认我是。主观上,我对DotA并没有什么好或者不好的感情,因为我并不玩DotA/DotA2或者LOL。客观上,我并不认同DotA2的设计、策划等各方面。不过从今天起,我不再会对DotA2做任何评论,毕竟以前是自由身,所以说什么都无所谓,而现在别人会认为我在故意诋毁竞争对手,况且我也没必要帮对手改进产品不是吗。就让一切都用行动来说话吧。
]]>先来看一个蓝图的例子,效果是,按下”方向键下”,屏幕上会显示2、4、6、8、10。
为了让不懂蓝图的童鞋能理解,我做了与之等价的WE的触发
##第一印象
第一眼看上去,蓝图更为图形化的界面让人眼前一亮,各种鲜艳颜色的搭配也比WE略显凌乱的的界面更胜一筹。反观WE这边,文字显得比较多,这也跟WE是十多年前的产品有关。图形加色彩代替文字,这是近年来软件流行的趋势,这点上蓝图确实做得更好。
##阅读难度
然而,当我仔细考究蓝图中的这个触发时,却感觉这其中的逻辑让人很难理解,要知道这只是一个很简单很简单的触发,我很难想象一个复杂的触发要怎么看。为了说明这点,容我先对不懂蓝图的童鞋作下简单的说明。
蓝图中一个矩形框等价于WE中的一个UI。WE把UI分为了两大类action和call,action就是无返回值的UI,call就是有返回值的UI。在蓝图中也是这样,圆角矩形就是action,直角矩形的就是call,为了方便描述,我也把蓝图中的这两种UI称为action和call。在蓝图的action的左右分别有一个白色的简体(某些有多个),通过这个箭头你可以用白线把action都串起来(这类似于WE中UI之间的那条虚线),然后虚幻就是按照这点白线执行action。在上面的例子里一个有四个action,从左往右分别是”方向键下按下事件”、”循环”、”条件判断”、”显示文字”,这和WE里的例子是一样,大家和对比下。
那么有颜色的线条又是什么鬼呢,我们知道在WE里一个UI会有若干个输入参数,而在蓝图里的表现就是白色箭头下的带颜色的小圈圈,左边是输入,右边是输出,你可以把输出的圈和另一个UI输入的圈连在一起,这样就把一个参数传递了过去。在WE我们通常会把需要使用多次的值存到变量里,变量就是连接不同UI的线条。而在蓝图中我们不需要变量,下图是一个创建单位的UI,可以看到在右侧就有一个圈,这就是创建出来的单位,当你需要用这个单位时,只需要把线练到这里来。
好了分析完毕,如果你已经理解我上面的说明,应该已经能看懂图1了。即使看得懂,我仍然觉得难以理解。首先,我认为蓝图的action UI做得不多,如果只保留action UI,你可以很容易看出触发的执行流程,尤其是有各种循环、分支的情况下,action可以一眼就是清楚输入参数和输出参数,这点也很不错。然而加上call,问题就来了。图1中有三个call,上面两个分别是摸和判断整数相等,这两个显然没WE中的 循环整数A mod 2 == 0来得清晰。由于call不需要有分支,所以图形化的展开反而增加了理解的难度。此外加上call之后,蜘蛛网一样的线条也会增加理解难度。
##操作便利
在WE中,写call是一件痛苦的事情,一个action里可能会嵌入十几层的call,如果你想修改第十层的内容,你只能一层一层地把UI点开。这还不算什么,如果我想把第二层的UI换成另一个,第三层之后的UI保持不变,怎么办?你只能把第二层之后的UI全部重写一遍。你想把一个call复制到另一个地方,怎么办?重写一遍。而在蓝图中,这些都很容易做到,因为在蓝图中action和call都是平铺在一起的,你可以像复制action一个复制call,操作action一样操作call。
##小结
蓝图之于WE的变化
历经多年的开发,YDWE已经很强大,基本覆盖地图编辑的方方面面。不过也有一点小遗憾,多人协作开发。如何让一张地图可以让两个或者更多的人同时开发,想必是很多地图制作小组都遇到的问题。在年初的新年计划上,其实我已经有了开发的计划,可惜因为各种原因,我已经没有太多的时间和精力来开发YDWE,这个功能也就此搁置了。不过令我感到欣慰的时,全明星战役的作者已经在他的地图中实现了这一点,让我们来看看他是怎么做的吧。
多人协作开发,在软件开发中并不是一件很困难的事。这得益于强大的版本控制工具 ,开发者可以分别独立开发自己的功能,最后通过版本控制工具合并为一个版本。不过版本控制工具只支持文本格式文件的管理,对于mpq、w3u、w3i这种私有格式的文件,版本控制工具显然是无能为力的。不过我们可以把这些文件全部转为文本格式的文件,那么版本控制工具就可以对我们的地图进行版本管理了。
全明星战役正是这样做的,它会把w3x文件解压为一个文件夹,包含地图内的所有文件,而w3u、w3i这些则会进一步转换为文本格式文件(txt)。当你需要编辑的时候,先把这个文件夹转换为一张完整的w3x地图,用编辑器打开编辑,编辑完毕再转换回一个文件夹,在版本管理工具里提交。如果只是简单的修改物编,你甚至可以不用打开编辑器,直接用记事本修改即可。如果你尝试过这种编辑地图的方式,我想你会爱上它的。
当然我我认为全明星战役还做得不管简便,如果可以直接打开一个文件夹,保存的时候也直接保存为一个文件夹,那就更方便了。
听起来,全明星战役怎么像是一个地图编辑器而不是一张地图?记得我曾经说过一句话,没有人使用了YDWE中5%以上的功能,这句话在今天看来依然适用。YDWE最核心的功能其实是高度可定制化,全明星战役就是这点的最好例子。另外,全明星战役开源的,有兴趣的童鞋可以前去围观,围观地址 。
全明星战役还有很多值得让人学习的地方,比如动态补丁、录像调试等功能,时间有限,我们下次再聊。
]]>NewTesh 已经出现很久了,不过一直对中文的支持不好,作者又是个傲娇,不肯放出源码,所以一直没放到YDWE里。不过功能还是很强大的(和旧的TESH比),详细可以看我贴吧里以前发过的贴。直到现在NewTesh还是只会搜索英文的触发编辑器窗口,为了兼容它,1.28里的YDWE的触发编辑器窗口名也改成了英文了,这个影响应该也不大,等NewTesh的作者肯放出源码再改吧。
可能熟悉我的人已经知道了,我已经离开11了,所以这次更新的JAPI大概也不会出现在11上,它是YDWE用户的“专属特权”!不过我听说腾讯对战平台已经支持YDWE的JAPI,但这事腾讯的人并没有知会我,他们会不会跟进我就不知道了。话说回来,虽然YDWE是开源的,但也是有“游戏规则”的,不是大街边上的无主之物,随意拿取。当然我也知道对流氓公司说“游戏规则”只是对牛弹琴,我也就吐个槽,没想怎么着。
这半年发生了很多事,也领悟到了很多东西,好的和不好的。跟你们说一个故事,同样一件事,A能力很强,一个人就能搞定,所以一直都是他一个人做,三年后他还是一个人在做;另一个人B他自己一个搞不定这件事,所以上头还给了他两个小弟,他们三个人一起才勉强能把这件事做好,三年后,B已经在领导几十个人的团队了。在A的领导看来,这件事不算是很难的事,因为一个人就够了。在B的领导看来,B是一个很厉害的人,因为他领导了几十个人把一个需要几十个人才能做的事做好了。希望大家不要犯A的错误,共勉。
最后,还在WE上努力各位,希望YDWE仍然能够帮得上你们的忙。
]]>ydwe是一个开源软件,欢迎有能力的童靴到GitHub上提供你的PR(pull request),或者fork。事实上ydwe已经了有了一个非官方的fork,里面提供了比官方版更多的japi函数。
正事说完,我们来聊聊别的东西吧。今年发生了很多事情,比如说,最近我已经从一名windows客户端的程序员转职成了一枚linux服务端的程序员,虽然后缀还是一样,不过其中的差别远比想象中的大。我在windows客户端这个领域上,不敢说登峰造极,也算是方方面面都能搞定的程度了,不过对于大多数没有追求的公司而言,客户端的事情,能凑合用就行,厉害与不厉害,对他们来说不重要也不关心。客户端程序员挺悲惨的,我虽然一开始选的路错了,但现在意识到应该还不算太晚。在linux上,我还是一只菜鸡,要学的东西太多太多了,比如最基础的linux上的各种命令还有vi的使用等,以前总是觉得vi是一个很反人类的东西,现在也渐渐地觉得在linux上vi确实是个不错的编辑器。
事情总是变化得太快,曾经我对新一代的编辑器充满着期待,比如DotA2编辑器、三国争霸2编辑器、众神争霸编辑器等,甚至还有YDWE和这些编辑器打通的计划并且还做了一些前期工作。不过也是最近我得到了一些不太好的消息,以至于那些编辑器以后会不会出现都成为了问题。未来的事谁说得好呢,指望那些掉到钱孔里的公司们永远都是一件不靠谱的事情,我们至少还有魔兽编辑器和YDWE。
]]>##1.准备工作
最新版本的YDWE
建议在配置中关闭预处理器,因为它和Lua的一个符号有冲突,当然你也可以选择回避使用这个符号.
##2.入口
使用
这段代码等价于你在地图内导入了一个名字为”filename”,内容为”script”的脚本
你可能迫不及待想要尝试写Lua了,不过在这里我要建议你在运行自己的Lua脚本之前,先单独运行一次以下代码(通过
这段代码的效果是打开一个控制台窗口,这样如果你的其他Lua代码出现了错误,控制台窗口中将会显示出错误原因和错误位置,非常便利.此外通过Lua的print函数,你也可以将各种内容显示在这里
##4.hello world
没错,第一步是hello world
|
|
控制台窗口中将会显示”hello world”
##5.Lua的基本介绍
lua是一个简单,高效,强大的脚本语言,其语法和jass有很多相似之处,因此有jass基础的话可以很快掌握lua
lua和jass最大的不同在于lua不需要定义变量类型,例:
|
|
在这个例子中,a的类型分别为数字(number)与字符串(string)
lua的注释符为
####在lua中有以下几个类型:
nil 空值,任何变量在赋值前都是nil,给变量赋值nil相当于摧毁该变量,类似于jass中的null
boolean 布尔值,与jass一样包含true与false
number 数字,lua中的数字没有整数或实数之分,因此在lua中 5 / 2 == 2.5
string 字符串,与jass中的字符串基本相同
table 表,lua中最强大的类型,他可以简单的当做数组或哈希表使用,更是lua构成复杂的高级功能的基础,这将在之后重点学习
function 函数,与jass中的函数不同,lua中的函数也被视为一个值,你可以随时将它赋值给一个变量,或在其他函数中作为参数或返回值传递
userdata 自定义数据,你可以将其理解为jass中的handle,lua无法直接对其进行修改
####lua有以下几个保留字:
|
|
与jass很像不是吗?
##6.值之间的操作
####运算
lua在必要的时候会自动转换类型,因此需要特别留意字符串是通过 .. 来连接的
此外lua还自带了 幂(^) 与 取模(%)
####逻辑
lua的if结构与jass相似,只要注意endif要改成end
第2个条件中,只有当x的值为nil或false才不成立,其他情况包括0或””都是成立的
##7.变量
lua对变量进行操作时不需要 set 关键字
lua使用全局变量无须事先声明,任何时候
局部变量可以在任何位置声明,影响范围由声明的位置而定,例如:
将依次显示20与10
记住,lua不需要写变量类型哦
你可以一次声明多个变量
|
|
##8.字符串
lua的字符串有3种符号,例:
其中用[[]]表示的字符串将忠实的记录你输入的文字,忽略其中的任何转义符.如果你
需要注意的是,[[]]形式的字符串可能会与你的脚本产生一些冲突,例如
你可以改写成如下的形式
这样lua只会把拥有相同等号数量的括号认为是一组
同样的,你可以将YDWE内写lua的代码改成如下代码:
以回避一些冲突
##9.简单循环
lua有3种简单循环方式
####① for循环
最常用的循环方式
直接看例子
|
|
将依次打印 1, 2, 3, 4, 5
你也可以指定循环的步长
|
|
将依次打印 1, 3, 5
需要注意的是,你无法通过修改i的值来控制循环
####② while循环
当条件成立时进行循环
|
|
####③ repeat循环
当条件成立时退出循环
|
|
以上三种循环你都可以使用break来提前退出,常见的用法有
|
|
需要注意的是,break必须放在一段代码的最后面
|
|
##10.简单的数据结构
lua只有一种数据结构:table(表)
|
|
任何非nil的值都可以作为table的索引
|
|
在lua中,可以将
你可以在创建table的时候直接定义其数组内容,注意lua中的数组索引是从1开始的(jass是0),多个元素之间需要用逗号隔开
|
|
通过嵌套table,你可以很简单的构造出多维数组
|
|
随着你对lua的深入,你可能会越来越多的使用这样的结构:
|
|
##11.函数的声明
lua的函数声明有2种形式:
|
|
|
|
这2种声明方式是完全等价的,都是声明了一个名字为”func”,参数为”a, b, c”的函数
显然这里的参数不需要填写类型,函数也不需要声明返回值的类型,顺便将endfunction改为end即可
从第二种声明方式中我们可以看出,lua中的函数是可以赋值给变量的:我声明了一个新的函数,然后将其赋值给变量”func”
一个简单的函数重载:
|
|
既然是变量,那么他也就可以局部化:
|
|
lua不限定函数的返回值,你可以返回或不返回;返回一个或返回多个;返回布尔或返回整数:
|
|
需要注意的是,与break一样,return必须放在一段代码的最后面
##12.函数的调用
lua调用函数显然是不需要写call这个关键字的,而且函数作为一个变量,只要他被赋值了你就可以去调用他,因此也没有声明顺序的问题
不过你依然需要关注一下局部函数的有效范围:
|
|
正确的写法应该是
|
|
lua不关心你调用函数时的参数是否与函数声明时的参数一致,多余的参数被抛弃,不足的参数为nil,类似于多变量赋值
|
|
同样的,一个拥有多返回值的函数也有类似的情况:
##13.表的简单操作
jass中是直接定义某个变量为数组的,而lua中则是将一个表赋值给一个变量,显然表也可以作为一个普通的对象进行传递
|
|
这张表分为了2个部分,数组与哈希表.lua对数组的定义是从索引1开始的连续元素,因此t[0]是哈希表部分,而将t[3]截断后,之后元素不再连续,此时的数组大小便为2.我们可以通过符号#来获取数组部分的长度
|
|
有2种简单的方式来遍历表的数组部分
|
|
第二种方式也是循环的一种,既然循环,他也可以用过break来提前退出
lua也提供了1种简单的方式来遍历表的所有元素
|
|
在例子中我之所说是可能显示,是因为哈希表部分的遍历顺序是随机的,与你存储的顺序无关.不过他依然会按照顺序先遍历出数组的部分
lua提供了2个常用的插入/移除函数:table.insert与table.remove
|
|
##动手步骤
下面我们开始动手。我们需要的工具很简单:
一个十六进制编辑器(例如WinHex,Hexworkshop,Ultraedit之类)
准备好工具后,正式开始我们的修改。修改步骤如下:
完毕,很简单吧。1.24e版本的game.dll一共要替换3处地方。
上述方法适用于1.24a以及以后的所有魔兽版本。
##附注
##编后语
本文成文于两年前,而今已经有不少平台支持本补丁了,比如11平台的大地图房间,HF貌似也有某些房间支持。如果你想让你的ydwe也支持本补丁也是一件很简单的事情,ydwe支持魔兽补丁功能,你只需要在share/patch目录下新建一个目录(比如就叫8mb补丁),然后按本文把修改过的game.dll放到这个目录下,再从配置程序中选择加载此补丁即可。
以下是我在msvc2010下编译php5.4.27的记录。
##坑之ZTS
php默认启用ZTS(php线程安全),但ZTS宏却不是默认开启.如果你编译php时没有关掉ZTS,那么使用php api时会得到以下错误
你需要自己定义ZTS
##坑之PHP_WIN32、ZEND_WIN32
完全想不出不通过_WIN32宏来生成PHP_WIN32、ZEND_WIN32宏的理由,总之你是需要自己来定义了。还有为什么会有PHP_WIN32和ZEND_WIN32两个宏呢。
另外,虽然php不会理会_WIN32宏,但却会理会_WIN64宏。
##坑之ZEND_WIN32_FORCE_INLINE
如果说之前的坑只是懒,那这个坑只能说是逗了。让我们来看看ZEND_WIN32_FORCE_INLINE都干了些什么。
注意,默认是没有定义ZEND_WIN32_FORCE_INLINE的,所以当你引用了php的头文件,你代码中inline就会全部失效。c++标准库的头文件中也使用了inline,所以就算你没用inline,一样会让你的代码无法编译。错误通常为
所以ZEND_WIN32_FORCE_INLINE宏必须被定义,或者删掉这段略逗的代码。
##坑之_USE_32BIT_TIME_T
如果你没定义_WIN64宏,那么php会帮你定义
但msvc默认是不定义这个宏的,所以当你在未引用php头文件时,使用c++的头文件,使用的是64位time_t,引用php头文件后就变32位time_t了。错误通常为
或者
解决方法,删掉这段代码。
##坑之ZEND_DEBUG和UNICODE
_zend_executor_globals是php中一个很重要的结构体(也就是EG(xxx)),这个结构体中包含了一个OSVERSIONINFOEX成员,而OSVERSIONINFOEX的长度会根据UNICODE宏的定义与否有所不同,php默认的编译选项是没有定义UNICODE宏的,如果你的工程定义了UNICODE宏,那么你就会获得很多迷のbug。ZEND_DEBUG也会有同样的问题,如果你编译时有–enable-debug,就必须定义ZEND_DEBUG,反之就不能定义。
php完全可以增加编译时的检查,以保证不会有类似的abi错误,但它没有这么做。
##坑之导出的全局变量
你可能无法想象,php不但很随意地使用了全局变量,并且还很随意地把它直接导出了。这种莫名其妙的设定遍地都是,不过我今天要说的是编译的坑。
表面上看,你在c++里直接使用php的头文件并没有什么问题,因为php在每个导出函数前都加了extern “C”,以保证c++编译器能找到php导出的函数。但这个并没有包括它导出的全局变量,所以当你在c++中使用它导出的全局变量时,就会得到一个链接错误。所以你必须给每个你要引用的php头文件加上extern “C”,就像这样
显然它原来自己加的extern “C”就没用了,倒不如你就像lua那样宣称我就是要用c,你要用c++就自己加extern “C”得了,做事做一半就不管了,算是个什么意思。
##坑之debug模式
当你编译为debug模式的php,php会为你做很多额外的检查,这本来是一件好事,但它最通常的做法就是直接崩掉,没有任何有用的提示;关掉debug模式的话,是ok的(至少表面看起来ok)。对于这种暴力的提示错误方式,我真不知道该如何招架,或许就php开发组的人玩得溜了。
最后吐槽下php的代码到处都充斥着丑陋的TSRM宏,无论是否开启ZTS,tsrm_ls都应该作为一个外部参数传入,php不应该也没必要在内部保留自己的状态,无论是否开启ZTS。
由于2048游戏的火热,不同衍生版层出不穷,yichaodong童靴也做了一个《魔兽争霸III》版2048。不仅还原了原版的2048,还加入了具有魔兽特色的人族版2048,不过我个人对龙鹰是2048,狮鹫是1024的设定存有异议,明明是狮鹫比较贵啊。
##地图下载
请用ydwe1.25.8以上的版本打开
##题外话
看到很多童靴还在1024和512里挣扎,下面分享两个玩2048技巧,掌握了的话,标准的4*4玩到2048基本没什么难度了。
2 | 32 | ||
---|---|---|---|
4 | 16 | 128 | |
16 | 64 | 512 | |
2 | 8 | 256 | 1024 |
2 | 4 | 32 | |
---|---|---|---|
4 | 16 | 128 | |
16 | 64 | 512 | |
8 | 256 | 1024 |