找回密码
 点一下
查看: 2879|回复: 8

我也写一个jass教程(草稿版)

[复制链接]
发表于 2006-12-24 23:44:53 | 显示全部楼层 |阅读模式
Jass教程:
为什么要学JASS,JASS有什么用?
JASS是魔兽争霸中的脚本语言,在WE中写的触发器实际上都是转换成jass在WAR3中起作用的。
所以我们可以用JASS做一些触发器做不到的事情,而且还能提高完成的效率。另外一些好的地图都加了密
只要我们能看懂JASS就能学到地图中一些好的方法。

基本书写规范:
区分大小写:a和A是不同的。
jass中每个语句占一行,并且语句不需要结束符。(如果你留意的话会发现JASS的每个语句都是关键字开头的)
注释:jass中只有一种注释,单行注释,使用//开头,即//到行末的内容。
注释中的内容绝对不会对代码有任何影响,相反它能提示你一些信息。应当养成写注释的良好习惯。
另外我们可以临时把出错的代码注释起来,再进行调试。

一,基本变量类型:
boolean是非型(真假型):可以存放true和false两个状态。
integer 整型:可以存放整数。
real 实型:可以存放浮点数,也就是实数。
string字符串:可以存放字符串。支持转义字符,当你在JASS中用字符串表示path(路径)时,别忘了把\改为\\
常用的转义字符有:\n换行。
handle句柄:句柄这个概念可能还不太懂是吗?这里粗略的说一下吧。
在魔兽争霸中很多要描述的物体(对象)都不是简单的变量类型。如unit(单位),ability(技能)
item(物品),当一个函数需要以这些对象为参数的时候我们必须填写一个标识符在参数的地方。
war3中的这种标识符就是handle类展开的变量。句柄就是这些对象的一个唯一的标识。
war3通过一个句柄就可以知道这个句柄代表的是什么?是item还是itemtype(物品类型),是unit还是button(对话框)等等。
并且能够通过句柄找到该物体(对象)在内存中的地址,对物体(对象)的数据进行访问。
这一切是通过handle系统(实际上负责维护一个handle到指针的映射)完成的(这里不属于我们的讨论范畴)
所以,句柄这个东西说白了就是一个标识,通常句柄都是在整型基础上定义的,也就是说句柄几乎和整型通用。
但要注意并不是每一个整数都对应了一个句柄,句柄这个东西是在句柄标识的东西存在的情况下才起作用的。如果我们把一个unit
删除掉,那么这个unit对应的句柄也就失效了。而且我们删除该unit时恰好是用到unit的句柄来表示该unit。是不是很有趣!

有很多变量其实都是句柄变量(该变量仅仅存储对象的句柄值):如
unit ...
code类型:code实际是function指针。当我们需要一个函数的指针的时候就可以用code变量来代替。
注意:
赋值,code c=function funcname
code变量的实质:jass脚本函数的指针。你可以让它指向任意一个takes nothing returns nothing的函数,并且在需要code的地方用code变量代替。
如注册触发器,过滤器等等。
二,基本变量的操作:

赋值操作:所有的变量都支持赋值操作,使用set 开头,如set a=10当然等号两端得保持相同的变量类型。

boolean型:可以进行and与运算,or或运算,not非运算。
所谓与运算就是只要两个boolean型变量中有一个为假的时候结果就为假。
或运算恰好相反,只要有一个为真那么结果就为真。非运算就是求一个boolean变量的反面。
integer型:+ - * /四则运算。其他的高级计算不直接支持,但是你可以自己写代码来进行高级计算。
real型:+ - * /四则运算。
string型:+运算,这个运算把两个string变量连接成一个string变量。
handle型:没有相关运算。
常用的库函数:Pow(real x,real power)乘法运算。求x的power次方的值。注意x和power都是real型。
GetRandomInt(integer low,integer high)得到low到high之间的随机数,能取到边界。
GetRandomReal(real low,real high)用法同上面的函数,只是参数类型和返回类型不同而已。
基本三角函数支持。Sin,Cos,Tan等等。
I2S:把integer转换成string,比如说你想在屏幕上显示一个integer变量的值就需要这个函数的帮助。
同样有S2I,R2I,R2S,I2R,S2R这些函数完成integer,real,string之间的相互转化。它们是非常有用的。
另外对于string还有专门的函数:
StringLength:求string的字节长度。注意汉字在war3中占3字节,而字母占1位。
SubString(string s,integer m,integer n)分割string,留下m节点到n节点间的string。注意节点是指两个
字节(不是字符)之间的点,而且string两端的端点属于节点。假如我要取得一个string的第一个汉字,
我可以这样写,SubString(s,0,3)懂了吗。
另外还有大小写转换的函数,这里就不一一列举了,自己去看commom.j文件。

一个最常用的函数Player(integer i)通过玩家的序列,得到玩家。

三,简单的接口函数:
jass中使用war3提供的接口函数对war3进行一些操作。
调用一个函数(不需要返回值时),需要在行首加上call,如
call DisplayTextToPlayer(Player(0),0,0,"简单的调用")
上面的语句中DisplayTextToPlayer就是一个接口函数,它完成这样一个功能,即在玩家Player(0)的屏幕上0,0位置
显示字符串 “简单的调用”,当我们理解到这个函数的功能时,我们能够举一反三地写出更多的调用,在不同的玩家
屏幕上,不同的位置,显示不同的字符串。是不是很简单。
像这样的函数直接由函数名几乎就可以猜出功能了,再由参数类型和参数名,也能大概猜到调用方法。
在commom.j函数中几乎所有的接口函数都是这样。而且war3针对不同的功能把借口函数分类放到一起,用户
可以方便查找和掌握。只要你用jassshoppro打开common.j我就敢保证你能至少能了解到10个以上非常有用的函数。
对于那些你还不是很了解或是不认识的英文,你可以不必去了解。毕竟we也没用全接口函数。

一些返回句柄的函数通常可以用call调用。不必非拿一个句柄变量去装。如call CreateUnit(...)

四,选择分支和循环:
jass中判断分支语句是if-then-endif结构的,如果中间要进行多次判断,可以添加elseif-then在if判断体中。
另外也支持else,注意else不用写条件。
例子:
if name=="杀残枫" then
   set name="天使:"+name
elseif name=="hackwaly" then
   set name="垃圾:"+name
else
   set name=name+"是谁?"
endif
能看懂吗?一个判断中elseif-then可以有多条。但是if-endif只能有一个。if和endif必须成对出现。else可有可无,但必须是最后一个判断。

jass中的循环方法是最简单最“无招胜有招”的。
loop-endloop结构就是一个循环。代码执行到endloop时自动跳转到与该endloop成对的loop处执行。
例如:
loop
call DisplayTextToPlayer(Player(0),0,0,"循环啊循环")
endloop
事实上上面的循环是死循环,因为循环不是有限次的,而且还不能跳出循环。
exitwhen语句就是专门用在loop-endloop中间,跳出循环用的,一看就知道,是当条件满足时跳出循环。
这比C语言的条件就满足执行循环灵活多了。

set I=0
loop
exitwhen I>11
call DisplayTextToPlayer(Player(I),0,0,"要让每个玩家都看见这句话")
set I=I+1
endloop
当然你要这样写也是对的
set I=0
loop
if I==12 then
   exitwhen true
endif
call DisplayTextToPlayer(Player(I),0,0,"要让每个玩家都看见这句话")
set I=I+1
endloop
下面这种exitwhen的用法也并不是什么时候都像现在这样没什么额外的好处。有些时候正需要这样写。
其实要在每个玩家屏幕上显示消息,可以这样写。
call DisplayTextToPlayer(GetLocalPlayer(),0,0,"每个运行到这条语句的终端都会显示本消息了")
GetLocalPlayer()从字面上理解是取得本地玩家。为什么一条语句就能在完成呢?
这里只是提一下,后面会有相关的解释。
五,函数
在jass代码中,除去全局声明就是函数声明了。函数是现代所有的程序语言中的基本部分。
如果前面提到的那么多次的函数概念你还没有一点头绪的话,可以看一下我对函数的理解。
  一,没有函数可不可以?当然可以,没有函数的话直接写语句完成所有功能。
  二,函数的作用?函数其实就是为了方便。我们可以把完成一个特定功能的代码段写成一个函数,
当我们需要完成该功能时,只需要调用一下函数就可以了,不需要重新去写已经写过的代码。
  三,函数的参数?函数完成功能时需要知道一些参数,以便来完成功能,比如说一个max(m,n)函数,
它是求m,n中间较大的那个数,如果你调用max函数而不传给它m和n的值,max函数就无法完成它的功能了。
很简单的道理。
  四,函数的返回值?调用函数是用来完成功能的,我们可能需要知道函数完成的怎么样了,
或者说是完成的结果,这些都是函数的返回值来完成。如我们调用max(3,5)就会得到5,5就是max的返回值。
我们可以把max(3,5)当作5来用,当参数不确定时,同样max(m,n)也可以当成一个变量来用。
函数的定义方法:
  前面我们讲了很多调用函数的例子,知道了函数怎么的神奇,可是神奇的函数是怎么来的呢?我们能不能创造自己的函数呢?
答案是肯定的。
看max函数的定义吧
function max takes integer m,integer n returns integer
  if m>n then
     return m
  else
     return n
  endif
endfunction
能看懂吗?关键字function开头,然后是函数名,接着takes参数类型和参数名,这个参数名我们通常叫做
形参,是我们在函数的内部用到的。我们的函数的调用不需要满足这个名,只需要满足变量类型相同即可。
形参在函数内部可以当成变量来用,函数结束后,形参也会消失(被销毁)。
所有的参数写完了后是returns(注意有个s啊)返回类型。说明函数可以被当成什么类型的变量使用。
在整个函数定义结束后是endfunction,同if,loop等一样,function和endfunction也成对。指示中间的代码是函数定义。
我们可以看到函数内部有一个return语句,大家可能还不认识它。
return语句负责返回函数值,返回的值就是return后面的表达式值(注意,jass中表达式和语句分得很清楚比c语言好多了)。
表达式可以是变量,也可以是能返回值的函数,也可以是常数,等等,只要是能计算出值的都可以。
当函数过程运行到return语句时就返回return后面的表达式的值,并且退出函数。返回到调用函数的地方去执行代码了。

set a=max(3,5+4)
上面的语句是个赋值语句,它的需求值是max(3,5+4),这个表达式是个函数调用,它又需求参数的值,所以正确的执行顺序是
先计算5+4=9,然后计算max(3,9)这时就跳转到max函数内部去执行去了,此时形参m=3,n=9,m<n,所以return n,运行到这一句,
函数过程就宣告结束了,然后把n的结果9返回给调用max(3,9)的语句,也就是赋值语句了,整个顺序基本上就是这样。
现在能理解到执行的返回和值的返回了吧。
当然函数就是这么简单,那那些厉害的函数是怎么写出来的?
在回答问题之前,我得再讲一下,函数的局部变量。把函数部分讲完对不对。
在函数内部声明变量,必须在函数过程开始之前,也就是写在函数定义的最前面才行了。同样是max函数为例
这次我们希望函数能够顺序运行到endfunction前才返回。也就是说,这次的max函数只有一个return.
function max takes integer m,integer n returns integer
  local integer maxer=m
  if m<n then
     set maxer=n
  endif
  return maxer
endfunction
想起来这个函数没有前面那个函数快,事实上很多时候我们需要这样写,比如说,max函数的参数不是两个,而是10个或者更多。
你不觉的一遍一遍return很麻烦吗。而且打乱了函数的结构。
可以看出local关键字的用法了吗?和全局变量的声明一样,你可以把变量初始化,也可以等到你要用变量的时候才对它赋值。
下面看一个调用接口函数完成公告消息功能的函数
function gonggao takes string message returns nothing
  local integer I=0
  loop
      exitwhen I>11
      call DisplayTextToPlayer(Player(I),0,0,message)
      set I=I+1
  endloop
endfunction
看见returns后面跟的nothing了吗,这表示函数不返回值,也就是说你只能通过call来调用它。
在函数体中同样可以使用return来退出函数,不同的是你不需要写return nothing而是直接写return就可以了
怎么样?啊!不怎么样啊。如果你向没有学过JASS的人或者是还没学JASS时候的你介绍说这个函数能够完成让所有玩家不得不看消息。
他肯定大吃一惊,佩服的说,这么少的代码啊。
六,数组
要想写出好的函数不是光靠循环和判断就能写出来的,还需要数组。什么?你被这名字吓着了!
别慌,数组其实就是一串变量,记住它是变量啊。
首先我们见识一下数组的好处,即为什么要有数组这个东西。(几乎所有的程序语言中都支持数组)
例如,我们写一个函数实现对每个玩家显示不同的消息。
function Demo takes nothing returns nothing
    local string s1="玩家1的消息"
    local string s2="玩家2的消息"
    local string s3="玩家3的消息"
    call DisplayTextToPlayer(Player(0),0,0,s1)
    call DisplayTextToPlayer(Player(1),0,0,s2)
    call DisplayTextToPlayer(Player(2),0,0,s3)
endfunction
幸好war3中只有12个玩家要是有30个玩家就相当麻烦了。这里仅仅写出3个玩家就不想写了
如果我们还想让玩家2收到玩家1和玩家3的消息,玩家3能收到玩家2和玩家4的消息...不敢想像。
我们看用了数组的版本
function Demo takes nothing returns nothing
    local string array s
    local integer I
    set s[1]=""
    set s[2]=""
    set s[3]=""
    ...
    loop
       exitwhen I>11
       if I>0 then
       call DisplayTextToPlayer(Player(I),0,0,s[I-1])
       endif
       call DisplayTextToPlayer(Player(I),0,0,s[I])
       if I<11 then
       call DisplayTextToPlayer(Player(I),0,0,s[I+1])
       endif
       set I=I+1
    endloop
endfunction
看到了吗,上面的函数就能完成艰巨的任务,让每个玩家收到他邻位玩家的消息。
如果你能够读懂的话,那么说明你有相当高的自学天赋了。
array关键字用在变量名和变量类型之间,声明一个数组变量。简称数组,有称矩阵变量的。
我们通过数组变量名加上中括号[](又称下标号),在中括号里面填上索引就可以访问到相对应的单元变量。
如s[1],s[2]等等。最方便的是索引你可以使用一个整型表达式来代替。这样方便我们用循环来访问数组中每一个变量。
在JASS中对数组很宽松,不用指定数组的大小,但默认情况下,数组索引为8192。
数组是很重要的内容,建议你自己下来多实际试一下。熟练一下数组对于编程是很有帮助的。
七,触发器
好吧,我们开始接触jass中的触发器吧。
早在开篇我就提过,war3中的对象都是用handle标识的。触发器也不例外。
一个触发器变量仅仅存放了标识触发器的handle,而不是触发器的真正数据。
理解这个对于我们的jass相当重要。
回想WE中的触发器都由什么构成?
对了,就是事件,环境,动作三部分。
在WE中,把三部分放到一起,方便我们设计触发器。
而在jass中,三部分不必在同一地方,非常的灵活。
首先,得创建一个触发器对象,要注意的是创建触发器对象并不是声明一个触发器变量。
那该死的trigger变量仅仅是一个句柄变量。创建触发器对象的函数war3已经封装好了,是CreateTrigger
直接用就行了,不需要任何参数,它返回一个trigger句柄,你可以用一个trigger变量来存放,也可以不存放。
不管你存不存放,它都已经在内存中创建好了。
当然,没有任何的参数,你一定会奇怪,触发器的事件,环境,动作到哪里去了。
不要急,先前就说过,触发器的设计在jass中是可以分开的。
我们先创建一个空的触发器;以后再添加它的事件,动作也是可以的。
在jass中给触发器添加事件是通过调用相应的接口函数(API)来实现的,而且war3把它叫做注册事件函数。
这种函数不只一个,有很多,不同类型的事件有不同的注册函数。这让jass写触发器的人很麻烦。
我介绍一种简单的方法,可以避开自己去找哪个事件对应哪个函数:
用WE中的触发编辑器,把你要的事件添加好之后,再把触发器转化为文本。就可以清楚的看到事件是该怎么注册的。
到jassshoppro中查看这些注册函数,你会发现它们有个共同点,第一个参数都是trigger变量。
即触发器句柄。这表示把事件注册到哪个触发器上面。我们在为一个触发器注册事件时,一定要保证这个
参数是我们要设计的那个触发器的句柄,不然就会注册错。
好吧,事件注册完了,轮到环境和动作了。
为什么把他们并到一块呢?
因为它们有很多共同点,首先他们的注册函数只有一个。你此时肯定会想到WE中的条件和动作是那么的多。
其次,他们注册的东西不是一个对象了,而是一个函数。这下能想通刚才的困惑了吧?
环境注册函数把一个条件函数注册到触发器。动作注册函数把动作函数注册到触发器。
这里以动作为例就不讲条件了。条件函数的要求是返回值类型为boolean
注册分先后,触发器触发时,动作函数的运行先后也确定了,和你的注册顺序是一样的。
在jass下注册动作一般只注册一个动作函数。在那个动作函数里把所有的动作和判断都完成,我们还推荐你不要注册条件,
而是把条件判断写到动作函数里。
当然仅仅是对于静态的触发器而言,如果你的触发器需要不时的增加和减少条件动作,那么你不得不多写几个函数了。
这种动态的触发器只是听说过和设想过,往往写成jass的只有很简单的触发了。

下面介绍一下jass下触发器的原理。
当一个事件注册到触发器后,每当发生该事件war3都会创建一个线程运行该触发器的条件函数,一旦条件函数返回假,线程就停止运行。
如果条件函数返回真,则运行下一个条件函数。直到所有的条件函数返回值都是真时运行动作函数。
这时触发器的触发计数器加一。接着是其他的动作函数。记住,运行下一个动作函数是要等待上一个返回的。

下面讲触发器中最重要的部分:触发资源。
为触发器注册的事件往往不是详细的事件,而是一个笼统的事件。比如说“一个单位进入某个区域”
虽然区域指定了,但是单位没有指定,任何单位进入该区域我们的事件都会触发。我们怎么能知道进入区域的那个单位
是不是我们要等它进入的那个单位呢?
war3在为触发器创建线程时,同时也创建了线程的全局资源(你可以理解为全局变量),之所以叫全局资源而不叫全局变量,
是因为我们不能像访问变量一样访问它们,它们被要求是只读的。如果弄成全局变量我们很有可能错误的对它进行写操作。
所以war3把它们设计成了只能用函数读取值的资源。
下面是几个常见的触发器资源读取API(接口)
GetTriggerPlayer()返回触发事件的玩家。
GetTriggingTrigger()返回当前触发器的句柄。
还有更多,它们和WE中的要求变量时可选的“功能”有一样的效果。当然你只能在相应的触发器下面使用相应的资源读取API,
不同事件的触发器总能有一些相同命名的资源。
你如果要了解更多资源,请使用jassShopPro
至此war3 jass最基本的东西算是讲完了

八,过滤器

对不起,这个我也不太会,但是它是用Jass写(群杀)(光环)技能的重要工具。如果有人会这个,麻烦帖一下。

高级运用:
一,丢弃垃圾全局变量:我们打开一个用WE做的图可以看见全局变量有很多。用JassShopPro检查一下。
发现这些变量我们根本就没调用过,说明是垃圾变量,这些变量大都是一些句柄变量,用来保存我们在地图上放置的单位。
方便触发编辑器中调用。很多没用到的变量占用了资源,学过了JASS应该知道怎么办吧,
记住:句柄变量不是对象,一个对象没有句柄变量也能存在。当一个对象创建完毕,不需要句柄变量时就可以
对句柄变量赋值另一个句柄。典型的用法是在触发器的设计上,当我们设计触发器时,必须有设计中的触发器对象的句柄,
设计完成后,就不需要该句柄了,触发器能自己去运行。

local trigger t
set t=CreateTrigger()
call TriggerRegisterEvent()
call TriggetAddAction()
set t=CreateTrigger()
call TriggerRegisterEvent()
call TriggetAddAction()
...
能理解吗?这一次不能理解我也不会解释了。
二,动作函数
如果叫你用WE的触发器编辑器和jass来完成触发器的动作部分,你会选择JASS吗?
在JASS里面,没有别扭的循环数A,循环数B。在JASS里面判断条件比在触发编辑器中找条件要方便得多,如果你用过。
另外你可以在动作函数里面写一些资源,如数组的string,这是触发编辑器绝不可能办到的。
三,读图线程和触发线程
main函数开始的线程是读图线程,读图的时候运行。
触发动作函数开始的线程是触发线程。
读图线程负责初始化全局变量,注册触发器,创建对象。在读图线程中的交互式操作无效。如显示消息,对话框等等。
通常至少有一个触发器在读图线程中被注册。一般情况下触发器都由读图线程注册。如果你懂你可以在触发线程中注册另一个触发器
或修改本身。
四,关于GetLocalPlayer()
这个函数很简单。返回一个玩家。
你需要理解jass脚本在war3中的执行方式。
多终端同步执行,也就是说jass脚本不是只在建主机(OP)的那个机器上运行,而是所有的终端都在运行,终端之间的全局变量通常是完全一致的。
这个函数的用法就是自己去理解吧,真难表述!后悔没学好语文。
通常   if GetTriggerPlayer()==GetLocalPlayer() then
           call action()
       endif
这样就能节约资源,要掉线也只是他一个掉,不会出现主机掉的情况。

怎么编写JASS代码,怎么把他用到地图中去?

下面是一些jass的研究。

war3中颜色代码。
代码执行次数的研究zyl910

return bug
游戏缓存的研究

我d发现
RemoveUnit(null)这个函数调用不会被认为是错误,运行时也不会导致程序退出。但它让玩家不能使用UI键,而只能使用鼠标。
UnitRemoveAbility可以删除单位的基本技能如'Aatk'攻击,'Amov'移动,但不能添加这些技能,其他的技能不存在此问题。
发表于 2006-12-25 00:06:42 | 显示全部楼层
这年头怎么都发水区来了...
回复

使用道具 举报

发表于 2006-12-25 00:33:30 | 显示全部楼层
5555
好像在XX地方看到过...
回复

使用道具 举报

发表于 2006-12-25 00:52:01 | 显示全部楼层
RemoveUnit(null)这个函数调用不会被认为是错误,运行时也不会导致程序退出。但它让玩家不能使用UI键,而只能使用鼠标。
这个怎么说?
回复

使用道具 举报

发表于 2006-12-25 18:10:45 | 显示全部楼层
好吧。正式版再表扬一下好了。
回复

使用道具 举报

发表于 2006-12-25 21:29:51 | 显示全部楼层
水区果然素个好 地方 ....
回复

使用道具 举报

发表于 2006-12-25 21:34:22 | 显示全部楼层
现在的情况就是水区外的地方无人问津
回复

使用道具 举报

发表于 2006-12-25 21:45:57 | 显示全部楼层
也不素那样 偶有问题还素会去对口区发问滴...
回复

使用道具 举报

发表于 2008-1-12 16:28:11 | 显示全部楼层
我看见everyguo的那J教程`哪里下载 啊~?
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-11-23 18:27 , Processed in 0.032472 second(s), 18 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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