偶久网

 找回密码
 注册会员

QQ登录

只需一步,快速开始

搜索

网站魔兽热门地图

查看: 15425|回复: 17

JASS教程—0基础新手向教程

  [复制链接]
邪恶叔 title=
发表于 2016-8-20 16:29:11
第一章:魔兽程序的概念
先举个非常简单的例子:计算器
当你输入 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用于魔兽的各种写法还是有一定差距的;另外,上面这个函数还是少了一点东西,下一章节会讲。
邪恶叔 title=
 楼主| 发表于 2016-8-20 16:32:47
第二章:第一章续——函数的完整写法
先把上一章节的函数搬过来:
  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
复制代码


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

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:33:30
第三章:条件判定句——基础

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

首先,什么是“条件判定”?
非常简单,比如“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中所有的起始和结尾引导词都是这种写法

“条件判定句”基础部分完~

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:34:46
第四章:条件判定句——进阶部分(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的情况

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:35:29
第五章:条件判定句——进阶部分(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,否则没动作

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

And和Or是可以混用的,但是一定要注意括号的使用
直接举例说明吧:
如果(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

“条件判定句”进阶部分完~

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:37:56
第七章:变量进阶

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

如果有三个函数,第一个是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

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

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:40:55
第十章:循环(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

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:42:05
第十一章:触发器的创建、注册事件、添加条件和动作

一个触发器的“事件”就是运行“动作”的引导,即只有遇到了指定“事件”魔兽才会去运行指定的“动作”;至于“条件”呢,就是在遇到“事件”后,魔兽还要经过“条件”的同意后才会运行“动作”,即“条件”的结果是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中的顺序:
由于初始化触发器中调用了“条件”和“动作”,所以,初始化触发器这个函数必须放在“条件函数”和“动作函数”的下面。

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

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:48:48
第十四章:注释 & 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),用作全局变量的时候视情况而定。

邪恶叔 title=
 楼主| 发表于 2016-8-20 16:49:48
第十五章:计时器

参数类型: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次后删除计时器~


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