魔兽世界修改安全问题JASSHELPER问题

查看: 9591|回复: 19
【转自GA作者:chyj4747】JASS教程——0基础新手向教程
点击注册会员,更多精品好玩的地图等你来下载!
才可以下载或查看,没有帐号?
LZ是看这篇和冰块那篇学会JASS,当然LZ之前也看过不少教程其中YDWE的JASS教程也不错通俗易懂具体更新内容请看顶楼的更新说明~
说明:教程导航详见,不过看之前请先看下顶楼的“补充”部分
常言道:前言就是废话,废话即可忽略。
于是我决定不写废话,我写序言~
& && &很多教程(即便是所谓新手向的),一上来说的内容基本上是“该编程语言是什么样的语言”、“该语言的特色”、“运算符”及“常量&变量”(各种讨论变量的概念、变量的类型及不同变量的区别),其实那种教程我个人认为更适合给知道编程是个什么东西,但是却不知道怎么编的人看。新手们如果连编程的概念都没有,看那些东西马上就晕。
& && &我自己以前0基础的时候看过C语言的入门教程,那是神马入门啊。。上来讲的第一个内容是C语言发展历史。。我想问这跟C语言的使用有关吗。。就算有,比如几个有趣的历史BUG,那也要等学有所成的时候才看得懂吧。。
& && &所以我个人认为那样的教程反而会增加0基础的新手们学编程的难度,在该教程中,我会尽量以我自己学编程的经验,写出能使0基础的新手们也看的懂看得明白的JASS教程~
先附上些其它我觉得比较好的教程,当然我不可能看过全世界所有的教程,以下仅是我看过的觉得蛮好的教程:
PS:以下有些内容需要新人们有一定的基础,否则不推荐看,去看的后果只有一个,你会晕的……
以下是传送门:
冰块前辈的教程:
(该教程也比较适合新人。其实冰块前辈的教程也是驱使我来写这篇教程的原因,因为冰块写到重要的地方就不更新了……)
U9的acomc的教程(他在GA也叫这个名字):
(计时器和哈希表的使用,我是从这篇教程学的,不过因为我学之前已经有编程基础了,看得还挺轻松的~) LZ比较的懒,原帖链接也失效了,所以就懒得找了
血戮魔动冰前辈的哈希表教程:
(非常详细的哈希表教程,但是。。如果没基础,还是先别传过去送了吧……)
写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能检查语法,无需另去下载,就这样。
最新更新说明:&&在第十八章补充了StringHash()& && &
& && && && && && && && &&& 修正了第六、七两章的错误(感谢archxm指出~)
& && && && && && && && &&& 进行了维护修正工作~(调整了教程的部分说明、修正了几个错别字)
& && && && && && && && &&& 进行了维护修正工作~
补充:首先,教程一般不会短到哪去,看的时候要有耐心~ 虽然我是以我认为的最简洁易懂的方式来写的,但长度是不可避免的。
对教程有什么意见或建议尽管提~ 不过当然是有用的……纯灌水等无益的还是算了
当然了,由于是第一次写这么长的教程,肯定会有疏漏的地方导致误解或无法理解,这些只能在发现之后做修改了,无法做到一步到位,发现错误的同学请立刻提出来~谢谢
PS:最后我还是决定不教库&域和宏,尤其是宏,虽然不难,但是入门者最需要的是先掌握基础,我认为这两个都是属于进阶物,所以就不放在这个基础教程里了~
不过库&域这个东西如果同学们需求量大的话我可以简单讲下……或者我建议你们去看acomc的教程(也算是新手教程吧,链接在上面),库&域的讲解在他那篇教程的最后部分
第十八章:哈希表——基础应用
现在我们来看下十六章中的剧情弄成J到底是怎么样的。
先假设事件为任意单位使用“睡眠”技能后触发,函数就叫F吧,技能目标叫A吧:
[jass]function F_Act takes nothing returns nothing
& & local unit A=GetSpellTargetUnit()
& & set A=null
endfunction
function F_Cond takes nothing returns Boolean
& & return GetSpellAbilityId() == “睡眠”
endunction
function Init_F takes nothing returns nothing
& & local trigger trig=CreateTrigger()
& && &&&call TriggerRegisterAnyUnitEvent( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
& && &&&call TriggerAddCondition( trig, Condition(function F_Cond) )
& && &&&call TriigerAddAction( trig, function F_Act )
& & set trig=null
endfunction[/jass]
PS:还是再提醒下吧,之后我就把没改变的函数截掉了。
显然需要个计时器:
顺便简化下动作部分,就事件触发3s后叫醒A这个单位好了
[jass]function F_TimeAct takes nothing returns nothing
endfunction
function F_Act takes nothing returns nothing
& & local timer t=CreateTimer()
& & local unit A= GetSpellTargetUnit()
& && &&&call TimerStart( t, 3, false, function F_TimeAct )
& & set t=null
& & set A=null
endfunction[/jass]
同学们应该发现了,这个F_TimeAct是“动作”或者说“代码”,也就意为着不能有参数,那么用局部变量记录的单位A无法用传参的方式从F_Act传递到F_TimerAct(注意局部变量只能在一个函数内部使用,别的函数无法使用的,要用的话就必须使用某种手段传递过去),有的同学说用全局变量就好了啊,的确能解决问题,但是注意全局有可能会造成多人使用时变量冲突,尤其是在做技能的时候,一个技能若被多次使用,那么这个全局变量会被最后使用的技能覆盖,也就造成了变量冲突,使用局部变量就能简单有效地避免这样的问题。
虽然这个单位变量无法传递,但是这个计时器却可以传递:
[jass]function F_TimeAct takes nothing returns nothing
& & local timer t=GetExpiredTimer()
& & set t=null
endfunction[/jass]
那么有什么方法能使单位随着这个计时器一起传过去吗?
这就要用哈希表把单位A和这个计时器绑定了,换成哈希表的术语说,就是把这个单位A储存在计时器的整数地址主目录下。
估计某些同学忘了,于是复习一下,除了基础数据外的数据都是指针型数据,都有个整数地址,计时器也是个数据类型,自然也有整数地址。
也就是先在F_Act里记录,之后便可以在F_TimeAct里读取,相当于管理员在闹钟响后取出对应闹钟编号的本子查看人名一样。
但是问题又来了。。如何获得计时器(或者其他指针型数据)的整数地址。。
这就要用到GetHandleId()这个函数了,所有的指针型数据都可以用这个来获取整数地址。
记得初始化哈希表~
[jass]globals
& & Hashtable ht=InitHashtable()
endglobals
function F_TimeAct takes nothing returns nothing
& & local timer t=GetExpiredTimer()
& & set t=null
endfunction
function F_Act takes nothing returns nothing
& & local timer t=CreateTimer()
& & local unit A= GetSpellTargetUnit()
& & local integer i=GetHandleId( t )
& && &&&call TimerStart( t, 3, false, function F_TimeAct )
& && &&&// 被储存的是单位不是计时器,计时器只是作为主目录编号
& && &&&call SaveUnitHandle( ht, i, 1, A )
& & set t=null
& & set A=null
endfunction[/jass]
之后在F_TimeAct中读取这个单位,当然了,还要再获取次这个计时器的地址:
[jass]globals
& & Hashtable ht=InitHashtable()
endglobals
function F_TimeAct takes nothing returns nothing
& & local timer t=GetExpiredTimer()
& & local integer i=GetHandleId( t )
& & local unit u=LoadUnitHandle( ht, i, 1 )
& & set t=null
& & set u=null
endfunction
function F_Act takes nothing returns nothing
& & local timer t=CreateTimer()
& & local unit A= GetSpellTargetUnit()
& & local integer i=GetHandleId( t )
& && &&&call TimerStart( t, 3, false, function F_TimeAct )
& && &&&// 被储存的是单位不是计时器,计时器只是作为主目录编号
& && &&&call SaveUnitHandle( ht, i, 1, A )
& & set t=null
& & set A=null
endfunction[/jass]
这样单位A就从F_Act传到F_TimeAct了,最后就是在F_TimeAct中写让A做的动作,比如要叫醒A,那么就使用UnitWakeUp()这个函数。。额。。不过这个函数不会影响催眠魔法效果(我没有试过,所以不是很清楚,想知道的同学可以自己试试~)
记得计时器的排泄~(这里的计时器是一次性的,所以不用先暂停以避免BUG了)
[jass]function F_TimeAct takes nothing returns nothing
& & local timer t=GetExpiredTimer()
& & local integer i=GetHandleId( t )
& & local unit u=LoadUnitHandle( ht, i, 1 )
& && &&&call UnitWakeUp( u )
& && &&&call DestroyTimer( t )
& & set t=null
& & set u=null
endfunction[/jass]
最后的最后,就要对哈希表进行排泄了,记得那个管理员把第一批人叫醒后做了什么吗?
撕下“1”这个标签,把本子扔了……
也就是把计时器整数地址的子目录全部清除了,因为之后绑定到闹钟1上的人可能不同了,虽然也可能重复,或者说可以新数据覆盖旧数据,但是一直放在箱子里很占重量的……
有人想偷懒不排泄,于是不服:一本本子能有多重?
那如果有十万本呢?并且其中99999本没用呢?
所以排泄有益于减少内存的占用,提高运行速度~
PS:一般比较常用的是清除一个主目录下的所有子目录,子目录里的数据一般都是被新数据覆盖的,不用替换数据前特地清除一下
[jass]function F_TimeAct takes nothing returns nothing
& & local timer t=GetExpiredTimer()
& & local integer i=GetHandleId( t )
& & local unit u=LoadUnitHandle( ht, i, 1 )
& && &&&call UnitWakeUp( u )
& && &&&call DestroyTimer( t )
& && &&&call FlushChildHashtable( ht, i )
& & set t=null
& & set u=null
endfunction[/jass]
之前忘说了于是补上:每个子目录只能记录一个数据。
举个实例的思路吧:
比如击退,施放技能后局部变量获取被击退目标,记作u,绑定u到一个计时器t,然后开启计时器t每X秒运行另一个函数F;
F中获得到期的计时器t,读取绑定的单位u,移动u,用一个整数i记录移动的次数,也就相当于t的循环次数,当达到一定值后(用条件判定式判断)停止t,删除t,清空哈希表中储存在主目录t下的所有子目录。
有的同学可能觉得把计时器的地址作为子目录也行,主目录用普通整数(1,2,3,……),是没问题,但是存多个单位时就要改变主目录或者让计时器的地址做算术运算,对于前者,清理的时候要清除主目录而不是子目录,也就会把其它储存的主目录也删掉,自然不行;对于后者。。不仅麻烦,也可能会造成主目录冲突,因为主目录是用数字,而触发做多了之后又记不得哪个用的数字1,哪个用的数字2,万一不小心弄了两个一样的主目录,就冲突了,而且这问题排查的时候还很麻烦,但是用数据的地址就不同了,因为每个数据即使类型相同,地址也是不同的。
& && &如果要以一个string作为主目录储存怎么办?因为目录必须都是整数,所以字符串明显不能作为目录嘛……这时就需要用到StringHash这个东西,这个函数能把一个string转成一个整数地址,当然,相同的string得到相同的整数,不同的自然不同~
& && & 比如说StringHash(&a&)就把a这个字符串转成了一个整数地址,具体是多少我没研究过不太清楚。。想知道的同学可以用 I2S 这个函数把得到的整数再转成string后看一下。
& && &用法非常简单,之前把单位绑计时器的时候是GetHandleId( GetExpiredTimer() )作为主目录,如果要把这个单位绑到“a”这个string上呢就StringHash( &a& )作为主目录即可~
现在同学们可以去看下血戮魔动冰前辈的哈希表教程了,链接在顶楼,虽然可能还是会晕 ^_^
第十章:循环(loop)
循环在程序中是个什么概念?
比如现在有个程序,功能是给甲乙丙丁四个人按顺序发牌,第一张给甲,第二张给乙,……,以此类推,直到所有的牌发完。这就是一个循环。
现在来看下J的写法:
[jass]function 发牌 takes nothing returns nothing
& & local integer 牌数 = 52
& && &&&exitwhen 牌数==0
& && && && &……(发牌动作)
& && &&&set 牌数=牌数-1
& & endloop
endfunction[/jass]
很明显,引导词是“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。
[jass]function 发牌 takes nothing returns nothing
& & local integer 牌数 = 52
& & local integer n = 0
& && &&&exitwhen 牌数==0
& && && && &if n==0 then
& && && && && & 发牌给甲
& && && && && & n=n+1
& && && && &elseif n==1 then
& && && && && & 发牌给乙
& && && && && & n=n+1
& && && && &elseif n==2 then
& && && && && & 发牌给丙
& && && && && & n=n+1
& && && && &elseif n==4 then
& && && && && & 发牌给丁
& && && && && & n=0
& && && && &endif
& && &&&set 牌数=牌数-1
& & endloop
endfunction[/jass]
PS:这段函数可以把n=n+1提出来,在发牌的判定句结束之后再写,缩短函数,不过写成这样比较直观,所以我就先不简化了。
练习(大家自己试一下~):
按下ESC在屏幕左下角按递增顺序显示1-10这10个数字。
思路(按Ctrl+A查看):
先设置局部整数变量n为1,循环 - 当n大于10时跳出循环,call DisplayTextToPlayer(……, I2S(n)),设置n=n+1
第十三章:单位组&玩家组
单位组这个数据类型在J中是:group(本意是组、团队)
玩家组是:force(本意是力量)
虽然同是“组”,中文可以用单位和玩家来区别,但是英文不行,写成unit_group和player_group的话不仅占地方而且写起来很麻烦。所以玩家组就用force这个词,在这里就是“势力”的意思。
这两个数据使用方法其实差不多,这里主要讲group的使用。
首先,如果什么都不做,魔兽中是没有group这个东西的,所以我们要做的第一件事是新建一个单位组。
之前我们已经见过了新建单位的写法,新建单位组也是类似:CreateGroup();也就是凡是新建,基本都是用Create这个单词。
在J中,几乎所有的数据都可以当做变量,所以单位组的申明可以在globals里,也可以使用local:
[jass]globals
& & group g=CreateGroup()
endglobals
function G takes nothing returns nothing
& & local group g=CreateGroup()
& & set g=null
endfunction[/jass]
注:若以局部变量的方式,则记得要排泄。
PS:两种方式需要根据不同的情况灵活使用。
单位组的作用呢就是可以让我们只发布一次命令就让一堆单位按这个命令行动,而不再是对这些单位一个个发布命令。当然还有其它的好处,之后遇到的时候就会明白的。
先看下如何往单位组里加单位以及移除单位:
首先我们需要一个单位组:
[jass]globals
& & group g=CreateGroup()
endglobals[/jass]
于是我们就有了一个新建的空单位组。
[jass]globals
& & group g=CreateGroup()
endglobals
function add_del takes nothing returns nothing
& & call GroupAddUnit( g, gg_unit_hpea0000 )
& & call GroupRemoveUnit( g, gg_unit_hpea000 )
endfunction[/jass]
注:gg_unit_hpea0000是已经在地图上放置的农民,大家放置时的编号可能不同。
在add_del这个函数中调用的两个本地函数的意思就是为一个单位组添加/删除单位。
Add:加;Remove:移除、去除
这两个函数需要的参数相同,都需要一个已经存在的单位组,一个将被添加/删除的单位。
但是添加单位这个函数的缺陷很明显,一次只能添加一个,如果有一群单位的话一个个添加就太繁琐了。
这种时候就要使用“选取范围内所有单位”这个函数:
[jass]native GroupEnumUnitsInRange takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing[/jass]
注:看不懂函数名的同学请回顾前面几章,已经教了很多次翻译函数名的方法了,之后也不再逐一翻译了。
boolexpr filter:这个参数是布尔表达式,简单来说就是一个判断对错的函数。
这个本地函数的意思是往一个group里添加以(x,y)为圆心范围radius之内且符合filter的所有单位。
那如果不要条件呢?可以这么写:
[jass]call GroupEnumUnitsInRange( group, 0, 0, 100, null )[/jass]
null在这里就表示无条件,也就是添加范围内的所有单位。
添加完单位后就需要对这个单位组里的单位发布命令了:
我们先转化“单位组-选取(单位组)内的单位做动作”这个T动作到J看下函数
[jass]call ForGroupBJ(……)[/jass]
转化后发现是这么个东西,再到function list查一下,原来这个函数是调用了ForGroup这个函数,另外还有一个变量以及调用了DestroyGroup(删除单位组)这个函数。
也就是T中的这个选取单位组做动作在做完动作后直接删除了这个单位组,但是这么写其实会降低运行效率,因为多调用了一次ForGroup这个函数。反正这个函数也是调用了ForGroup和DestroyGroup,我们直接写这两个函数就行了,而且这样写的另一个好处是什么时候删除单位组是我们自己掌控的。
注:ForGroupBJ并不是无用,当需要选取比如某个玩家的所有单位做一次动作的时候用这个函数写起来就比较省力了。
ForGroup这个函数除了单位组外,还需要一个code,也就是我们需要另外写一个函数,其功能就是让这个单位组内的单位做动作。当然了,这个code是“被使用”,所以要放在前面。
先了解下下面这两个函数的作用,之后看实例来理解吧……
(其实是我没想到好的讲解方法。。)
这两个函数都不需要参数:
GetEnumUnit():相当于T中的“选取单位”,在选取单位组做动作时用来在动作函数中获取被选取的单位。
GetFilterUnit():相当于T中的“匹配单位”,在选取符合条件的单位添加到单位组时用来在条件函数中获取被选取的单位。
我们直接看一个实例:
山丘之王的雷霆一击大家都知道吧(不知道的同学请使用人族首发山丘之王学习第二个技能打一盘常规战……),现在我们想知道山王敲地板时有多少单位在该技能范围内(不包括友军)。
PS:暂时只考虑一级的雷霆一击,不然可能要用到还没学的东西。
先想下我们需要什么事件,既然是在山王施放雷霆一击时显示单位数,则我们需要山王开始放技能事件。不过由于山王不是一开始就放在地图上的,我们不能使用指定单位事件,只能使用任意单位事件。
触发器就叫“LTYJ”(Lei Ting Yi Ji)吧……
[jass]function InitTrig_LTYJ takes nothing returns nothing
& & local trigger trig=CreateTrigger()
& && &&&call TriggerRegisterAnyUnitEventBJ( trig, )
& && &&&call TriggerAddCondition( trig, Condition( function ))
& && &&&call TriggerAddAction( trig, function )
& & set trig=null
endfunction[/jass]
注:TriggerRegisterAnyUnitEventBJ就是“触发器注册任意单位事件”,不过这是个BJ函数。
在“物体编辑器”中找到“雷霆一击”,按Ctrl+D查看编号;或者在T中写一个含有该技能的动作然后转J查看。得到该技能的编号是:‘AHtc’。顺便看下一级雷霆一击的范围,是250码。
然后补上Condition:
[jass]function LTYJ_Condition takes nothing returns boolean
& & return GetSpellAbilityId() == ' AHtc '
endfunction
function InitTrig_LTYJ takes nothing returns nothing
& & local trigger trig=CreateTrigger()
& && &&&call TriggerRegisterAnyUnitEventBJ( trig, )
& && &&&call TriggerAddCondition( trig, Condition( function ))
& && &&&call TriggerAddAction( trig, function )
& & set trig=null
endfunction[/jass]
注:GetSpellAbilityId()就是获取施放的技能的ID,无需参数,如果是T的话就相当于在“条件”中判断施放技能是否为是雷霆一击。
再补上动作部分,我们需要先选取山王周围250范围内的非友军单位(确定了“动作”的名字,就顺便把初始化函数里的事件条件动作也补上,我这里事件用的是“发动技能效果”):
[jass]function LTYJ_Condition2 takes nothing returns boolean
& & return IsUnitEnemy( GetFilterUnit(), GetTriggerPlayer() ) == true
endfunction
function LTYJ_Action takes nothing returns nothing
& & local group g=CreateGroup()
& & local unit u=GetTriggerUnit()
& & call GroupEnumUnitsInRange( g, GetUnitX(u), GetUnitY(u), 250, Condition(function LTYJ_Condition2) )
set g=null
set u=null
endfunction
function LTYJ_Condition takes nothing returns boolean
& & return GetSpellAbilityId() == ' AHtc '
endfunction
function InitTrig_LTYJ takes nothing returns nothing
& & local trigger trig=CreateTrigger()
& && &&&call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
& && &&&call TriggerAddCondition( trig, Condition( function LTYJ_Condition ))
& && &&&call TriggerAddAction( trig, function LTYJ_Action )
& & set trig=null
endfunction[/jass]
接着就是显示单位组中的单位数量
有一个BJ函数叫CountUnitsInGroup,在T里就是获取“某单位组中的单位数量”,大家直接用这个就行,不过这里使用ForGroup,其实CountUnitsInGroup也调用了ForGroup。
我们先加上ForGroup,顺便先为其准备一个空代码:
[jass]function LTYJ_Action2 takes nothing returns nothing
endfunction
function LTYJ_Condition2 takes nothing returns boolean
& & return IsUnitEnemy( GetFilterUnit(), GetTriggerPlayer() ) == true
endfunction
function LTYJ_Action takes nothing returns nothing
& & local group g=CreateGroup()
& & local unit u=GetTriggerUnit()
& & call GroupEnumUnitsInRange( g, GetUnitX(u), GetUnitY(u), 250, Condition(function LTYJ_Condition2) )
& & call ForGroup( g, function LTYJ_Action2 )
set g=null
set u=null
endfunction
function LTYJ_Condition takes nothing returns boolean
& & return GetSpellAbilityId() == ' AHtc '
endfunction
function InitTrig_LTYJ takes nothing returns nothing
& & local trigger trig=CreateTrigger()
& && &&&call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
& && &&&call TriggerAddCondition( trig, Condition( function LTYJ_Condition ))
& && &&&call TriggerAddAction( trig, function LTYJ_Action )
& & set trig=null
endfunction [/jass]
再往LTYJ_Action2中添加我们需要的动作,也就是数数(由于还没学过哈希表,所以用全局变量计数,若局部变量的话无法叠加,因为在一个函数里用完就自动被清除了);
最后添加显示最后的结果到ForGroup下面:
[jass]globals
& & integer n=0
endglobals
function LTYJ_Action2 takes nothing returns nothing
& & set n = n+1
endfunction
function LTYJ_Condition2 takes nothing returns boolean
& & return IsUnitEnemy( GetFilterUnit(), GetTriggerPlayer() ) == true
endfunction
function LTYJ_Action takes nothing returns nothing
& & local group g=CreateGroup()
& & local unit u=GetTriggerUnit()
& & call GroupEnumUnitsInRange( g, GetUnitX(u), GetUnitY(u), 250, Condition(function LTYJ_Condition2) )
& & call ForGroup( g, function LTYJ_Action2 )
& & call DisplayTextToPlayer( Player(0), 0, 0, I2S(n) )
set g=null
set u=null
endfunction
function LTYJ_Condition takes nothing returns boolean
& & return GetSpellAbilityId() == ' AHtc '
endfunction
function InitTrig_LTYJ takes nothing returns nothing
& & local trigger trig=CreateTrigger()
& && &&&call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
& && &&&call TriggerAddCondition( trig, Condition( function LTYJ_Condition ))
& && &&&call TriggerAddAction( trig, function LTYJ_Action )
& & set trig=null
endfunction [/jass]
注:别忘了把WE里触发器的名字改成初始化函数使用的名字,忘了的同学请回顾触发器写法那两章。
大家自己想几个简单的题目练习下吧,主要熟悉下GroupEnumUnitsInRange和ForGroup这两个函数。
比如选取一定范围内的单位然后改变他们的移动速度,改变速度这个函数可以在T中“动作”的“单位”里找到,然后转J看下就知道了。
第一章:魔兽程序的概念
先举个非常简单的例子:计算器
当你输入 1+1= 之后,就会出来数字2
其中 1+1= 一共两个数字两个符号,是需要输入的东西,在程序中叫做参数,得到的答案2(即计算器运算程序计算后得到的值)叫返回值。
那么这个程序在JASS中是什么样子的呢?
首先,我们必须输入一个特定的东西(单词、符号之类的)来告诉电脑,接下来的这串内容是JASS程序。在JASS中,用function这个单词来开头。(这个单词是函数的意思)
[jass]function&&xxxxxxx[/jass]
于是电脑就知道这个function后面的那串x是程序,接下来用到这个函数的时候要按照这个里面写的来执行。
现在回到刚才 1+1=2 这个例子,之前说了,参数是需要输入的,但是输入的参数可不止 1+1= 这么些,也可能是 1+2= 、2+5= 等等。也就是说,输入的参数是“第一个数字”“运算符”“第二个数字”,然后等号的意思就是告诉电脑该出返回值了。
与之前function同理,必须用一个引导词告诉电脑接下来是参数,以及用另一个引导词告诉电脑这个函数有一个返回值。
JASS中的写法就是:
[jass]function takes 第一个数字,第二个数字 returns 一个数字[/jass]
其中takes就是参数的引导词,retrurns就是告诉电脑有返回值,不同的参数之间用逗号隔开。
突然发现上面这个函数没有“运算符”这个参数,并且仅仅是告诉了电脑有一个返回值,但究竟怎么得到这个值电脑还是不知道。
于是,我们要把运算过程告诉电脑:
[jass]function takes 第一个数字,第二个数字 returns 一个数字
& & return 第一个数字 + 第二个数字[/jass]
这个函数第二行的意思就是:返回&&第一个数字加上第二个数字所得到的答案;也就是,返回值 = 第一个数字 + 第二个数字
至此,这个加法函数的功能就编完了,但是JASS还不知道这个函数已经结束了,怎么办?
……对了,用个引导词告诉它这个函数结束了就行了~
在结尾加上endfunction,即
[jass]function takes 第一个数字,第二个数字 returns 一个数字
& & return 第一个数字 + 第二个数字
endfunction[/jass]
PS:return前面空四格是很好的编程习惯,为了下次看的时候能很清楚地区分函数的开头结尾和中间的运算过程。这种空格在编程中叫做缩进(indentation)。
小技巧:当光标在最前面时,按TAB键直接缩进四格。
最后,就是使用这个函数的方法了,并不是写成&&1+1&&这种数学式,而是
这个函数(第一个数字,第二个数字)这种形式,
比如 1+1 就是 这个函数(1,1),但是每次都叫“这个函数”很傻,函数多了电脑也不知道我们说的到底是哪个函数,所以给每个函数一个名字也是必须的。
既然这是加法那就叫“add”吧,同时,JASS程序中是不能用中文的,所以换成英文(num是number的缩写):
[jass]function add takes num1, num2 returns num3
& & return num1 + num2
endfunction[/jass]
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用于魔兽的各种写法还是有一定差距的;另外,上面这个函数还是少了一点东西,下一章节会讲。
本章完~& && && && && && && && && && &
第二章:第一章续——函数的完整写法
先把上一章节的函数搬过来:
[jass]function add takes num1, num2 returns num3
& & return num1 + num2
endfunction[/jass]
上面这段函数呢,魔兽还是看不懂的,因为我们还没告诉它这些数字的类型,不告诉它JASS就不肯运行。(所谓类型,就是比如整数、实数这种,当然JASS中对于数字也只有这两种。)
那么怎么告诉它……用引导词:
比如 1+1 什么的都是整数的运算,那么这两个参数(num1和num2)就是整数类型。(不同的数字类型现在不考虑)
整数这个词在英文中是integer
[jass]function add takes integer num1, integer num2 returns num3
& & return num1 + num2
endfunction[/jass]
如果是实数呢,就用real
[jass]function add takes real num1, real num2 returns num3
& & return num1 + num2
endfunction[/jass]
我们还发现,returns后面是一个具体的数字num3,这要怎么返回一个任意值?
所以返回的不是一个数字,而是这个数字的类型
[jass]function add takes integer num1, integer num2 returns integer
& & return num1 + num2
endfunction[/jass]
[jass]function add takes real num1, real num2 returns real
& & return num1 + num2
endfunction[/jass]
至此,这个函数才是真正地写对了。
第三章:条件判定句——基础
(这名字是我自取的。。其实叫什么判断式之类的都行。。)
首先,什么是“条件判定”?
非常简单,比如“2是否大于1”,这个就是条件,之后对其判定,发现这个条件是对的(有谁敢说这是错的吗……站出来!)。这整个过程就是“条件判定”。
那什么是“条件判定句”呢?
也是非常简单的~句型如下:
如果(某个条件对了)那么(做某些动作)否则(做另外一些动作)
对应到游戏中呢,就比如“如果(你挂了),那么(你输了),否则(你赢了)”
这就是“条件判断句”的基本句型。
那么放到JASS里是怎么样呢?
我们先为前两章用的加法函数加些条件,以及符合条件后相对应的动作:
(为了减少打字数,我假设A=“第一个数”,B=“第二个数”)
条件:如果A & B,那么A-B,否则A+B
& && &&&这里就要说到那些“比较符”了,就是“大于”、“小于”、“大于等于”、“小于等于”、“等于”、“不等于”这些,前四个在JASS中对应的就是“&”、“&”、“&=”、“&=”;
& && &“等于”和“不等于”在JASS有些不同,“等于”是“==”,“不等于”是“!=”,不等号这个好理解,大家在键盘找一圈就明白了,没有把等号划掉这种符号;但是等号为什么要两个“=”呢?因为在JASS中有一个更加常用到的东西占用了一个“=”(之后讲变量时会讲),所以等号就只能是“==”了。
& && &另外“不等于”在JASS中还经常表现为“not&&A==B”这个形式,按字面理解就是“不是A等于B”,虽然有点绕但却也是种常用的写法。(注意:not是个关键词(与引导词类似),需要与前后的内容空开)
现在回到刚才说的加条件那,把那个判定式加入函数看看是什么样的:
[jass]function add takes integer A, integer B returns integer
& & 如果 A & B 那么
& && &&&return A – B
& && &&&return A+B
& & 结束判定句
endfunction[/jass]
我们发现,判定句跟function一样有起始和结束引导词,中间还有“那么”和“否则”这两个过渡引导词,转成英文来看:
[jass]function add takes integer A, integer B returns integer
& & if A & B then
& && &&&return A – B
& && &&&return A+B
endfunction[/jass]
(注:else本意是“其它”,这里指代其它情况,即不是A&B的情况下)
然后不难发现,结束引导词都是“end+起始引导词”的形式,Jass中所有的起始和结尾引导词都是这种写法
“条件判定句”基础部分完~
第四章:条件判定句——进阶部分(1)
像第三章的写法,不难发现其实只能有两个条件,比如第三章的例子中就是A&B和A&=B这两种,如果需要更多的判断怎么办?
比如:如果A==1,那么……,如果A==2,那么……,如果B==1,那么……,否则……
这明显一个判定式远远不够嘛……
于是有人就想了,既然一个判定式是 如果(条件)那么(动作)否则(另外的动作),那么在“否则”里再加判定式,即
如果(条件1)那么(动作1)否则(如果(条件2)那么(动作2)否则(如果(条件3)……))
恭喜这位同学回答正确~但是你看着晕不晕啊?……
这种情况是有两种常用写法的。
首先第一种:
在JASS中,如果一个函数你不给它写任何的运算式,这个程序不会有任何功能,即无动作,比如下面这个
[jass]function F takes integer A returns nothing
endfunction[/jass]
(注:nothing就是指 返回值==没有,也就是没有返回值)
这是个正常的可以用的函数,只不过没有任何功能罢了。那么条件判定句中也是如此,“那么”和“否则”之后你不加任何东西的话就是“无动作”
于是多个条件就能写成:
如果(条件1)那么(动作1)否则(),如果(条件2)那么(动作2)否则()……
是不是看起来稍微好了点。。。
放到JASS中:
[jass]function add takes integer A, integer B returns integer
& & if A==1 then
& && &&&return ……
& & if A==2 then
& && &&&return ……
endfunction[/jass]
然后,这个判定句还可以再优化下,去掉那些多余的没用的东西:
[jass]function add takes integer A, integer B returns integer
& & if A==1 then
& && &&&return ……
& & if A==2 then
& && &&&return ……
endfunction[/jass]
注:else本身就不带动作,所以不写对函数本身不造成任何影响
现在来看看第二种写法:
其实用中文的话可以这么说:
如果(条件1)那么(动作1),非之前的情况下如果(条件2)那么(动作2),非之前的情况下如果(条件3)那么(动作3)……
有木有发现这个“非之前的情况下如果”很牛呢~相当于之前的条件全错的情况下,如果下一个条件成立就怎么怎么样……
如果JASS中有就好了啊~~~~~
JASS说:“我还真有 = =”
稍微加大点跨度,直接加到函数转成英文:
[jass]function add takes integer A, integer B returns integer
& & if A==1 then
& && &&&return ……
& & elseif A==2 then
& && &&&return ……
& & elseif B==1 then
& && &&&return ……
& && &&&return ……
endfunction[/jass]
注:这个 elseif 就有“非之前的情况下如果”的效果,同时,因为它有“if”的效果,需要“then”作过渡引导。
PS:当然了,上面这个函数若else没有动作的话也可以省掉不写。
是不是觉得这第二种方法好看很多呢~
当然这两种方法在这个情况下是可以通用的(我推荐第二种写法,因为简单好看~)
注:以下内容若看不懂可以直接忽略,以后直接会懂的,不用再来看
以上两个方案并不是哪都通用的。方法1呢,是在需要按顺序判断的情况下并且动作不在一个系统里面时用的,比如下面这个J(JASS的简称)
[jass]function F takes …… returns ……
& & if 单位生命值&100 then
& && && &……
& && && &……
& & if 单位魔法值&100 then
& && && &……
& && && &……
endfunction[/jass]
很明显在判断HP时如果把判断MP的判定式用elseif套在里面会直接跳过MP的判断,也就是当HP大于100时,就无视了MP的判断。
但是,如果是“单位等级==1时” “单位等级==2时”……“单位等级==n时”这种条件,用方法2的写法就不会做多余的判断,比如单位等级为1,那么判断为1之后就不会接着判断等级不为1的情况
第五章:条件判定句——进阶部分(2)
如果大家忘了判定句的内容呢,回前一章回顾一下吧~
除了第四章所讲的多个判定句的情况呢,还有一种比较常见的情况:
不是每个条件成立对应一个动作,而是多个条件成立了才做一个动作
比如:要同时A==1,B==1的情况下,才把A和B加起来,别的情况不做动作。
像这种需要两个或更多条件成立才做动作的呢,可以用一个关联词“and”进行连接,也就是:
如果(第一个条件成立)并且(第二个条件成立)并且……并且(第N个条件成立),那么(动作1),否则(……)
其中,“并且”就是关联词“and”,放到JASS中看下:
[jass]function add takes integer A, integer B returns integer
& & if (A==1) and (B==1) then
& && &&&return A+B
endfunction[/jass]
注:A==1与B==1不一定要括号,这里是为了区分清楚。
以上是两个或更多条件同时成立的情况,如果要众多条件中的任意一个成立呢,就用另一个关联词:or
[jass]function add takes integer A, integer B returns integer
& & if (A==1) or (B==1) then
& && &&&return A+B
endfunction[/jass]
这段函数的意思就是当A==1或者B==1时,返回A+B,否则没动作
---------------------------------------------&&进一步进阶的分割线&&---------------------------------------------
And和Or是可以混用的,但是一定要注意括号的使用
直接举例说明吧:
如果(A==1并且B==1)或者(A==2并且B==2),那么(A+B),否则无动作
[jass]function add takes integer A, integer B returns integer
& & if (A==1 and B==1) or (A==2 and B==2) then
& && &&&return A+B
endfunction[/jass]
PS:如果去掉条件中的括号,整个条件就很混乱了……
如果(A==1或者B==1)并且(C==2或者D==2),那么(A+B),否则(C+D)
[jass]function add takes integer A, integer B, integer C, integer D returns integer
& & if (A==1 or B==1) and (C==2 or D==2) then
& && &&&return A+B
& && &&&return C+D
endfunction[/jass]
中文的意思非常好理解吧,如果直接写程序会弄错的话,先想想中文是什么样子的吧~
以下内容先了解下,若暂时看不懂,还是那句话,以后会懂的。
判定句中条件是有返回值的,返回“true”(对的)和“false”(错的),当条件判断后得到的值是 true ,就会运行 then 中的动作;反之,若返回 false ,就运行 else 中的动作。
这种 true 和 false 跟数字一样,是一种数据类型,叫做布尔值,英文叫boolean
“条件判定句”进阶部分完~
第六章:变量
在学习变量之前呢,我们先回顾下前几章中出现的数据类型。
integer(整数),real(实数),boolean(布尔),nothing(无数据类型)
首先,变量这东西呢,就相当于各种称呼、各种名字。
比如“总统”这个词,现在是指胡锦涛,以前呢,是指江泽民,再以前就是指毛泽东了。
“总统”这词在这里就是个变量,在不同的时期指代不同的人,但是“总统”这词本身并不改变,包括这词的类型也不会改变,即“总统”在这里就是“人”这个类型,其它类型的东西不能用“总统”指代,你总不能用国家总统指代苹果吧。
那么,将一个人跟“总统”这个称谓挂钩起来的这个过程呢,就叫赋值,字面意思就是将一个值赋予(赋给)一个词。
比如胡锦涛主席就任的时候,在程序里就是将“胡锦涛”这个人赋给了“总统”这词,于是以后叫“总统”大家都知道是指胡主席了。
再比如,现实生活中呢,是在人出生时给这个人一个名字,为了以后能用这个名字来指代这个人,而不是一直喊“喂”“那个谁”“就是你”……
但其实呢,给名字也可以倒过来看,不是把名字给人,而是将这个人赋给这个名字,这个名字就是变量。任何人都可以叫这个名字,也就是可以将任何人赋值给这个名字,但是为了防止出现喊两个同名的人的名字时两个人都回应的这种情况,每个名字都只能赋一个值。
综上所述,JASS中变量类型和名字确定后就不能随便更换了,但是变量名所指代的内容是可以变的,用赋值这个方法。同时,为了不出现一个变量名指代两个或更多内容的情况,赋值的时候会将之前已经在的值抹掉,然后再将新的值赋上。
比如,胡锦涛就任时,“总统”这词就不再指代江泽民了,已经被胡锦涛替换掉了,这时候江泽民呢,只能用“前总统”来称呼了。但是“前总统”和“总统”是两个不同的变量。
-------------------------------------------------------------&&变量的使用&&-------------------------------------------------------------
那么,变量这个东西在J中到底如何用呢?
先把前面用过的J复制过来:
[jass]function add takes real A, real B returns real
& & return A + B
endfunction[/jass]
注:该函数用的是real,输入时如果输入整数,也会被弄成1.0这种小数形式,如果一定要整数,请使用integer。
大家看到,如果只要用到两个固定的数值,比如A==1,B==2,使用该函数时每次都要写成add( 1, 2 )这个形式,有没有省力点的办法,比如每次使用这个函数的时候能写成add()这样就好了,这时就要使用变量了。
跟之前一样,要先告诉电脑接下来的内容是变量,用”globals”(全局,全球)这个词,当然结尾记得也有个引导词:
[jass]globals
endglobals[/jass]
其中xxxxx的部分就是写变量的地方
现在加入变量:
[jass]globals
& & real A
& & real B
endglobals[/jass]
这样就已经申请好了两个实数变量,一个叫A,另一个是B。
接下来呢,就是如何在函数中使用这两个变量了:
由于是直接拿变量做加法,所以不需要参数了。
[jass] globals
& & real A
& & real B
endglobals
function add takes nothing returns real
& & return A+B
endfunction[/jass]
但是这样写电脑不知道A和B到底是多少,于是就要给A和B赋值,使用“=”
[jass] globals
& & real A=1
& & real B=2
endglobals
function add takes nothing returns real
& & return A+B
endfunction[/jass]
于是,如果使用add()的话,返回值就是3.0了
那么现在更正下上面讲的内容,所有的变量都是有初始值的:
[jass] globals
& & real A
& & real B
endglobals
function add takes nothing returns real
& & return A+B
endfunction[/jass]
对于写在globals里的变量,如果没有赋值,JASS就会使用该变量类型的默认初始值,上面这个函数用的是实数,A和B的初始值就是0.0,整数的话就是0,如果变量是unit(单位)类型的话,初始值就是“没有单位”。
PS:该用参数的时候还是要用参数~这章里所讲的只是为了说明变量
本章节于更正错误:local integer n& &n没有初始值,不是0<font color="#11/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
[jass]globals
& & integer A = 3
& & integer B = 2
endglobals
function JiaFa takes nothing returns integer
& & return A+B
endfunction
function JianFa takes nothing returns integer
& & return A-B
endfunction
function ChengFa takes nothing returns integer
& & return A*B
endfunction[/jass]
这样至少比写六个参数要方便多了吧~
注:JASS读函数是从上到下读的,也就是优先读全局变量,再读JiaFa,再JianFa,最后ChengFa,所以全局变量一定要写在最前面,不然电脑在看到函数中出现变量后就不知道那些变量是什么。
像这种所有的函数都能使用,用”globals”(全局)作引导词的变量,叫全局变量。从名字也能看出是全部函数都能使用的变量。
如果要这些变量只能给一个函数用呢,那就使用“局部变量”,引导词”local”(局部)。
但是局部变量不再在globals里申请了,有权利在globals里申请的都是全局变量。
既然局部变量是只能给一个函数用,那就在函数里申请:
[jass]function add takes nothing returns integer
& & local integer A = 1
& & local integer B = 2
& && &&&return A+B
endfunction[/jass]
注:&return&前多空了4格,是为了看的时候方便区分局部变量和这个函数的功能。
发现局部变量这个写法其实还不如写成参数省力,而且参数也是只能给这个函数使用,为什么要用局部变量?当然在这里是看不出来的,以后会遇到那些只能使用无参数的函数的情况,另外全局变量冲突的情况用局部变量解决最为效率。
这里先简单讲解下全局变量冲突:
首先,先讲下真正的赋值。
函数中是能改变变量的,不管是全局还是局部,方法当然是赋值,不过跟之前的不同。
之前的情况,是新建了一个变量并赋予初始值,也就是在设置变量的时候用“=”赋值;这里所讲的赋值呢,是用新的数据去替换旧的数据,比如要用“胡锦涛”来替换“总统”这个变量中的“江泽民”
使用的引导词为&set&(设置),中文是:设置(变量名)= xxxxxx
[jass]globals
& & integer A = 1
& & integer B = 2
endglobals
function JiaFa takes nothing returns integer
& & set A = 5
& & set B = 4
& & return A+B
endfunction[/jass]
于是,全局变量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看到未知的变量时,会先从最近的地方开始往上找,所以明显局部变量的位置更近嘛(本来就是在函数里……)
[jass]globals
& & integer A = 1
& & integer B = 2
endglobals
function JiaFa takes nothing returns integer
& & local integer A = 5
& & local integer B = 4
& && &&&return A+B
endfunction
function JianFa takes nothing returns integer
& & return A-B
endfunction
function ChengFa takes nothing returns integer
& & return A*B
endfunction[/jass]
于是,JiaFa() == 9,JianFa() == -1,ChengFa() == 2
结束之前再强调一个重点:在一个触发器里,全局变量必须在所有函数之前申明,每个函数的局部变量必须在这个函数的最开端申明。未申明过的变量是没法用的。
第八章:字符串&调用函数
字符串(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。
这不是之前的条件判定嘛!?
不过这里我们要用调用函数来写,调用之前写的“加法”“减法”和“乘法”函数:
[jass]把之前的“JiaFa”“JianFa”“ChengFa”写到这里,不过教程中就省略了
function F takes integer A, integer B returns nothing
& & if A&B then
& && &&&call JianFa(A,B)
& & elseif A&B then
& && &&&call JiaFa(A,B)
& && &&&call ChengFa(A,B)
endfunction[/jass]
注:调用函数时有一点跟全局变量一样,被调用的函数一定要放在上面。比如函数A里 call 函数B,那么写J的时候,B要写在A的上面。
PS:本章其实是为下一章作铺垫的。
第九章:在魔兽中看J的运行(含本地函数&数据类型转换&传参的概念)
从这章开始,我们就要进入魔兽,看看J运行之后的结果到底是怎么样的。
首先,从最简单的程序,显示信息开始,在上一章节,我们已经学过了如何调用一个函数以及对字符串有了一点点概念,现在就要让任何字符串显示给玩家。
我们需要一个能对玩家显示信息的函数,叫做DisplayTextToPlayer,不懂英文的同学看着可能会觉得很辛苦,我们就先把这个函数名拆分一下:
Display(显示)Text(文本)To(给、到)Player(玩家)
我们发现,这个函数名按照大写的字母可以拆成四个单词,于是对应每个单词翻译意思,就能猜出大致意思。
先回答部分同学的第一个问题:为什么会有这个函数?或者说这个函数哪来的?
答:这是JASS中自带的函数,函数的功能是被定义好的,没法随便修改(好吧,其实可以改,但是去改那玩意儿对初学J的同学来说没有什么意义。。),但是可以随便拿来用,使用的方法呢,就是直接使用和调用两种。像这种JASS自带的函数,或者说是暴雪公司已经写好的函数,叫做本地函数(内置函数)。
第二个问题:看不懂这个函数名怎么办?
答:暴雪为了让别人从函数名就能看出这个函数的意思,基本上所有的函数名都是由几个简单的单词组成来说明这个函数的作用。比如DisplayTextToPlayer 就对应“显示信息给玩家”。所以遇到看不懂的,按照首字母大写拆开来一个个弄到翻译器里翻译,不用怕翻译器会翻得很烂,因为拆开成单词后,就是翻单词的意思了,不带任何语法,而翻译器翻烂掉的往往都是带很复杂的语法的句子。
第三个问题:如何知道这些本地函数怎么使用?
答:非常简单。在各种编写J的编辑器中都有函数搜索功能,比如这里就以YDWE为例:
& & & & & & & & & & & & & & & &
----------------------------------------------
& & & & & & & & & & & & & & & &
----------------------------------------------
& & & & & & & & & & & & & & & &
这里的参数呢,分别是“(第几个)玩家”“坐标X”“坐标Y”“字符串(信息)”,其中这个坐标(X,Y)就是指在屏幕显示信息的位置。
我们就开始编写这个显示信息的函数:
[jass]function F takes nothing returns nothing
& & call DisplayTextToPlayer( Player(0), 0, 0, “Jass” )
endfunction[/jass]
于是,这个函数F的作用就成了在屏幕(0,0)这个位置,显示信息“Jass”给玩家一
好奇怪?为什么玩家一是Player(0)而不是Player(1)?这个就跟编程的机制有关了,在编程中,很多时候第一位是0而不是1,1代表第二位,2代表第三位,以此类推。比如“Jass”这个字符串中,第一个字母J实际是在这个字符串的0位。
光是有这个函数F还不够,游戏运行时,电脑还是不知道要什么时候显示这个信息,总不能一直显示着吧。于是……你们懂的……引导~
不过这里的引导不再是什么单词啊符号啊之类的了,这里需要的是“事件”,也就是当遇到某个事件的时候显示信息“Jass”。不过我们还没学触发器的事件之类的写法,所以先用T代替下:
[trigger]DisplayString
& && &&&玩家 - 玩家1(红色) 按下Esc键
& && &&&自定义代码:& &call F()
[/trigger]
注:在自定义代码中完全可以直接写成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的函数:
[jass]function F takes nothing returns nothing
& & local integer A = 1+1
& & call DisplayTextToPlayer( Player(0), 0, 0, I2S(A) )
endfunction[/jass]
数据类型转换这种函数,一般情况下都是直接使用的。基本用不到调用的写法。比如放在DisplayTextToPlayer这个函数里就变成个参数了,参数不能用调用的写法。
像这种把一个函数里的变量或参数传到另一个函数里(不管是直接使用还是调用)的做法就叫传递参数,简称传参。
第十一章:触发器的创建、注册事件、添加条件和动作
一个触发器的“事件”就是运行“动作”的引导,即只有遇到了指定“事件”魔兽才会去运行指定的“动作”;至于“条件”呢,就是在遇到“事件”后,魔兽还要经过“条件”的同意后才会运行“动作”,即“条件”的结果是true才行。
我们先做一个T:
[trigger]Unit
& && &&&玩家 - 玩家1(红色) 选择 一个单位
& && &&&(触发单位) 等于 农民 0001 &预设&
& && &&&游戏 - 对 玩家1(红色) 在屏幕位移(0.00,0.00)处显示文本: 选则了单位
[/trigger]
这个名叫“Unit”的T作用是:当玩家1选择了农民后,显示“选择了单位”。
现在转成J来看下:
[jass] function Trig_UnitConditions takes nothing returns boolean
& & return ((GetTriggerUnit() == gg_unit_hpea_0001))
endfunction
function Trig_UnitActions takes nothing returns nothing
& & call DisplayTextToPlayer( Player(0), 0, 0, &TRIGSTR_008& )
endfunction
//===========================================================================
function InitTrig_Unit takes nothing returns nothing
& & set gg_trg_Unit = CreateTrigger()
& & call TriggerRegisterPlayerSelectionEventBJ( gg_trg_Unit, Player(0), true )
& & call TriggerAddCondition(gg_trg_Unit, Condition(function Trig_UnitConditions))
& & call TriggerAddAction(gg_trg_Unit, function Trig_UnitActions)
endfunction[/jass]
额。。。眼睛开始转圈了。。。。这是神马。。。。
做下深呼吸,镇静一下~
开始分析:
先看最上面那个函数,名字叫“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自建“全局变量”的前缀,这个变量其实就是:
[jass]globals
& & trigger gg_trg_Unit
endglobals[/jass]
现在来看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里加入一个函数参数
注:这个加入的函数必须没有参数!返回值必须是布尔值!因为是条件嘛。
于是回到最前面那个函数:
[jass] function Trig_UnitConditions takes nothing returns boolean
& & return ((GetTriggerUnit() == gg_unit_hpea_0001))
endfunction[/jass]
GetTriggerUnit(),拆开来看:得到 触发 单位
gg_unit_hpea_0001:这个是我在地图上放的农民,WE给他的编号是0001
这个函数就是返回(触发单位==农民0001)
--------------------------------------------&&动作&&--------------------------------------------
最后一个调用的函数不用说肯定是“动作”了:
TriggerAddAction(gg_trg_Unit, function Trig_UnitActions)
触发器是gg_trg_Unit
由于是“动作”所以直接运行函数Trig_UnitActions就行
下面是“动作”部分:
[jass]function Trig_UnitActions takes nothing returns nothing
& & call DisplayTextToPlayer( Player(0), 0, 0, &TRIGSTR_008& )
endfunction[/jass]
这个大家都能看懂吧,看不懂的去回顾下第九章吧~
但是很奇怪,T里面不是写的显示“选择了单位”吗?这里怎么变成&TRIGSTR_008&了?
好吧。。其实我也不知道,可能是WE自动把“选择了单位”这个string记录在“TRIGSTR”里,编号是008
这个不用管的,大家可以直接写成:
[jass]function Trig_UnitActions takes nothing returns nothing
& & call DisplayTextToPlayer( Player(0), 0, 0, &选择了单位& )
endfunction[/jass]
-----------------------------------------------------&&总结&&-----------------------------------------------------
一个完整的最基础的触发器需要:
1.& & 初始化触发器函数(此函数无需参数和返回值,在这个函数里需要新建触发器,并为这个新建的触发器注册事件,然后添加条件和动作)
2.& & 条件函数(无参数且返回值必须为布尔值)
3.& & 动作函数(无参数且无返回值,所有要做的事全在这个函数内完成,即使用调用函数的方式完成动作)
要注意这三个函数在J中的顺序:
由于初始化触发器中调用了“条件”和“动作”,所以,初始化触发器这个函数必须放在“条件函数”和“动作函数”的下面。
下一章节教大家如何自己写触发器。
第十二章:写自己的触发器
PS:以下写J的顺序是我自己写J的顺序,同学们不一定要仿照。
先看下我们要写个什么样的J:
触发器名字:Unit
事件:玩家1输入a
条件:农民0001是存活的(该农民是地图上事先放置的农民,0001是WE给这个农民的编号,大家放置的时候也许编号会不同)
动作:创建一个玩家1的步兵在地图中心。
好像没什么思路额。。。但是不管写什么触发都需要很关键的第一步,那就是初始化。
注:一个初始化的函数的名字必须带上“InitTrig_”这个前缀。
[jass]function InitTrig_Unit takes nothing returns nothing
endfunction[/jass]
注:“InitTrig_”后面跟的名字必须跟该触发器的名字相同,否则无法初始化,也就是无法使用。意思就是:
如果你在这里写的触发器名字是XXX:
& & & & & & & & & & & & & & & &
那么就必须写成:InitTrig_XXX
第二步:创建触发器
我们不再使用上一章中的全局变量触发器,改用局部变量;至于区别。。这我还真不知道,至少程序长了以后不用每次都拉到顶端写到globals里再拉回来。
[jass]function InitTrig_Unit takes nothing returns nothing
& & local trigger trig = CreateTrigger()
& & set trig = null
endfunction[/jass]
注:set trig = null 这一步就是将这个局部变量触发器清空,并不是销毁这个触发器,而是将这个变量清空,也是排泄的一种。(至于排泄的概念……已经有很多相关教程了,搜索下就有了;总之就是不排泄地图就会越玩越卡)
PS:null 就是“没有”的意思
第三步:注册事件
我们的事件是玩家1输入a,于是在T里找到这个事件,然后转成J之后自己照着样子打一遍,不要复制黏贴,尽量看懂那个函数名:
[jass]function InitTrig_Unit takes nothing returns nothing
& & local trigger trig = CreateTrigger()
& && &&&call TriggerRegisterPlayerChatEvent( trig, Player(0), &a&, true )
& & set trig = null
endfunction[/jass]
第四步:加入条件
[jass]function InitTrig_Unit takes nothing returns nothing
& & local trigger trig = CreateTrigger()
& && &&&call TriggerRegisterPlayerChatEvent( trig, Player(0), &a&, true )
& && &&&call TriggerAddCondition( trig, Condition( …… ))
& & set trig = null
endfunction[/jass]
由于我们还没写条件函数,所以Condition()的括号中先空着
第六步:加入动作
[jass]function InitTrig_Unit takes nothing returns nothing
& & local trigger trig = CreateTrigger()
& && &&&call TriggerRegisterPlayerChatEvent( trig, Player(0), &a&, true )
& && &&&call TriggerAddCondition( trig, Condition( …… ))
& && &&&call TriggerAddAction( trig, …… )
& & set trig = null
endfunction[/jass]
同样的,由于还没写动作函数,先空着
第七步:写条件函数
我们的条件是:农民0001是存活的
在T中找到这个条件,由于是条件,所以肯定在“布尔值”里
转成J看懂后输入到我们的程序里:
PS:因为函数名只要对应就可以使用和调用,所以不用再加什么“Trig_”这种非常麻烦而且难看的前缀了,只要写成自己能看懂的名字就行了。
[jass]function Unit_Condition takes nothing returns boolean
& & return IsUnitAliveBJ( gg_unit_hpea_0001 )
endfunction
function InitTrig_Unit takes nothing returns nothing
& & local trigger trig = CreateTrigger()
& && &&&call TriggerRegisterPlayerChatEvent( trig, Player(0), &a&, true )
& && &&&call TriggerAddCondition( trig, Condition( function Unit_Condition ))
& && &&&call TriggerAddAction( trig, …… )
& & set trig = null
endfunction[/jass]
注: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中找到“单位-创建单位(面向角度)”这个动作(我用的是(可用地图区域的中心点),其它没变):
[trigger]未命名触发器 005
& && &&&单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((可用地图区域) 的中心点) ,面向角度为 默认建筑朝向 度
[/trigger]
转成J后,发现是这么个东西:
[jass]CreateNUnitsAtLoc( 1, 'hfoo', Player(0), GetRectCenter(GetPlayableMapRect()), bj_UNIT_FACING )[/jass]
这个函数会创建一个“点”(数据类型为point)在地图中心,然后创建1个步兵(‘hfoo’)到这个“点”,并且让这个步兵面朝(bj_UNIT_FACING)(与T对应后即可知道是默认建筑朝向)
但是创建完步兵这个“点”就没用了,需要删除(也就是排泄)。
所以这里推荐一个J的本地函数:
[jass] native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit [/jass]
参数分别是:玩家,单位的ID,坐标X,坐标Y,面朝方向
返回值是单位
由于地图中心的坐标是(0,0),且坐标是实数,无需排泄
所以动作函数可以写成:
[jass]function Unit_Condition takes nothing returns boolean
& & return IsUnitAliveBJ( gg_unit_hpea_0001 )
endfunction
function Unit_Action takes nothing returns nothing
& & call CreateUnit( Player(0), 'hfoo', 0, 0, bj_UNIT_FACING )
endfunction
function InitTrig_Unit takes nothing returns nothing
& & local trigger trig = CreateTrigger()
& && &&&call TriggerRegisterPlayerChatEvent( trig, Player(0), &a&, true )
& && &&&call TriggerAddCondition( trig, Condition( function Unit_Condition ))
& && &&&call TriggerAddAction( trig, function Unit_Action )
& & set trig = null
endfunction[/jass]
至此,一个完整的触发就写完了。
有的同学问:这个步兵的代码(‘hfoo’)只能写个T的动作再转换来查看吗?
答:可以直接在物体编辑器里查看。到“单位”里找到“步兵”,然后按Ctrl+D即可。
& & & & & & & & & & & & & & & &
-------------------------------&&Ctrl+D&&--------------------------------
& & & & & & & & & & & & & & & &
要还原的话再按下~
用纯J(不要做完T转换哦~,不过允许不知道的函数名用T转换看)写一个完整的触发:
在地图上放一个农民一个步兵
事件:玩家1选择一个单位;
条件:被选择的是步兵
动作:如果农民被选择,显示信息:农民被选择
& && && &&&如果步兵被选择,显示信息:步兵被选择
Powered by
X3.2 Designed by

我要回帖

更多关于 魔兽争霸3语言问题 的文章

 

随机推荐