找回密码
 点一下
查看: 34559|回复: 85

JASS教程—0基础新手向教程;内容多打开可能较慢;共十八章完结;2014/11/24进行了维护

[复制链接]
发表于 2011-7-12 03:35:35 | 显示全部楼层 |阅读模式
本帖最后由 chyj4747 于 2016-3-9 23:15 编辑

具体更新内容请看顶楼的更新说明~
沙发是导航楼,如果不是的话说明浏览器还没读出首页的内容,请耐心等待~
如果等了一段时间还没刷出来就点一下第二页试下~     


说明:教程导航详见沙发,不过看之前请先看下顶楼的“补充”部分
常言道:前言就是废话,废话即可忽略。
于是我决定不写废话,我写序言~

序言
      很多教程(即便是所谓新手向的),一上来说的内容基本上是“该编程语言是什么样的语言”、“该语言的特色”、“运算符”及“常量&变量”(各种讨论变量的概念、变量的类型及不同变量的区别),其实那种教程我个人认为更适合给知道编程是个什么东西,但是却不知道怎么编的人看。新手们如果连编程的概念都没有,看那些东西马上就晕。
      我自己以前0基础的时候看过C语言的入门教程,那是神马入门啊。。上来讲的第一个内容是C语言发展历史。。我想问这跟C语言的使用有关吗。。就算有,比如几个有趣的历史BUG,那也要等学有所成的时候才看得懂吧。。
      所以我个人认为那样的教程反而会增加0基础的新手们学编程的难度,在该教程中,我会尽量以我自己学编程的经验,写出能使0基础的新手们也看的懂看得明白的JASS教程~
     

先附上些其它我觉得比较好的教程,当然我不可能看过全世界所有的教程,以下仅是我看过的觉得蛮好的教程:
PS:以下有些内容需要新人们有一定的基础,否则不推荐看,去看的后果只有一个,你会晕的……

以下是传送门:
冰块前辈的教程冰块教你学JASS
(该教程也比较适合新人。其实冰块前辈的教程也是驱使我来写这篇教程的原因,因为冰块写到重要的地方就不更新了……)

U9的acomc的教程(他在GA也叫这个名字)[实用Jass/vJass入门] 只学简单有用的, 其他无视
(计时器和哈希表的使用,我是从这篇教程学的,不过因为我学之前已经有编程基础了,看得还挺轻松的~)

血戮魔动冰前辈的哈希表教程Hashtable教程~
(非常详细的哈希表教程,但是。。如果没基础,还是先别传过去送了吧……)
     

写JASS的工具
1、JassCraft(我现在在用这个和YD,下载地址的话大家先自己搜下吧。。以后我再弄链接)
2、JassShop
3、YDWE较新版本
(关于YD我要说下,既然准备学Jass了那么就推荐使用YD,即使你是新手,但是除了触发器中“局部变量”和后缀带有<NEW>的比如“单位-添加技能<NEW>”之外,其它的比如“跳跃函数”、“冲锋函数”,还有什么各种强大的计时器功能(不包括普通计时器功能)等普通WE没有的都不推荐使用,宁可自己花一个礼拜甚至更长的时间做,做不出来去各大论坛问,也不要使用这些“便利”的功能,不是说这些功能不好,而是你要自己先会做了,之后用不用那就是你自己的事了。PS:相比T中原动作,我更推荐使用后缀带<NEW>的动作,因为效率更高,而且中文的翻译更加符合中文逻辑,至于为什么效率更高,接下来的教程中会有讲解)

工具使用方法
在WE的触发器中新建触发器,然后 编辑->转成文本格式,删掉那些英文就可以开始编了。若不是在WE中,其它工具比如JassCraft,那么先新建一个文本,编好后复制黏贴到WE中,其实跟在WE中编写是一样的,但是各有各的好处就是了,我个人比较喜欢JassCraft。至于WE选择YD是因为YD的编写JASS功能比别的我见过的UI要强大,还有JASSHELPER能检查语法,无需另去下载,就这样。
     

最新更新说明:2011/08/16  在第十八章补充了StringHash()      
                          2011/10/11 修正了第六、七两章的错误(感谢archxm指出~)
                          2011/12/29 进行了维护修正工作~(调整了教程的部分说明、修正了几个错别字)
                          2012/03/07 进行了维护修正工作~
                          2014/11/24 进行了维护修正工作~(GA换引擎后[jass]和[trigger]标签都失效了,换成[code];并且修复了导航楼链接失效的问题)
  

补充:首先,教程一般不会短到哪去,看的时候要有耐心~ 虽然我是以我认为的最简洁易懂的方式来写的,但长度是不可避免的。
对教程有什么意见或建议尽管提~ 不过当然是有用的……纯灌水等无益的还是算了
当然了,由于是第一次写这么长的教程,肯定会有疏漏的地方导致误解或无法理解,这些只能在发现之后做修改了,无法做到一步到位,发现错误的同学请立刻提出来~谢谢
PS:最后我还是决定不教库&域和宏,尤其是宏,虽然不难,但是入门者最需要的是先掌握基础,我认为这两个都是属于进阶物,所以就不放在这个基础教程里了~
不过库&域这个东西如果同学们需求量大的话我可以简单讲下……或者我建议你们去看acomc的教程(也算是新手教程吧,链接在上面),库&域的讲解在他那篇教程的最后部分

如果有些图片显示不完整或JASS过长,请切换到论坛的“宽版”模式(在顶楼的右上):
QQ截图未命名.png



 楼主| 发表于 2011-7-12 03:44:40 | 显示全部楼层
回复

使用道具 举报

 楼主| 发表于 2011-7-12 03:44:59 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:27 编辑

第一章:魔兽程序的概念
先举个非常简单的例子:计算器
当你输入 1+1= 之后,就会出来数字2
其中 1+1= 一共两个数字两个符号,是需要输入的东西,在程序中叫做参数,得到的答案2(即计算器运算程序计算后得到的值)叫返回值

那么这个程序在JASS中是什么样子的呢?
首先,我们必须输入一个特定的东西(单词、符号之类的)来告诉电脑,接下来的这串内容是JASS程序。在JASS中,用function这个单词来开头。(这个单词是函数的意思)
也就是:
  1. function  xxxxxxx
复制代码

于是电脑就知道这个function后面的那串x是程序,接下来用到这个函数的时候要按照这个里面写的来执行。

现在回到刚才 1+1=2 这个例子,之前说了,参数是需要输入的,但是输入的参数可不止 1+1= 这么些,也可能是 1+2= 、2+5= 等等。也就是说,输入的参数是“第一个数字”“运算符”“第二个数字”,然后等号的意思就是告诉电脑该出返回值了。

与之前function同理,必须用一个引导词告诉电脑接下来是参数,以及用另一个引导词告诉电脑这个函数有一个返回值。
JASS中的写法就是:
[jass]function takes 第一个数字,第二个数字 returns 一个数字[/jass]
其中takes就是参数的引导词,retrurns就是告诉电脑有返回值,不同的参数之间用逗号隔开。

突然发现上面这个函数没有“运算符”这个参数,并且仅仅是告诉了电脑有一个返回值,但究竟怎么得到这个值电脑还是不知道。
于是,我们要把运算过程告诉电脑:
  1. function takes 第一个数字,第二个数字 returns 一个数字
  2.     return 第一个数字 + 第二个数字
复制代码

这个函数第二行的意思就是:返回  第一个数字加上第二个数字所得到的答案;也就是,返回值 = 第一个数字 + 第二个数字

至此,这个加法函数的功能就编完了,但是JASS还不知道这个函数已经结束了,怎么办?
……对了,用个引导词告诉它这个函数结束了就行了~
在结尾加上endfunction,即
  1. function takes 第一个数字,第二个数字 returns 一个数字
  2.     return 第一个数字 + 第二个数字
  3. endfunction
复制代码

PS:return前面空四格是很好的编程习惯,为了下次看的时候能很清楚地区分函数的开头结尾和中间的运算过程。这种空格在编程中叫做缩进(indentation)。
小技巧:当光标在最前面时,按TAB键直接缩进四格。

最后,就是使用这个函数的方法了,并不是写成  1+1  这种数学式,而是
这个函数(第一个数字,第二个数字)这种形式,
比如 1+1 就是 这个函数(1,1),但是每次都叫“这个函数”很傻,函数多了电脑也不知道我们说的到底是哪个函数,所以给每个函数一个名字也是必须的。

既然这是加法那就叫“add”吧,同时,JASS程序中是不能用中文的,所以换成英文(num是number的缩写):
  1. function add takes num1, num2 returns num3
  2.     return num1 + num2
  3. endfunction
复制代码

PS:“num1 + num2”中间没有空格也行,即“num1+num2”,现在不用刻意去记哪些需要哪些不需要,学多了看多了写多了就知道了。

于是,使用这个函数的时候就写成add(num1,num2)的形式就好了。
看到这里估计有些同学已经联系不到魔兽了。。现在联动一下:
把这个程序应用到魔兽中的话,使用add(x,y)后就可以得到x+y的值了(x,y为输入的两个数字)。比如用来增加HP,那么x是现有HP的值,y是增加的量,算出来后就是增加后的HP了,然后设置单位的HP为计算后的值。这是魔兽程序中最最基础的运行方式,当然还需要“事件”来触发启动这个函数,之后会讲到~

最后一个PS:本章只是给0基础的同学们一个JASS程序的概念,跟JASS用于魔兽的各种写法还是有一定差距的;另外,上面这个函数还是少了一点东西,下一章节会讲。

本章完~                              
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 03:45:18 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:27 编辑

第二章:第一章续——函数的完整写法
先把上一章节的函数搬过来:
  1. function add takes num1, num2 returns num3
  2.     return num1 + num2
  3. endfunction
复制代码

上面这段函数呢,魔兽还是看不懂的,因为我们还没告诉它这些数字的类型,不告诉它JASS就不肯运行。(所谓类型,就是比如整数、实数这种,当然JASS中对于数字也只有这两种。)

那么怎么告诉它……用引导词:
比如 1+1 什么的都是整数的运算,那么这两个参数(num1和num2)就是整数类型。(不同的数字类型现在不考虑)
整数这个词在英文中是integer
  1. function add takes integer num1, integer num2 returns num3
  2.     return num1 + num2
  3. endfunction
复制代码


如果是实数呢,就用real
  1. function add takes real num1, real num2 returns num3
  2.     return num1 + num2
  3. endfunction
复制代码



我们还发现,returns后面是一个具体的数字num3,这要怎么返回一个任意值?
所以返回的不是一个数字,而是这个数字的类型
整数:
  1. function add takes integer num1, integer num2 returns integer
  2.     return num1 + num2
  3. endfunction
复制代码


实数:
  1. function add takes real num1, real num2 returns real
  2.     return num1 + num2
  3. endfunction
复制代码


至此,这个函数才是真正地写对了。

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 03:45:43 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:27 编辑

第三章:条件判定句——基础

(这名字是我自取的。。其实叫什么判断式之类的都行。。)

首先,什么是“条件判定”?
非常简单,比如“2是否大于1”,这个就是条件,之后对其判定,发现这个条件是对的(有谁敢说这是错的吗……站出来!)。这整个过程就是“条件判定”。

那什么是“条件判定句”呢?
也是非常简单的~句型如下:
如果(某个条件对了)那么(做某些动作)否则(做另外一些动作)
对应到游戏中呢,就比如“如果(你挂了),那么(你输了),否则(你赢了)”
这就是“条件判断句”的基本句型。

那么放到JASS里是怎么样呢?
我们先为前两章用的加法函数加些条件,以及符合条件后相对应的动作:
(为了减少打字数,我假设A=“第一个数”,B=“第二个数”)
条件:如果A > B,那么A-B,否则A+B

        这里就要说到那些“比较符”了,就是“大于”、“小于”、“大于等于”、“小于等于”、“等于”、“不等于”这些,前四个在JASS中对应的就是“>”、“<”、“>=”、“<=”
      “等于”和“不等于”在JASS有些不同,“等于”“==”“不等于”“!=”,不等号这个好理解,大家在键盘找一圈就明白了,没有把等号划掉这种符号;但是等号为什么要两个“=”呢?因为在JASS中有一个更加常用到的东西占用了一个“=”(之后讲变量时会讲),所以等号就只能是“==”了。
      另外“不等于”在JASS中还经常表现为“not  A==B”这个形式,按字面理解就是“不是A等于B”,虽然有点绕但却也是种常用的写法。(注意:not是个关键词(与引导词类似),需要与前后的内容空开)

现在回到刚才说的加条件那,把那个判定式加入函数看看是什么样的:
  1. function add takes integer A, integer B returns integer
  2.     如果 A > B 那么
  3.         return A – B
  4.     否则
  5.         return A+B
  6.     结束判定句
  7. endfunction
复制代码

我们发现,判定句跟function一样有起始和结束引导词,中间还有“那么”和“否则”这两个过渡引导词,转成英文来看:
  1. function add takes integer A, integer B returns integer
  2.     if A > B then
  3.         return A – B
  4.     else
  5.         return A+B
  6.     endif
  7. endfunction
复制代码

(注:else本意是“其它”,这里指代其它情况,即不是A>B的情况下)
然后不难发现,结束引导词都是“end+起始引导词”的形式,Jass中所有的起始和结尾引导词都是这种写法

“条件判定句”基础部分完~
回导航楼
回复 1 0

使用道具 举报

 楼主| 发表于 2011-7-12 03:47:14 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:28 编辑

第四章:条件判定句——进阶部分(1)

像第三章的写法,不难发现其实只能有两个条件,比如第三章的例子中就是A>B和A<=B这两种,如果需要更多的判断怎么办?
比如:如果A==1,那么……,如果A==2,那么……,如果B==1,那么……,否则……
这明显一个判定式远远不够嘛……

于是有人就想了,既然一个判定式是 如果(条件)那么(动作)否则(另外的动作),那么在“否则”里再加判定式,即
如果(条件1)那么(动作1)否则(如果(条件2)那么(动作2)否则(如果(条件3)……))
恭喜这位同学回答正确~但是你看着晕不晕啊?……

这种情况是有两种常用写法的。
首先第一种
在JASS中,如果一个函数你不给它写任何的运算式,这个程序不会有任何功能,即无动作,比如下面这个
  1. function F takes integer A returns nothing
  2. endfunction
复制代码

(注:nothing就是指 返回值==没有,也就是没有返回值)
这是个正常的可以用的函数,只不过没有任何功能罢了。那么条件判定句中也是如此,“那么”和“否则”之后你不加任何东西的话就是“无动作”

于是多个条件就能写成:
如果(条件1)那么(动作1)否则(),如果(条件2)那么(动作2)否则()……
是不是看起来稍微好了点。。。
放到JASS中:
  1. function add takes integer A, integer B returns integer
  2.     if A==1 then
  3.         return ……
  4.     else
  5.     endif
  6.     if A==2 then
  7.         return ……
  8.     else
  9.     endif
  10.     ……
  11. endfunction
复制代码

然后,这个判定句还可以再优化下,去掉那些多余的没用的东西:
  1. function add takes integer A, integer B returns integer
  2.     if A==1 then
  3.         return ……
  4.     endif
  5.     if A==2 then
  6.         return ……
  7.     endif
  8.     ……
  9. endfunction
复制代码

:else本身就不带动作,所以不写对函数本身不造成任何影响

现在来看看第二种写法
其实用中文的话可以这么说:
如果(条件1)那么(动作1),非之前的情况下如果(条件2)那么(动作2),非之前的情况下如果(条件3)那么(动作3)……

有木有发现这个“非之前的情况下如果”很牛呢~相当于之前的条件全错的情况下,如果下一个条件成立就怎么怎么样……

如果JASS中有就好了啊~~~~~
JASS说:“我还真有 = =”
稍微加大点跨度,直接加到函数转成英文:
  1. function add takes integer A, integer B returns integer
  2.     if A==1 then
  3.         return ……
  4.     elseif A==2 then
  5.         return ……
  6.     elseif B==1 then
  7.         return ……
  8.     ……
  9.     else
  10.         return ……
  11.     endif
  12. endfunction
复制代码

:这个 elseif 就有“非之前的情况下如果”的效果,同时,因为它有“if”的效果,需要“then”作过渡引导。
PS:当然了,上面这个函数若else没有动作的话也可以省掉不写。

是不是觉得这第二种方法好看很多呢~
当然这两种方法在这个情况下是可以通用的(我推荐第二种写法,因为简单好看~)

注:以下内容若看不懂可以直接忽略,以后直接会懂的,不用再来看
以上两个方案并不是哪都通用的。方法1呢,是在需要按顺序判断的情况下并且动作不在一个系统里面时用的,比如下面这个J(JASS的简称)
  1. function F takes …… returns ……
  2.     if 单位生命值>100 then
  3.          ……
  4.     else
  5.          ……
  6.     endif
  7.     if 单位魔法值>100 then
  8.          ……
  9.     else
  10.          ……
  11.     endif
  12. endfunction
复制代码

很明显在判断HP时如果把判断MP的判定式用elseif套在里面会直接跳过MP的判断,也就是当HP大于100时,就无视了MP的判断。

但是,如果是“单位等级==1时” “单位等级==2时”……“单位等级==n时”这种条件,用方法2的写法就不会做多余的判断,比如单位等级为1,那么判断为1之后就不会接着判断等级不为1的情况

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:27:06 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:28 编辑

此章节于2011/07/14修正了错误
第五章:条件判定句——进阶部分(2)

如果大家忘了判定句的内容呢,回前一章回顾一下吧~


除了第四章所讲的多个判定句的情况呢,还有一种比较常见的情况:
不是每个条件成立对应一个动作,而是多个条件成立了才做一个动作
比如:要同时A==1,B==1的情况下,才把A和B加起来,别的情况不做动作。

像这种需要两个或更多条件成立才做动作的呢,可以用一个关联词“and”进行连接,也就是:
如果(第一个条件成立)并且(第二个条件成立)并且……并且(第N个条件成立),那么(动作1),否则(……)
其中,“并且”就是关联词“and”,放到JASS中看下:
  1. function add takes integer A, integer B returns integer
  2.     if (A==1) and (B==1) then
  3.         return A+B
  4.     endif
  5. endfunction
复制代码

:A==1与B==1不一定要括号,这里是为了区分清楚。

以上是两个或更多条件同时成立的情况,如果要众多条件中的任意一个成立呢,就用另一个关联词:or
  1. function add takes integer A, integer B returns integer
  2.     if (A==1) or (B==1) then
  3.         return A+B
  4.     endif
  5. endfunction
复制代码

这段函数的意思就是当A==1或者B==1时,返回A+B,否则没动作

---------------------------------------------  进一步进阶的分割线  ---------------------------------------------

AndOr是可以混用的,但是一定要注意括号的使用
直接举例说明吧:
如果(A==1并且B==1)或者(A==2并且B==2),那么(A+B),否则无动作
  1. function add takes integer A, integer B returns integer
  2.     if (A==1 and B==1) or (A==2 and B==2) then
  3.         return A+B
  4.     endif
  5. endfunction
复制代码

PS:如果去掉条件中的括号,整个条件就很混乱了……

如果(A==1或者B==1)并且(C==2或者D==2),那么(A+B),否则(C+D)
  1. function add takes integer A, integer B, integer C, integer D returns integer
  2.     if (A==1 or B==1) and (C==2 or D==2) then
  3.         return A+B
  4.     else
  5.         return C+D
  6.     endif
  7. endfunction
复制代码

中文的意思非常好理解吧,如果直接写程序会弄错的话,先想想中文是什么样子的吧~

以下内容先了解下,若暂时看不懂,还是那句话,以后会懂的。
判定句中条件是有返回值的,返回“true”(对的)和“false”(错的),当条件判断后得到的值是 true ,就会运行 then 中的动作;反之,若返回 false ,就运行 else 中的动作。
这种 true 和 false 跟数字一样,是一种数据类型,叫做布尔值,英文叫boolean

“条件判定句”进阶部分完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:27:20 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:28 编辑

2011/10/11更正错误:部分有返回值的函数之前写成returns nothing了
2012/03/07进行了错误修正
第六章:变量

在学习变量之前呢,我们先回顾下前几章中出现的数据类型。
integer(整数),real(实数),boolean(布尔),nothing(无数据类型)


首先,变量这东西呢,就相当于各种称呼、各种名字。
比如“总统”这个词,现在是指胡锦涛,以前呢,是指江泽民,再以前就是指毛泽东了。
“总统”这词在这里就是个变量,在不同的时期指代不同的人,但是“总统”这词本身并不改变,包括这词的类型也不会改变,即“总统”在这里就是“人”这个类型,其它类型的东西不能用“总统”指代,你总不能用国家总统指代苹果吧。

那么,将一个人跟“总统”这个称谓挂钩起来的这个过程呢,就叫赋值,字面意思就是将一个值赋予(赋给)一个词。
比如胡锦涛主席就任的时候,在程序里就是将“胡锦涛”这个人赋给了“总统”这词,于是以后叫“总统”大家都知道是指胡主席了。

再比如,现实生活中呢,是在人出生时给这个人一个名字,为了以后能用这个名字来指代这个人,而不是一直喊“喂”“那个谁”“就是你”……
但其实呢,给名字也可以倒过来看,不是把名字给人,而是将这个人赋给这个名字,这个名字就是变量。任何人都可以叫这个名字,也就是可以将任何人赋值给这个名字,但是为了防止出现喊两个同名的人的名字时两个人都回应的这种情况,每个名字都只能赋一个值

综上所述,JASS中变量类型和名字确定后就不能随便更换了,但是变量名所指代的内容是可以变的,用赋值这个方法。同时,为了不出现一个变量名指代两个或更多内容的情况,赋值的时候会将之前已经在的值抹掉,然后再将新的值赋上。
比如,胡锦涛就任时,“总统”这词就不再指代江泽民了,已经被胡锦涛替换掉了,这时候江泽民呢,只能用“前总统”来称呼了。但是“前总统”和“总统”是两个不同的变量。

-------------------------------------------------------------  变量的使用  -------------------------------------------------------------

那么,变量这个东西在J中到底如何用呢?
先把前面用过的J复制过来:
  1. function add takes real A, real B returns real
  2.     return A + B
  3. endfunction
复制代码

注:该函数用的是real,输入时如果输入整数,也会被弄成1.0这种小数形式,如果一定要整数,请使用integer。
大家看到,如果只要用到两个固定的数值,比如A==1,B==2,使用该函数时每次都要写成add( 1, 2 )这个形式,有没有省力点的办法,比如每次使用这个函数的时候能写成add()这样就好了,这时就要使用变量了。

跟之前一样,要先告诉电脑接下来的内容是变量,用”globals”(全局,全球)这个词,当然结尾记得也有个引导词:
  1. globals
  2.     xxxxx
  3. endglobals
复制代码

其中xxxxx的部分就是写变量的地方

现在加入变量:
  1. globals
  2.     real A
  3.     real B
  4. endglobals
复制代码

这样就已经申请好了两个实数变量,一个叫A,另一个是B。

接下来呢,就是如何在函数中使用这两个变量了:
由于是直接拿变量做加法,所以不需要参数了。
  1. globals
  2.     real A
  3.     real B
  4. endglobals

  5. function add takes nothing returns real
  6.     return A+B
  7. endfunction
复制代码

但是这样写电脑不知道A和B到底是多少,于是就要给A和B赋值,使用“=”
  1. globals
  2.     real A=1
  3.     real B=2
  4. endglobals

  5. function add takes nothing returns real
  6.     return A+B
  7. endfunction
复制代码

于是,如果使用add()的话,返回值就是3.0了

那么现在更正下上面讲的内容,所有的变量都是有初始值的:
  1. globals
  2.     real A
  3.     real B
  4. endglobals

  5. function add takes nothing returns real
  6.     return A+B
  7. endfunction
复制代码

对于写在globals里的变量,如果没有赋值,JASS就会使用该变量类型的默认初始值,上面这个函数用的是实数,A和B的初始值就是0.0,整数的话就是0,如果变量是unit(单位)类型的话,初始值就是“没有单位”。

PS:该用参数的时候还是要用参数~这章里所讲的只是为了说明变量

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:28:01 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:28 编辑


本章节于2011/07/15更正错误:local integer n   n没有初始值,不是0
2011/10/11更正错误:有返回值的函数之前写成returns nothing了
第七章:变量进阶

大家也许发现了,上一章中讲的其实与一个函数有两个参数一样,多个参数就多写几个变量,没有什么区别。这一章就讲下变量的方便之处。

如果有三个函数,第一个是A+B,第二个是A-B,第三个是A*B,第四个是……好吧,就先三个吧。
如果按照最先的写法呢,需要写成:
函数1有两个参数然后返回A+B,函数2有两个参数然后返回A-B,函数3有两个参数然后返回A*B。
一共要写六个参数。。感觉好累的说……

现在有变量之后,可以这么写:
假设A是3,B是2;
加法函数的名字是JiaFa,减法的是JianFa,乘法的是ChengFa
  1. globals
  2.     integer A = 3
  3.     integer B = 2
  4. endglobals

  5. function JiaFa takes nothing returns integer
  6.     return A+B
  7. endfunction

  8. function JianFa takes nothing returns integer
  9.     return A-B
  10. endfunction

  11. function ChengFa takes nothing returns integer
  12.     return A*B
  13. endfunction
复制代码

这样至少比写六个参数要方便多了吧~
:JASS读函数是从上到下读的,也就是优先读全局变量,再读JiaFa,再JianFa,最后ChengFa,所以全局变量一定要写在最前面,不然电脑在看到函数中出现变量后就不知道那些变量是什么。

像这种所有的函数都能使用,用”globals”(全局)作引导词的变量,叫全局变量。从名字也能看出是全部函数都能使用的变量。

如果要这些变量只能给一个函数用呢,那就使用“局部变量”,引导词”local”(局部)。
但是局部变量不再在globals里申请了,有权利在globals里申请的都是全局变量。
既然局部变量是只能给一个函数用,那就在函数里申请:
  1. function add takes nothing returns integer
  2.     local integer A = 1
  3.     local integer B = 2
  4.         return A+B
  5. endfunction
复制代码

:"return"前多空了4格,是为了看的时候方便区分局部变量和这个函数的功能。

发现局部变量这个写法其实还不如写成参数省力,而且参数也是只能给这个函数使用,为什么要用局部变量?当然在这里是看不出来的,以后会遇到那些只能使用无参数的函数的情况,另外全局变量冲突的情况用局部变量解决最为效率。

这里先简单讲解下全局变量冲突
首先,先讲下真正的赋值。
函数中是能改变变量的,不管是全局还是局部,方法当然是赋值,不过跟之前的不同。
之前的情况,是新建了一个变量并赋予初始值,也就是在设置变量的时候用“=”赋值;这里所讲的赋值呢,是用新的数据去替换旧的数据,比如要用“胡锦涛”来替换“总统”这个变量中的“江泽民”
使用的引导词为"set"(设置),中文是:设置(变量名)= xxxxxx
  1. globals
  2.     integer A = 1
  3.     integer B = 2
  4. endglobals

  5. function JiaFa takes nothing returns integer
  6.     set A = 5
  7.     set B = 4
  8.     return A+B
  9. endfunction
复制代码

于是,全局变量A就变成了5,B就变成了4,JiaFa的返回值就变成了9

但是之前说了,一个变量名只能对应一个值,赋予新的值的时候原来那个值就被删除了,于是,这两个全局变量A和B就永远变成5和4了。
如果后边的JianFa和ChengFa要用A==1和B==2,就无法用了,这就是常见的变量冲突。

于是有人说,既然J是从上到下读整个程序的,那么把“减法”和“乘法”这两个函数放到“加法”的前面不就解决了,因为改变变量是在“加法”里,改之前先算“减法”和“乘法”就好了啊。
那么万一这些函数要运行多次呢?于是那人又说,在“减法”里 ”set A==1”、”set B==2”,好吧。。。这位同学你赢了……但是这么写很麻烦不是吗?

所以,用局部变量就行了,若局部变量跟全局变量重名了的话,J会优先选择局部变量,因为J看到未知的变量时,会先从最近的地方开始往上找,所以明显局部变量的位置更近嘛(本来就是在函数里……)
  1. globals
  2.     integer A = 1
  3.     integer B = 2
  4. endglobals

  5. function JiaFa takes nothing returns integer
  6.     local integer A = 5
  7.     local integer B = 4
  8.         return A+B
  9. endfunction

  10. function JianFa takes nothing returns integer
  11.     return A-B
  12. endfunction

  13. function ChengFa takes nothing returns integer
  14.     return A*B
  15. endfunction
复制代码

于是,JiaFa() == 9,JianFa() == -1,ChengFa() == 2

结束之前再强调一个重点在一个触发器里,全局变量必须在所有函数之前申明,每个函数的局部变量必须在这个函数的最开端申明。未申明过的变量是没法用的。
本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:28:36 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:29 编辑

第八章:字符串&调用函数

字符串(string)这东西新手们一开始也许会觉得很难理解,我暂时也没找到什么好的方法来讲……抱歉。。
符串或串(String)是由零个或多个字符组成的有限序列。一般记为 s='a1a2&#8226;&#8226;&#8226;an'(n>=0)。它是编程语言中表示文本的数据类型。
                                                                                                                                                  ——百度百科

引用的部分呢,前面第两句看不懂直接忽略,最后一句能稍微理解就行了。

简单点来说就是游戏中出现的左下角的那些文字就是string(字符串),玩家们输入的聊天信息也是字符串。

在编程中,string(字符串)都是以一对引号(“”)来做引导的,string就是引号里面的内容(不包括引号),比如"abc",引号里面的abc就是string。

总之,字符串就是储存文本或信息的一种数据类型。
在魔兽里字符串的使用需要跟别的知识结合,所以之后会有应用。

-----------------------------------------  发现上下毫无关系  -----------------------------------------

接下来讲下调用函数
在之前的章节我们已经学会如何使用一个函数了,调用呢,就是在别的函数里使用写好的函数。
估计有些同学已经弄不清了,所以先讲下区别:
直接使用一个函数,写成:函数名(参数1,参数2,参数3,……)这种形式
不过做地图时除了变量的赋值几乎用不到直接使用这种方式(这种变量赋值之后会讲到)
那么调用函数呢,就需要引导词了,你不跟J说明你要调用别的函数了,J怎么会知道。引导词是"call"(本意是:叫(某人),打电话给某人等)
再强调一遍,调用函数是放在函数里的。

举例说明:
函数F有两个整数参数A和B,如果A>B,则A-B,如果A<B,则A+B,否则A*B。
这不是之前的条件判定嘛!?
不过这里我们要用调用函数来写,调用之前写的“加法”“减法”和“乘法”函数:
  1. 把之前的“JiaFa”“JianFa”“ChengFa”写到这里,不过教程中就省略了

  2. function F takes integer A, integer B returns nothing
  3.     if A>B then
  4.         call JianFa(A,B)
  5.     elseif A<B then
  6.         call JiaFa(A,B)
  7.     else
  8.         call ChengFa(A,B)
  9.     endif
  10. endfunction
复制代码

:调用函数时有一点跟全局变量一样,被调用的函数一定要放在上面。比如函数A里 call 函数B,那么写J的时候,B要写在A的上面。

PS:本章其实是为下一章作铺垫的。

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:28:59 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:29 编辑

注:由于图片过长显示不完整,请切换到论坛的宽版模式,不知道切换方法请看顶楼的“补充”部分。
本章节于2011/12/29修正了错误
第九章:在魔兽中看J的运行(含本地函数&数据类型转换&传参的概念)

从这章开始,我们就要进入魔兽,看看J运行之后的结果到底是怎么样的。
首先,从最简单的程序,显示信息开始,在上一章节,我们已经学过了如何调用一个函数以及对字符串有了一点点概念,现在就要让任何字符串显示给玩家。

我们需要一个能对玩家显示信息的函数,叫做DisplayTextToPlayer,不懂英文的同学看着可能会觉得很辛苦,我们就先把这个函数名拆分一下:
Display(显示)Text(文本)To(给、到)Player(玩家)
我们发现,这个函数名按照大写的字母可以拆成四个单词,于是对应每个单词翻译意思,就能猜出大致意思。

先回答部分同学的第一个问题:为什么会有这个函数?或者说这个函数哪来的?
:这是JASS中自带的函数,函数的功能是被定义好的,没法随便修改(好吧,其实可以改,但是去改那玩意儿对初学J的同学来说没有什么意义。。),但是可以随便拿来用,使用的方法呢,就是直接使用和调用两种。像这种JASS自带的函数,或者说是暴雪公司已经写好的函数,叫做本地函数内置函数)。

第二个问题:看不懂这个函数名怎么办?
:暴雪为了让别人从函数名就能看出这个函数的意思,基本上所有的函数名都是由几个简单的单词组成来说明这个函数的作用。比如DisplayTextToPlayer 就对应“显示信息给玩家”。所以遇到看不懂的,按照首字母大写拆开来一个个弄到翻译器里翻译,不用怕翻译器会翻得很烂,因为拆开成单词后,就是翻单词的意思了,不带任何语法,而翻译器翻烂掉的往往都是带很复杂的语法的句子。

第三个问题:如何知道这些本地函数怎么使用?
:非常简单。在各种编写J的编辑器中都有函数搜索功能,比如这里就以YDWE为例:
1.png
----------------------------------------------
2.png
----------------------------------------------
3.png

这里的参数呢,分别是“(第几个)玩家”“坐标X”“坐标Y”“字符串(信息)”,其中这个坐标(X,Y)就是指在屏幕显示信息的位置。

我们就开始编写这个显示信息的函数:
  1. function F takes nothing returns nothing
  2.     call DisplayTextToPlayer( Player(0), 0, 0, “Jass” )
  3. endfunction
复制代码

于是,这个函数F的作用就成了在屏幕(0,0)这个位置,显示信息“Jass”给玩家一

好奇怪?为什么玩家一是Player(0)而不是Player(1)?这个就跟编程的机制有关了,在编程中,很多时候第一位是0而不是1,1代表第二位,2代表第三位,以此类推。比如“Jass”这个字符串中,第一个字母J实际是在这个字符串的0位。

光是有这个函数F还不够,游戏运行时,电脑还是不知道要什么时候显示这个信息,总不能一直显示着吧。于是……你们懂的……引导~
不过这里的引导不再是什么单词啊符号啊之类的了,这里需要的是“事件”,也就是当遇到某个事件的时候显示信息“Jass”。不过我们还没学触发器的事件之类的写法,所以先用T代替下:
  1. DisplayString
  2.     事件
  3.         玩家 - 玩家1(红色) 按下Esc键
  4.     条件
  5.     动作
  6.         自定义代码:   call F()
复制代码

:在自定义代码中完全可以直接写成call DiaplayTextToPlayer(……)这样子,不过学过触发器的写法后就不需要T了,函数F还是要那样写,这里T仅仅是代替下。
这个T就是:当玩家1按下ESC键,调用函数F,之后在F里再调用“显示信息”这个本地函数。
保存之后测试地图吧~

--------------------------------------------------  数据类型转换  --------------------------------------------------

上面所使用的string呢是个单词,并且是直接写成string的形式,如果要先运算再显示呢?
直接把运算过程放到string里吗?比如“1+1=”,能显示出数字2吗?
显然不行,显示出来的信息仍就是“1+1=”。

既然要先运算,运算的结果是数字的数据类型,而显示信息需要string这种数据类型,那就需要转换数据类型了。
转换数据类型使用的是本地函数,比如整数转成实数:I2R( 输入一个整数 ),运行之后这个输入的整数就转成实数了。
那么这个I2R怎么理解?还是拆开来看,I => Integer,R => Real,2 => ?
这个数字2怎么理解?2在英文中是two,有个同音单词是to,于是I2R还原以后就是IntegerToReal(整数到实数)。

那么同理,如果要:整数(Integer)转成(To)字符串(String),就是I2S了
现在写个1+1=2,然后显示2的函数:
  1. function F takes nothing returns nothing
  2.     local integer A = 1+1
  3.     call DisplayTextToPlayer( Player(0), 0, 0, I2S(A) )
  4. endfunction
复制代码


数据类型转换这种函数,一般情况下都是直接使用的。基本用不到调用的写法。比如放在DisplayTextToPlayer这个函数里就变成个参数了,参数不能用调用的写法。

像这种把一个函数里的变量或参数传到另一个函数里(不管是直接使用还是调用)的做法就叫传递参数,简称传参

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:29:10 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:29 编辑

第十章:循环(loop)

循环在程序中是个什么概念?
比如现在有个程序,功能是给甲乙丙丁四个人按顺序发牌,第一张给甲,第二张给乙,……,以此类推,直到所有的牌发完。这就是一个循环。

现在来看下J的写法:
  1. function 发牌 takes nothing returns nothing
  2.     local integer 牌数 = 52
  3.     loop
  4.         exitwhen 牌数==0
  5.             ……(发牌动作)
  6.         set 牌数=牌数-1
  7.     endloop
  8. endfunction
复制代码

很明显,引导词是“loop”。
exitwhen”这词看不懂就拆,exit:出去,when:当……时
再稍微联想一下,就是“当……时跳出循环”(:如果一直循环下去那就是死循环了,魔兽会崩的。。所有要避免死循环)

这个函数的解读方法如下:
1.    牌数是52不等于0
2.    发牌动作后设置牌数等于牌数减1(意思就是,新牌数=52-1)
3.    牌数是51不等于0
4.    发牌动作后设置牌数等于牌数减1(新牌数=51-1)
5.    之后重复
6.    直到牌数==0
7.    结束循环

接着完善下发牌动作:
我们需要在发到丁后重新发回给甲,常用的方法之一是给一个数字n赋值,一开始是0,n是0则发给甲,n=n+1==1,n是1则发给乙,n=n+1==2,n是2则发给丙,n=n+1==3,n是3则发给丁,n=0。
  1. function 发牌 takes nothing returns nothing
  2.     local integer 牌数 = 52
  3.     local integer n = 0
  4.     loop
  5.         exitwhen 牌数==0
  6.             if n==0 then
  7.                 发牌给甲
  8.                 n=n+1
  9.             elseif n==1 then
  10.                 发牌给乙
  11.                 n=n+1
  12.             elseif n==2 then
  13.                 发牌给丙
  14.                 n=n+1
  15.             elseif n==4 then
  16.                 发牌给丁
  17.                 n=0
  18.             endif
  19.         set 牌数=牌数-1
  20.     endloop
  21. endfunction
复制代码

PS:这段函数可以把n=n+1提出来,在发牌的判定句结束之后再写,缩短函数,不过写成这样比较直观,所以我就先不简化了。

练习(大家自己试一下~):
按下ESC在屏幕左下角按递增顺序显示1-10这10个数字。

思路(按Ctrl+A查看):
先设置局部整数变量n为1,循环 - 当n大于10时跳出循环,call DisplayTextToPlayer(……, I2S(n)),设置n=n+1

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:29:26 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:29 编辑

注:如果J过长自动换行,请切换到论坛的宽版模式,不知道切换方法请看顶楼的“补充”部分。
第十一章:触发器的创建、注册事件、添加条件和动作

一个触发器的“事件”就是运行“动作”的引导,即只有遇到了指定“事件”魔兽才会去运行指定的“动作”;至于“条件”呢,就是在遇到“事件”后,魔兽还要经过“条件”的同意后才会运行“动作”,即“条件”的结果是true才行。

我们先做一个T:
  1. Unit
  2.     事件
  3.         玩家 - 玩家1(红色) 选择 一个单位
  4.     条件
  5.         (触发单位) 等于 农民 0001 <预设>
  6.     动作
  7.         游戏 - 对 玩家1(红色) 在屏幕位移(0.00,0.00)处显示文本: 选则了单位
复制代码

这个名叫“Unit”的T作用是:当玩家1选择了农民后,显示“选择了单位”。

现在转成J来看下:
  1. function Trig_UnitConditions takes nothing returns boolean
  2.     return ((GetTriggerUnit() == gg_unit_hpea_0001))
  3. endfunction

  4. function Trig_UnitActions takes nothing returns nothing
  5.     call DisplayTextToPlayer( Player(0), 0, 0, "TRIGSTR_008" )
  6. endfunction

  7. //===========================================================================
  8. function InitTrig_Unit takes nothing returns nothing
  9.     set gg_trg_Unit = CreateTrigger()
  10.     call TriggerRegisterPlayerSelectionEventBJ( gg_trg_Unit, Player(0), true )
  11.     call TriggerAddCondition(gg_trg_Unit, Condition(function Trig_UnitConditions))
  12.     call TriggerAddAction(gg_trg_Unit, function Trig_UnitActions)
  13. endfunction
复制代码

额。。。眼睛开始转圈了。。。。这是神马。。。。

做下深呼吸,镇静一下~
开始分析:
先看最上面那个函数,名字叫“Trig_UnitConditions”,发现中间的这个单词“Unit”跟我们T的标题一样,然后Unit之后的这个单词“Conditions”不认识。。去google,于是知道了是“条件”的意思(Condition是单数,后面加s是复数),剩下开头部分的“Trig_”,这是个前缀,是T自动生成的,“Trig”是“Trigger”(触发器,本意是扳机、触发)的缩写
于是这个就可以译为“触发器_Unit的条件”,先不管这个函数是干嘛的,看下一个。

发现名字类似,“Trig_UnitActions”中现在唯一不懂的是“Actions”,去查。。于是知道了是“动作”的意思。合起来就是“触发器_Unit的动作”。

什么啊……原来一个T里的条件和动作是分开的两个函数啊。
现在看最后一个函数的名字,“InitTrig_Unit”,去掉Init的话就是“触发器_Unit”了,那这个Init呢,就是“Initializer”(初始化)这个单词的缩写,于是合起来就是“初始化触发器_Unit”。

现在我们知道了,一个完整的触发器是由三个函数,或者说至少三个函数组成的。

这里就要先讲个概念了,“触发器”这个东西实际上也是数据的一种,数据类型是:trigger。也就是说一开始是没有触发器这个东西的,需要新建这个数据。

--------------------------------------------  初始化触发器  --------------------------------------------

我们一样样来对应:
既然说触发器需要新建,那么新建的触发器在哪?
我们看到,在InitTrig_Unit中,第一句话是:
set gg_trg_Unit = CreateTrigger()
等号后面这个CreateTrigger()就是J的本地函数之一,作用是新建触发器。
然后看前面,发现引导词“set”和一个“=”,那么gg_trg_Unit肯定是个变量了。
gg_”是一种WE自建“全局变量”的前缀,这个变量其实就是:
  1. globals
  2.     trigger gg_trg_Unit
  3. endglobals
复制代码


现在来看InitTrig_Unit里第一个被调用的函数:
发现名字超级长……
TriggerRegisterPlayerSelectionEventBJ
但是有件事很开心,这是用首字母大写的单词组成的,于是拆开:
Trigger(触发器)Register(注册)Player(玩家)Selection(选择)Event(事件)BJ(BJ函数)
PS:BJ函数这个先别管。。以后会提到。

到函数列表里查一下就知道需要哪些参数了,需要(触发器,玩家,布尔值)
也就是说需要一个可以被注册事件的触发器,然后需要一个选择单位的玩家,最后一个布尔值的意思就是是否选择了单位(true的话就是选择了,false就是取消选择)
于是,
TriggerRegisterPlayerSelectionEventBJ( gg_trg_Unit, Player(0), true )
可以理解为:给触发器gg_trg_Unit注册玩家1(Player(0))选择单位事件

--------------------------------------------  条件  --------------------------------------------

然后来看第二个调用的函数:
TriggerAddCondition
意思马上能看懂,就是“触发器加条件”,同样的,需要作为目标的触发器,另外需要一个条件。
TriggerAddCondition(gg_trg_Unit, Condition(function Trig_UnitConditions))
触发器是gg_trg_Unit,看下条件的写法,需要一个叫“Condition”的函数,这个函数的作用就是把一个code(代码)变成“条件”(代码!?其实就是函数的另一种叫法)
所以,在函数Condition里加入一个函数参数
:这个加入的函数必须没有参数!返回值必须是布尔值!因为是条件嘛。

于是回到最前面那个函数:
  1. function Trig_UnitConditions takes nothing returns boolean
  2.     return ((GetTriggerUnit() == gg_unit_hpea_0001))
  3. endfunction
复制代码

GetTriggerUnit(),拆开来看:得到 触发 单位
gg_unit_hpea_0001:这个是我在地图上放的农民,WE给他的编号是0001
这个函数就是返回(触发单位==农民0001)

--------------------------------------------  动作  --------------------------------------------

最后一个调用的函数不用说肯定是“动作”了:
TriggerAddAction(gg_trg_Unit, function Trig_UnitActions)
触发器是gg_trg_Unit
由于是“动作”所以直接运行函数Trig_UnitActions就行

下面是“动作”部分:
  1. function Trig_UnitActions takes nothing returns nothing
  2.     call DisplayTextToPlayer( Player(0), 0, 0, "TRIGSTR_008" )
  3. endfunction
复制代码

这个大家都能看懂吧,看不懂的去回顾下第九章吧~
但是很奇怪,T里面不是写的显示“选择了单位”吗?这里怎么变成"TRIGSTR_008"了?
好吧。。其实我也不知道,可能是WE自动把“选择了单位”这个string记录在“TRIGSTR”里,编号是008
这个不用管的,大家可以直接写成:
  1. function Trig_UnitActions takes nothing returns nothing
  2.     call DisplayTextToPlayer( Player(0), 0, 0, "选择了单位" )
  3. endfunction
复制代码


-----------------------------------------------------  总结  -----------------------------------------------------

一个完整的最基础的触发器需要:
1.    初始化触发器函数(此函数无需参数和返回值,在这个函数里需要新建触发器,并为这个新建的触发器注册事件,然后添加条件和动作)
2.    条件函数(无参数且返回值必须为布尔值)
3.    动作函数(无参数且无返回值,所有要做的事全在这个函数内完成,即使用调用函数的方式完成动作)
要注意这三个函数在J中的顺序:
由于初始化触发器中调用了“条件”和“动作”,所以,初始化触发器这个函数必须放在“条件函数”和“动作函数”的下面。

下一章节教大家如何自己写触发器。

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:29:43 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:30 编辑

注:如果J过长自动换行,请切换到论坛的宽版模式,不知道切换方法请看顶楼的“补充”部分。
第十二章:写自己的触发器

PS:以下写J的顺序是我自己写J的顺序,同学们不一定要仿照。


先看下我们要写个什么样的J:
触发器名字:Unit
事件:玩家1输入a
条件:农民0001是存活的(该农民是地图上事先放置的农民,0001是WE给这个农民的编号,大家放置的时候也许编号会不同)
动作:创建一个玩家1的步兵在地图中心。
好像没什么思路额。。。但是不管写什么触发都需要很关键的第一步,那就是初始化
:一个初始化的函数的名字必须带上“InitTrig_”这个前缀。
  1. function InitTrig_Unit takes nothing returns nothing
  2. endfunction
复制代码

:“InitTrig_”后面跟的名字必须跟该触发器的名字相同,否则无法初始化,也就是无法使用。意思就是:

如果你在这里写的触发器名字是XXX:
QQ截图未命名.png
那么就必须写成:InitTrig_XXX

第二步创建触发器
我们不再使用上一章中的全局变量触发器,改用局部变量;至于区别。。这我还真不知道,至少程序长了以后不用每次都拉到顶端写到globals里再拉回来。
  1. function InitTrig_Unit takes nothing returns nothing
  2.     local trigger trig = CreateTrigger()
  3.     set trig = null
  4. endfunction
复制代码

set trig = null 这一步就是将这个局部变量触发器清空,并不是销毁这个触发器,而是将这个变量清空,也是排泄的一种。(至于排泄的概念……已经有很多相关教程了,搜索下就有了;总之就是不排泄地图就会越玩越卡)
PS:null 就是“没有”的意思

第三步注册事件
我们的事件是玩家1输入a,于是在T里找到这个事件,然后转成J之后自己照着样子打一遍,不要复制黏贴,尽量看懂那个函数名:
  1. function InitTrig_Unit takes nothing returns nothing
  2.     local trigger trig = CreateTrigger()
  3.         call TriggerRegisterPlayerChatEvent( trig, Player(0), "a", true )
  4.     set trig = null
  5. endfunction
复制代码


第四步加入条件
  1. function InitTrig_Unit takes nothing returns nothing
  2.     local trigger trig = CreateTrigger()
  3.         call TriggerRegisterPlayerChatEvent( trig, Player(0), "a", true )
  4.         call TriggerAddCondition( trig, Condition( …… ))
  5.     set trig = null
  6. endfunction
复制代码

由于我们还没写条件函数,所以Condition()的括号中先空着

第六步加入动作
  1. function InitTrig_Unit takes nothing returns nothing
  2.     local trigger trig = CreateTrigger()
  3.         call TriggerRegisterPlayerChatEvent( trig, Player(0), "a", true )
  4.         call TriggerAddCondition( trig, Condition( …… ))
  5.         call TriggerAddAction( trig, …… )
  6.     set trig = null
  7. endfunction
复制代码

同样的,由于还没写动作函数,先空着

第七步写条件函数
我们的条件是:农民0001是存活的
在T中找到这个条件,由于是条件,所以肯定在“布尔值”里
转成J看懂后输入到我们的程序里:
PS:因为函数名只要对应就可以使用和调用,所以不用再加什么“Trig_”这种非常麻烦而且难看的前缀了,只要写成自己能看懂的名字就行了。
  1. function Unit_Condition takes nothing returns boolean
  2.     return IsUnitAliveBJ( gg_unit_hpea_0001 )
  3. endfunction

  4. function InitTrig_Unit takes nothing returns nothing
  5.     local trigger trig = CreateTrigger()
  6.         call TriggerRegisterPlayerChatEvent( trig, Player(0), "a", true )
  7.         call TriggerAddCondition( trig, Condition( function Unit_Condition ))
  8.         call TriggerAddAction( trig, …… )
  9.     set trig = null
  10. endfunction
复制代码

Condition( function Unit_Condition )中使用Unit_Condition函数之前要加function这个关键词。这里Unit_Condition函数是作为Condition()函数的code(代码)参数,所以要跟电脑说明Unit_Condition是一个function。基本上当一个函数作为code被直接使用时就要加上function这个词,code这种参数在函数列表里可以看到。
PS:code和function是同一个东西,不过由于J中函数的参数需要说明数据类型,所以用code,明显code比function要短很多~

最后一步写动作函数
在T中找到“单位-创建单位(面向角度)”这个动作(我用的是(可用地图区域的中心点),其它没变):
  1. 未命名触发器 005
  2.     事件
  3.     条件
  4.     动作
  5.         单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((可用地图区域) 的中心点) ,面向角度为 默认建筑朝向 度
复制代码

转成J后,发现是这么个东西:
  1. CreateNUnitsAtLoc( 1, 'hfoo', Player(0), GetRectCenter(GetPlayableMapRect()), bj_UNIT_FACING )
复制代码

这个函数会创建一个“点”(数据类型为point)在地图中心,然后创建1个步兵(‘hfoo’)到这个“点”,并且让这个步兵面朝(bj_UNIT_FACING)(与T对应后即可知道是默认建筑朝向)
但是创建完步兵这个“点”就没用了,需要删除(也就是排泄)。

所以这里推荐一个J的本地函数
  1. native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit
复制代码

参数分别是:玩家,单位的ID,坐标X,坐标Y,面朝方向
返回值是单位
由于地图中心的坐标是(0,0),且坐标是实数,无需排泄
所以动作函数可以写成:
  1. function Unit_Condition takes nothing returns boolean
  2.     return IsUnitAliveBJ( gg_unit_hpea_0001 )
  3. endfunction

  4. function Unit_Action takes nothing returns nothing
  5.     call CreateUnit( Player(0), 'hfoo', 0, 0, bj_UNIT_FACING )
  6. endfunction

  7. function InitTrig_Unit takes nothing returns nothing
  8.     local trigger trig = CreateTrigger()
  9.         call TriggerRegisterPlayerChatEvent( trig, Player(0), "a", true )
  10.         call TriggerAddCondition( trig, Condition( function Unit_Condition ))
  11.         call TriggerAddAction( trig, function Unit_Action )
  12.     set trig = null
  13. endfunction
复制代码


至此,一个完整的触发就写完了。

有的同学问:这个步兵的代码(‘hfoo’)只能写个T的动作再转换来查看吗?
答:可以直接在物体编辑器里查看。到“单位”里找到“步兵”,然后按Ctrl+D即可。
1.png
-------------------------------  Ctrl+D  --------------------------------
2.png
要还原的话再按下~


练习
用纯J(不要做完T转换哦~,不过允许不知道的函数名用T转换看)写一个完整的触发:
在地图上放一个农民一个步兵
事件:玩家1选择一个单位;
条件:被选择的是步兵
动作:如果农民被选择,显示信息:农民被选择
           如果步兵被选择,显示信息:步兵被选择

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:30:04 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:30 编辑

第十三章:单位组&玩家组

单位组这个数据类型在J中是:group(本意是组、团队)
玩家组是:force(本意是力量)
虽然同是“组”,中文可以用单位和玩家来区别,但是英文不行,写成unit_group和player_group的话不仅占地方而且写起来很麻烦。所以玩家组就用force这个词,在这里就是“势力”的意思。

这两个数据使用方法其实差不多,这里主要讲group的使用。

首先,如果什么都不做,魔兽中是没有group这个东西的,所以我们要做的第一件事是新建一个单位组
之前我们已经见过了新建单位的写法,新建单位组也是类似:CreateGroup();也就是凡是新建,基本都是用Create这个单词。

在J中,几乎所有的数据都可以当做变量,所以单位组的申明可以在globals里,也可以使用local:
  1. globals
  2.     group g=CreateGroup()
  3. endglobals
  4. 或者
  5. function G takes nothing returns nothing
  6.     local group g=CreateGroup()
  7.     set g=null
  8. endfunction
复制代码

:若以局部变量的方式,则记得要排泄。
PS:两种方式需要根据不同的情况灵活使用。

单位组的作用呢就是可以让我们只发布一次命令就让一堆单位按这个命令行动,而不再是对这些单位一个个发布命令。当然还有其它的好处,之后遇到的时候就会明白的。

先看下如何往单位组里加单位以及移除单位:
首先我们需要一个单位组:
  1. globals
  2.     group g=CreateGroup()
  3. endglobals
复制代码

于是我们就有了一个新建的空单位组。
  1. globals
  2.     group g=CreateGroup()
  3. endglobals

  4. function add_del takes nothing returns nothing
  5.     call GroupAddUnit( g, gg_unit_hpea0000 )
  6.     call GroupRemoveUnit( g, gg_unit_hpea000 )
  7. endfunction
复制代码

:gg_unit_hpea0000是已经在地图上放置的农民,大家放置时的编号可能不同。
add_del这个函数中调用的两个本地函数的意思就是为一个单位组添加/删除单位。
Add:加;Remove:移除、去除
这两个函数需要的参数相同,都需要一个已经存在的单位组,一个将被添加/删除的单位

但是添加单位这个函数的缺陷很明显,一次只能添加一个,如果有一群单位的话一个个添加就太繁琐了。
这种时候就要使用“选取范围内所有单位”这个函数:
  1. native GroupEnumUnitsInRange takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
复制代码

:看不懂函数名的同学请回顾前面几章,已经教了很多次翻译函数名的方法了,之后也不再逐一翻译了。
boolexpr filter:这个参数是布尔表达式,简单来说就是一个判断对错的函数。
这个本地函数的意思是往一个group里添加以(x,y)为圆心范围radius之内且符合filter的所有单位。

那如果不要条件呢?可以这么写:
  1. call GroupEnumUnitsInRange( group, 0, 0, 100, null )
复制代码

null在这里就表示无条件,也就是添加范围内的所有单位。

添加完单位后就需要对这个单位组里的单位发布命令了:
我们先转化“单位组-选取(单位组)内的单位做动作”这个T动作到J看下函数
  1. call ForGroupBJ(……)
复制代码

转化后发现是这么个东西,再到function list查一下,原来这个函数是调用了ForGroup这个函数,另外还有一个变量以及调用了DestroyGroup(删除单位组)这个函数。
也就是T中的这个选取单位组做动作在做完动作后直接删除了这个单位组,但是这么写其实会降低运行效率,因为多调用了一次ForGroup这个函数。反正这个函数也是调用了ForGroup和DestroyGroup,我们直接写这两个函数就行了,而且这样写的另一个好处是什么时候删除单位组是我们自己掌控的。
注:ForGroupBJ并不是无用,当需要选取比如某个玩家的所有单位做一次动作的时候用这个函数写起来就比较省力了。

ForGroup这个函数除了单位组外,还需要一个code,也就是我们需要另外写一个函数,其功能就是让这个单位组内的单位做动作。当然了,这个code是“被使用”,所以要放在前面。

先了解下下面这两个函数的作用,之后看实例来理解吧……
(其实是我没想到好的讲解方法。。)
这两个函数都不需要参数:
GetEnumUnit():相当于T中的“选取单位”,在选取单位组做动作时用来在动作函数中获取被选取的单位。
GetFilterUnit():相当于T中的“匹配单位”,在选取符合条件的单位添加到单位组时用来在条件函数中获取被选取的单位。

我们直接看一个实例:
山丘之王的雷霆一击大家都知道吧(不知道的同学请使用人族首发山丘之王学习第二个技能打一盘常规战……),现在我们想知道山王敲地板时有多少单位在该技能范围内(不包括友军)。
PS:暂时只考虑一级的雷霆一击,不然可能要用到还没学的东西。

先想下我们需要什么事件,既然是在山王施放雷霆一击时显示单位数,则我们需要山王开始放技能事件。不过由于山王不是一开始就放在地图上的,我们不能使用指定单位事件,只能使用任意单位事件。
触发器就叫“LTYJ”(Lei Ting Yi Ji)吧……
  1. function InitTrig_LTYJ takes nothing returns nothing
  2.     local trigger trig=CreateTrigger()
  3.         call TriggerRegisterAnyUnitEventBJ( trig, )
  4.         call TriggerAddCondition( trig, Condition( function ))
  5.         call TriggerAddAction( trig, function )
  6.     set trig=null
  7. endfunction
复制代码

:TriggerRegisterAnyUnitEventBJ就是“触发器注册任意单位事件”,不过这是个BJ函数。

在“物体编辑器”中找到“雷霆一击”,按Ctrl+D查看编号;或者在T中写一个含有该技能的动作然后转J查看。得到该技能的编号是:‘AHtc’。顺便看下一级雷霆一击的范围,是250码。

然后补上Condition:
  1. function LTYJ_Condition takes nothing returns boolean
  2.     return GetSpellAbilityId() == ' AHtc '
  3. endfunction

  4. function InitTrig_LTYJ takes nothing returns nothing
  5.     local trigger trig=CreateTrigger()
  6.         call TriggerRegisterAnyUnitEventBJ( trig, )
  7.         call TriggerAddCondition( trig, Condition( function ))
  8.         call TriggerAddAction( trig, function )
  9.     set trig=null
  10. endfunction
复制代码

GetSpellAbilityId()就是获取施放的技能的ID,无需参数,如果是T的话就相当于在“条件”中判断施放技能是否为是雷霆一击。

再补上动作部分,我们需要先选取山王周围250范围内的非友军单位(确定了“动作”的名字,就顺便把初始化函数里的事件条件动作也补上,我这里事件用的是“发动技能效果”):
  1. function LTYJ_Condition2 takes nothing returns boolean
  2.     return IsUnitEnemy( GetFilterUnit(), GetTriggerPlayer() ) == true
  3. endfunction

  4. function LTYJ_Action takes nothing returns nothing
  5.     local group g=CreateGroup()
  6.     local unit u=GetTriggerUnit()
  7.     call GroupEnumUnitsInRange( g, GetUnitX(u), GetUnitY(u), 250, Condition(function LTYJ_Condition2) )
  8. set g=null
  9. set u=null
  10. endfunction

  11. function LTYJ_Condition takes nothing returns boolean
  12.     return GetSpellAbilityId() == ' AHtc '
  13. endfunction

  14. function InitTrig_LTYJ takes nothing returns nothing
  15.     local trigger trig=CreateTrigger()
  16.         call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
  17.         call TriggerAddCondition( trig, Condition( function LTYJ_Condition ))
  18.         call TriggerAddAction( trig, function LTYJ_Action )
  19.     set trig=null
  20. endfunction
复制代码


接着就是显示单位组中的单位数量
有一个BJ函数叫CountUnitsInGroup,在T里就是获取“某单位组中的单位数量”,大家直接用这个就行,不过这里使用ForGroup,其实CountUnitsInGroup也调用了ForGroup。

我们先加上ForGroup,顺便先为其准备一个空代码:
  1. function LTYJ_Action2 takes nothing returns nothing
  2. endfunction

  3. function LTYJ_Condition2 takes nothing returns boolean
  4.     return IsUnitEnemy( GetFilterUnit(), GetTriggerPlayer() ) == true
  5. endfunction

  6. function LTYJ_Action takes nothing returns nothing
  7.     local group g=CreateGroup()
  8.     local unit u=GetTriggerUnit()
  9.     call GroupEnumUnitsInRange( g, GetUnitX(u), GetUnitY(u), 250, Condition(function LTYJ_Condition2) )
  10.     call ForGroup( g, function LTYJ_Action2 )
  11. set g=null
  12. set u=null
  13. endfunction

  14. function LTYJ_Condition takes nothing returns boolean
  15.     return GetSpellAbilityId() == ' AHtc '
  16. endfunction

  17. function InitTrig_LTYJ takes nothing returns nothing
  18.     local trigger trig=CreateTrigger()
  19.         call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
  20.         call TriggerAddCondition( trig, Condition( function LTYJ_Condition ))
  21.         call TriggerAddAction( trig, function LTYJ_Action )
  22.     set trig=null
  23. endfunction
复制代码


再往LTYJ_Action2中添加我们需要的动作,也就是数数(由于还没学过哈希表,所以用全局变量计数,若局部变量的话无法叠加,因为在一个函数里用完就自动被清除了);
最后添加显示最后的结果到ForGroup下面:
  1. globals
  2.     integer n=0
  3. endglobals

  4. function LTYJ_Action2 takes nothing returns nothing
  5.     set n = n+1
  6. endfunction

  7. function LTYJ_Condition2 takes nothing returns boolean
  8.     return IsUnitEnemy( GetFilterUnit(), GetTriggerPlayer() ) == true
  9. endfunction

  10. function LTYJ_Action takes nothing returns nothing
  11.     local group g=CreateGroup()
  12.     local unit u=GetTriggerUnit()
  13.     call GroupEnumUnitsInRange( g, GetUnitX(u), GetUnitY(u), 250, Condition(function LTYJ_Condition2) )
  14.     call ForGroup( g, function LTYJ_Action2 )
  15.     call DisplayTextToPlayer( Player(0), 0, 0, I2S(n) )
  16. set g=null
  17. set u=null
  18. endfunction

  19. function LTYJ_Condition takes nothing returns boolean
  20.     return GetSpellAbilityId() == ' AHtc '
  21. endfunction

  22. function InitTrig_LTYJ takes nothing returns nothing
  23.     local trigger trig=CreateTrigger()
  24.         call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
  25.         call TriggerAddCondition( trig, Condition( function LTYJ_Condition ))
  26.         call TriggerAddAction( trig, function LTYJ_Action )
  27.     set trig=null
  28. endfunction
复制代码

:别忘了把WE里触发器的名字改成初始化函数使用的名字,忘了的同学请回顾触发器写法那两章。

大家自己想几个简单的题目练习下吧,主要熟悉下GroupEnumUnitsInRangeForGroup这两个函数。
比如选取一定范围内的单位然后改变他们的移动速度,改变速度这个函数可以在T中“动作”的“单位”里找到,然后转J看下就知道了。

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:30:55 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:30 编辑

第十四章:注释 & CJ函数与BJ函数的区别&参数类型

所谓“注释”就是写给编程的人看的,电脑在运行时会直接无视之,引导符是“//”
  1. // 这里随便输入内容,切记在“//”之后的所有的内容都会变成注释
  2. // 注释需要换行的时候记得在开头补上个“//”
  3. 比如:
  4. //          雷霆一击
  5. function LTYJ takes ……
  6. endfunction
复制代码

用处大家应该也想到了,就是以后函数太多记不住哪个是干嘛的时候,用这玩意儿注明一下,具体的应用大家以后写J的时候自会想到~比如能标注参数等~
   

CJ和BJ函数呢最大的区别就在于BJ是CJ的复刻版。。。而且从某种意义上来讲还复杂化了……
比如“给(某个单位)添加(某个技能)”这个动作:
用T弄出来转J后发现是个BJ函数:
  1. function UnitAddAbilityBJ takes integer abilityId, unit whichUnit returns boolean
  2.     return UnitAddAbility(whichUnit, abilityId)
  3. endfunction
复制代码

但是这个函数本质上还是使用了UnitAddAbility 这个CJ函数,从效率上来讲,明显是直接使用原CJ函数更快,那为什么暴雪还要这么无聊地写个BJ函数出来?
以下仅我个人见解:
中文里我们可以说:给某个单位添加某个技能 / 添加某个技能给某个单位
两种都行,前者偏向“单位”,后者偏向“技能”
但在英文中,如果翻译以上两句的话:
For Unit, Add skill / Add skill for unit (额。。翻译得略为简陋。。能看就行了。。)
按英文语法来说,后者更通顺且能让读者更容易明白。

由于CJ函数是按照前者的顺序写参数的,考虑到英语为母语的WEER们看着不习惯,于是就写了套BJ函数出来。。不过对我们来说,反而是前者更合适,所以直接抛弃大部分BJ,用CJ,不仅效率比BJ高,而且读起来也顺。
   

现在大家看各种参数类型应该不会那么头疼了,反正本章也是过路的。。于是在这里贴出来:
boolean 布尔值destructable 可破坏物dialog 对话button 按钮texttag
漂浮文字
integer 整数
item 物品leaderboard 排行榜player 玩家force 玩家组location 位置(点)real 实数
rect 地区effect 特效string 字符串group 单位组timer 计时器unit 单位

:表格引用自acomc的文章。
以上这些是平时比较常用的,当然还有别的数据类型……
除了boolean、integer、real、string这四个最基础的类型,其它的都是句柄型(handle)的数据类型(“句柄型”这个概念大家暂时无视就好……单位就当单位类型,计时器就当计时器类型~),用作局部变量时在一般在一个函数的最后把这个变量清空(set x = null),用作全局变量的时候视情况而定。

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:31:15 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:30 编辑

第十五章:计时器

参数类型:timer
作用:倒计时,然后时间到了之后该干嘛干嘛……

玩家输入a,每过0.1s在屏幕显示数字1,2,3,4,……,n(n趋向无穷)

首先是初始化触发,由于不需要条件,于是就去掉TriggerAddCondition这个函数:
  1. function Num_Time takes nothing returns nothing
  2. endfunction

  3. function InitTrig_Num takes nothing returns nothing
  4.     local trigger t = CreateTrigger()
  5.         call TriggerRegisterPlayerChatEvent( t, Player(0), “a”, true )
  6.         call TriggerAddAction( t, function Num_Time )
  7.     set t = null
  8. endfunction
复制代码

让计时器运作,我们需要创建一个计时器
  1. function Num_ Time takes nothing returns nothing
  2.     local timer t = CreateTimer()
  3.     set t = null
  4. endfunction

  5. function InitTrig_Num takes nothing returns nothing
  6.     local trigger t = CreateTrigger()
  7.         call TriggerRegisterPlayerChatEvent( t, Player(0), “a”, true )
  8.         call TriggerAddAction( t, function Num_ Time )
  9.     set t = null
  10. endfunction
复制代码

之后使用TimerStart函数,设定时间、是否循环计时、及时间到了之后的运行的代码(注意是代码,无参):
  1. function Num_Action takes nothing returns nothing
  2. endfunction

  3. function Num_ Time takes nothing returns nothing
  4.     local timer t = CreateTimer()
  5.         call TimerStart( t, 0.1, true, function Num_Action )
  6.     set t = null
  7. endfunction

  8. function InitTrig_Num takes nothing returns nothing
  9.     local trigger t = CreateTrigger()
  10.         call TriggerRegisterPlayerChatEvent( t, Player(0), “a”, true )
  11.         call TriggerAddAction( t, function Num_ Time )
  12.     set t = null
  13. endfunction
复制代码

为这个触发准备一个整数变量,每次时间到了就加1,之后显示:
  1. globals
  2.     Integer i = 0
  3. endglobals

  4. function Num_Action takes nothing returns nothing
  5.     set i = i+1
  6.     call DisplayTextToPlayer( Player(0), 0, 0, I2S(i) )
  7. endfunction

  8. function Num_ Time takes nothing returns nothing
  9.     local timer t = CreateTimer()
  10.         call TimerStart( t, 0.1, true, function Num_Action )
  11.     set t = null
  12. endfunction

  13. function InitTrig_Num takes nothing returns nothing
  14.     local trigger t = CreateTrigger()
  15.         call TriggerRegisterPlayerChatEvent( t, Player(0), “a”, true )
  16.         call TriggerAddAction( t, function Num_ Time )
  17.     set t = null
  18. endfunction
复制代码

:全局变量的整数要有初始值,因为函数中是直接对其进行加1,若没有初始值似乎不能加。。。(额。。我没试过。。至少有些编程语言里不行)

计时器的用法就这么简单,如果要使计时器停止的话,目前就只能用全局变量和BJ函数的记录最后创建的计时器(这个说实话也是全局变量记录……),然后在需要暂停的时候,使用PauseTimer()这个函数。
当然能暂停就能继续,用ResumeTimer()

计时器销毁
既然计时器是游戏开始后创建的,自然跟点一样需要排泄比如上面这个函数,我想在i==10之后便停止这个动作,于是我们就需要捕捉到这个正在为这个触发计时的计时器。
获取这个计时器的语句是:GetExpiredTimer()
PS:为了缩短页面方便大家浏览,我把这个触发截掉了一部分,之后也是如此,不再提醒。
  1. function Num_Action takes nothing returns nothing
  2.     local timer t=GetExpiredTimer()
  3.     set i = i+1
  4.     if i != 10 then
  5.         call DisplayTextToPlayer( Player(0), 0, 0, I2S(i) )
  6.     else
  7.         set i=0  //顺便清0
  8.         call PauseTimer( t ) //推荐删除前先暂停,不然直接删除可能会有BUG
  9.         call DestroyTimer( t )  //这个语句就是用来删除计时器的
  10.     endif
  11. endfunction

  12. function Num_ Time takes nothing returns nothing
  13.     local timer t = CreateTimer()
  14.         call TimerStart( t, 0.1, true, function Num_Action )
  15.     set t = null
  16. endfunction
复制代码

注意,只能在计时器的对应函数中才能捕捉该计时器,就想捕捉触发单位一样,只有触发了事件的单位才会被捕捉到,且这个捕捉命令必须在这个触发的动作里才行,计时器也是一样,在TimerStart里调用的函数里才能捕捉这个计时器

当然,上面讲的仅仅是直接捕捉的方法,之后会讲到用储存的方法储存计时器(可以理解为每个计时器都有个编号,储存编号而已),然后可以在别的非对应的函数中读取该计时器并进行暂停等相关操作。

计时器的应用:
比如制作击退技能,开启计时器每过Xs使目标单位后退Y距离,后退Z次后删除计时器~

本章完~
回导航楼
回复

使用道具 举报

 楼主| 发表于 2011-7-12 04:32:10 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:31 编辑

第十六章:哈希表——哈希表专用魔免盾


哈希表说:“我有全地图范围的风暴之锤,来学我者必中!”
于是新手们望而却步……
某某人说:“慌什么?我给你们套魔免盾,你们尽管冲!”

魔免盾——开始施法:
想象我们有一个空的大箱子,很多很多新买的空本子,每本本子呢都有很多页,不过每一页都是白纸,什么都没有的白纸,除此之外呢,我们还有个永远不睡觉的管理员。

现在进入剧情模式
        我们大家都住在一起,每人每天都为自己设置了闹钟,比如早晨起床时床边有一个闹钟,比如厨房的微波炉自带一个闹钟。由于闹钟太多了,所以编下号码,比如起床的闹钟就编1号吧。
由于每人起床时间段不同,我们不想因为某些人需要早起,设置了很早的闹铃而影响了别的人,所以我们把闹钟放在屋外,并在前一天晚上,我们告诉那个管理员,明天这个1号闹钟响的时候,一定要把我们中的某些人叫醒,因为这些人要去学校早自习;然后当2号闹钟响的时候,叫醒另外一些人,因为那些人要去上班,剩下的懒虫不用管。
但是我们人很多啊,管理员表示记不住,于是他拿来一本空本子,在封面贴了个大大的标签“1”,表示这是1号闹钟的记录本;然后他翻开本子开始记录我们这些人,他在第一页写了数字1,然后记录了一个人(比如记录面貌名字等);然后翻了一页写了个页码2,又记录了一个人,直到他记录完所有的需要在第一个闹钟响的时候起床的人。记完之后他就把那个本子放进箱子里,因为箱子外的本子太多了,怕弄混。
        随后他又以相同的方式在贴了标签“2”本子内记录了需要在2号闹钟响的时候起床的人,然后放进箱子,并把两本本子按顺序放好。
        第二天,1号闹钟响了,于是,箱子管理员从箱子里拿出封面贴着“1”的本子,翻到第一页后开始找对应的人,找到后就把他弄醒并告诉他需要做的事情。叫完所有人之后管理员发现这本子已经不需要了,于是他撕下标签“1”后就把本子扔进了垃圾桶。(好浪费本子……同学们要珍惜资源,不要学他,但是标签“1”可能还有用,因为1号闹钟可能还要用来计时,所以先留着)为什么要扔掉本子呢?因为没用的本子多了箱子会变重嘛~管理员也是人,本子太多了会搬不动箱子的。
        然后第二个闹钟又响了,管理员便找来标签“2”的本子开始找人,找完后撕下标签把本子扔了。
        于是,这三批人就该干嘛干嘛了~
魔免盾——施法结束

当然,并不是套了魔免盾就能完全抵抗强大的风暴之锤了,你们还需要另外一样东西,就是动脑技能。不过这技能是你们自带的。。。某某人是教不了你们的……

魔免盾施法完了~
PS:请接着看十七和十八章,不然魔免盾的功效到期后就要重新来套。。
回导航楼
回复

使用道具 举报

发表于 2011-7-12 07:25:11 | 显示全部楼层
强贴强力支持
回复

使用道具 举报

 楼主| 发表于 2011-7-12 07:36:18 | 显示全部楼层
本帖最后由 chyj4747 于 2014-11-24 14:31 编辑

第十七章:哈希表(Hashtable)——基础语句

PS:请结合十六章一起看。
其实呢~ 如果你被套了魔免盾后没有出现排斥现象的话~
那么恭喜这位同学,你已经会用哈希表在一个函数里绑定单位到计时器,并在另一个函数里等计时器到期后读取被绑定的单位并命令这个单位做动作了。当然了,还不止~ 你连哈希表的排泄都已经学会了~
好吧。。要是光看那个就能学会大家也不用那么辛苦了。。

现在让我们将第十六章中的故事翻译成J:
首先要理解下什么是哈希表:那个装本子的箱子就是哈希表,那些本子贴的标签叫哈希表的主目录(也有叫父目录之类的),本子的页码叫哈希表的子目录,最后每页记录的人名就是被记录在哈希表的主目录中的子目录中的值了。
至于管理员呢。。就是电脑

比如说我们在地图里放了个单位名字叫A(A就是这个单位的全局变量名,放到地图上WE会自动为其编个号)
然后试着用哈希表完成下面这三件事:
1.    将A存到一个哈希表的1号主目录1号子目录下
2.    从这个哈希表的1号主目录1号子目录中读取A
3.    将A从这个哈希表的1号主目录1号子目录中删除

第一步,我们需要初始化一个哈希表并为其取个名字:
  1. globals
  2. hashtable ht=InitHashtable()
  3. endglobals
复制代码

初始化哈希表一般就用全局变量初始化一个就够了,要存不同的东西完全可以存在一个哈希表的不同目录下。

然后就是储存这个单位:
  1. globals
  2. hashtable ht=InitHashtable()
  3. endglobals

  4. function Save takes unit A returns nothing
  5.     call SaveUnitHandle( ht, 1, 1, A )
  6. endfunction
复制代码

额。。SaveUnitHandle()这个函数不是翻翻字典就能理解的,所以我解释下,前面两个单词很好理解,Save(储存)和Unit(单位);关键是最后一个单词,Handle(手柄、把柄),在第十四章最后部分有提过,除了integer、real、boolean、string之外,都是指针型的数据,所谓指针型数据都是只有一个整数作为地址,然后这些数据就像个路标指针一样指向这个地址,所以叫指针型数据(比如一个农民,其实并没有“农民”这样的数据,而是以一个整数作为数据,然后这个农民就是指向这个整数的指针)

那么这里Handle的意思就是指这个单位的整数地址。
顺带一提,即使数据类型相同,比如都是单位,但是不同的单位整数地址也是不同的。

要举一反三就很简单了,比如储存特效X:
  1. call SaveEffectHandle( ht, 1, 1, X )
复制代码

如果不是指针型的呢,去掉Handle就行了,比如储存实数8.0
  1. call SaveReal( ht, 1, 1, 8.0 )
复制代码


第二步读取:
就是在函数中使用LoadUnitHandle()等这种函数,但是一般读取出来为了方便使用,就直接用变量赋值。
  1. function Load takes nothing returns nothing
  2.     local unit u=LoadUnitHandle( ht, 1, 1 )
  3. endfunction
复制代码

在这个函数中就讲ht中主目录1的子目录1的单位地址读取出来,但后赋值给局部变量u
虽然单位地址之前说过是整数,但是那只是个地址,实际读取出来的是这个单位。

最后一步就是删除哈希表里这个储存的单位了:
  1. call RemoveSavedHandle( ht, 1, 1 )
复制代码

既然单位是指针型数据,也就是有handle,那么管它是什么数据的handle,反正都是handle,直接删handle就行,其它的不是handle型的数据,就要区别开来。
  1. call RemoveSavedInteger( ht,1,1 )
  2. call RemoveSavedReal( ht, 1, 1 )
  3. call RemoveSavedString( ht, 1, 1 )
  4. call RemoveSavedBoolean( ht, 1, 1 )
复制代码


删除一个主目录下所有的子目录:
  1. call FlushChildHashtable( ht, 1 )
复制代码

也就是把主目录1的所有子目录删除了

如果要清理整个哈希表的话,也就是将所有的主目录删除:
  1. call FlushParentHashtable( ht )
复制代码


紧接十八章……
回导航楼
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-11-21 19:19 , Processed in 0.239515 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表