请选择 进入手机版 | 继续访问电脑版

 找回密码
 点一下
查看: 16278|回复: 12

jass使用教程--by eGust

  [复制链接]
发表于 2006-3-28 11:26:31 | 显示全部楼层 |阅读模式
《jass使用教程 基础篇》

=================================================

前言 jass语言概述
0. 教程对象
本教程适用于没有接触过计算机语言的人。由于jass非常简单,所以对学过一种中高级语言的人完全没有难度,而本教程对j语言的各部分都介绍的比较详细,所以没有必要学。如果只会用行号的basic(不包括vb、vbscript、ASP)的话,那还是看看这篇教程吧(不过可以略过很多部分)。
如果是完全没有接触过编程的话,建议第一遍通读,大体掌握jass语言的结构,然后再由基础到高级一步步的提高。
基础篇是对于语言的基本结构的介绍,首先要掌握好里面的关键部分,打好这个基础后,可以跟随高级篇里面的范例一步步的学jass的应用。

1. jass的特点
jass语言是一个语法非常简单的语言,语法结构上比较接近basic,同时也引用了许多c的东西。

2. 如何学习和使用jass
在trigger editor窗口中,我们可以很容易的把一个t转成j,方法是Edit(alt+e)->Conver to Custom Text(x),然后我们就可以在已有的j的基础上进行编辑。
由于we里的文本编辑器功能不够丰富,另外,we的容错机制比较差(j的错误太多容易导致直接关闭),所以这里推荐两个专门用于jass编辑的工具:jass editor(汉化版)和jass workshop(by soarchin)。两个工具各有各的特点,后面会在涉及到的部分提到。
另外还需要得到cj和bj两个接口函数库,这个在je和jw中均有提供

3. jass和trigger的关系
在地图编辑过程中,绝大多数用j能够完成的东西也能够用t来完成。事实上,所有的t最后都会转化成j,trigger只是面向we的独特体系。具体的说,war3提供的API有common.j和blizzad.j两个文件,其中com.j是纯粹的接口,而blz.j是对cj里面接口函数的封装,主要面向trigger editor,t生成的j代码中,绝大多数函数都是bj里的。

4. 为什么要学习j
a.用heavylock等工具压缩后,trigger和j面向we的部分被删除了,只留下script.j里面干净的j部分,为了研究别人的map,就要能读懂别人的代码;
b.t虽然能完成几乎所有的功能,但是对于内存释放和另外一些功能实现的能力太差;
c.虽然t看似是一个语法结构完整的可读性比较高的语句,但是因为常常语句太长导致可读性大大下降,而j可以更方便的体现逻辑性,事实上j的可读性更强;
d.用j可以写出比t效率高很多的代码,当然这是在对计算机工作原理比较了解的前提下。
 楼主| 发表于 2006-3-28 11:27:03 | 显示全部楼层
=================================================

                              第一章 jass基础
0. 本章概述
    本章介绍jass语言的最基本内容,包括注释、基本数据类型、变量、数组、基本运算符、运算的优先级、常量、基本运算法则
    首先在这里说明一点,jass是区分大小写的,也就是说,jass不会认为“a”和“A”是同一种东西

-----------------------------------------

1. 注释(comment)
    注释符号://
    不论是we的语法检查器还是war3,“//”之后该行的内容将被忽略

-----------------------------------------

2. 数据类型:
    jass虽然不大,但是提供的功能足够丰富,共提供了6种基本数据类型:
1) integer  : 32位有符号整数型(4byte)  取值范围:[-2147483648, 2147483647] (2^31+符号位)
2) real     : 实数型(单精度浮点型,4byte)  取值范围:[1.5*10^(-45), 3.4*10^38]
3) boolean  : 布尔型(1byte) 记录 true(真)或 false(假)
4) string   : 字符串型(不定长) 用来记录字符
5) handle   : 数据指针型 是jass语法的一个基础类型,相当于passcal中的pointer类型,由它可以派生出其他数据类型的指针。用来指向内存中的一个数据地址
6) code     : 函数指针型 用于指向内存中的函数地址

*  其他数据类型: 1.16有81种,1.17又新增5种,都是由继承handle而来,在common.j最开始的部分,声明形式如下:

type ******   extends   handle  //此处即使不是handle,也是其他由handle继承而来的类型


-----------------------------------------

3. 变量:
    知道了jass的基础数据类型,下面我们要了解如何使用这些数据。首先就是要对数据进行存取,基本的办法就是使用变量。什么是变量呢?就是war3在运行的时候在内存中申请一块数据空间,用来存取数据的。
1) 变量的类型:分全局变量和局域变量两种,在j中最常用的是局域变量
2) 变量的命名:
    首先要给变量一个独特名字,作为这个变量的标志,以便在以后的使用过程中访问。
    变量名称必须以英文字母开头,变量名中可以包括:大小写英文字母(A~Z, a~z)、数字(0~9)、下划线(_),其它符号会被认为非法(不要用中文命名变量名)。
    we会自动给全局变量加上“udg_”前缀,在jass中使用全局变量的时候直接手动输入“udg_”前缀即可访问全局变量。
    变量不得以关键字或已注册的名称来命名,例如:
  local location local //与关键字相同
  local item item //与已注册的变量类型重复
  local force GetPlayersAll=GetPlayersAll() //与已注册的函数重名

3) 变量的声明:在使用一个变量前,首先要告诉war3:“我要一块内存来存放数据啦!”这个时候就是对变量的声明。
    在we中对全局变量的声明,是在edit(编辑)菜单中点击Variablies(变量),或者直接点快捷菜单按钮中的“X”按钮,添加、删除、定义数据类型的,而实际在map文件中,这部分变量会放在.j文件最开始的部分声明,形式如下:
globals
   //说明:<>中的是可有的参数,()中的是必须的参数,之后还要用到这种形式
   (type) (变量名) <= 初始值>
   ...
endglobals

    对局域变量的声明,必须放在一段function(函数或过程)的最开始的部分,用location关键字开头来声明,全部局域变量声明结束后,才可进行函数的实体部分(这点和passcal比较类似):
function ***** takes **** returns ****
   location (type) (变量名) <= 初始值>
   ...
endfunction

4) 变量的优先级:
    我们知道,可以在声明局域变量的时候增加udg_前缀,以便在t中方便访问。那么如果与全局变量重名了会怎么样(可以与全局变量数据类型不同)?这个时候,在这个function中,在使用这个变量名的时候会代表location变量,也就是说,局域变量的优先级更高。所以不要使局域变量的变量名称与全局变量重名。

5) 变量的命名原则:
    a、b、c、d都可以用来命名变量,但是当变量比较多的时候,怎样来在后面方便知道变量储存的东西呢?其实这个完全是个人习惯的问题,不过有一定的通用规则,可以帮助你方便的识别自己的变量的含义。
    一般来说,首先要使变量名称有意义,例如,假如我们用一个integer变量来储存hero的strength(力量,强壮,不知道中文版怎么翻译的)值,我们可以把这个变量命名为strength。
    第二步,通过大小写、前缀、后缀的差别来命名函数。一般用前缀来标示变量的类型,个人的习惯不同,命名的方式不同。例如刚才那个变量,按照通用的习惯,可以命名为intStrength,“int”前缀表示是整数型,大写的S用来区分词义。我个人的习惯是,integer型用i打头,real型用f(float,浮点类型)打头,string用s打头……
    再次重复一遍,命名是个人习惯问题,如果你喜欢用a b c d dhfla tyy等等无意义的字母命名,也不会出现任何问题。但是不推荐这种方法,这样在有错误的时候给自己的阅读也带来很大的不便。当然,如果觉得自己英文不大灵的话,汉语拼音也完全ok。
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:27:29 | 显示全部楼层
6) 变量的赋值:
    声明好了变量之后,如何给变量设定一个值呢?有两种办法给变量赋值:
a. 在声明的时候加“=”,后面接变量值,在jass中,赋值的传递方向是把等号右边的传给左边,如:
  location integer i = 0
b. 在其他位置使用set关键字:
  location integer i
  set i = 1

* 补充:
    在jass中,除了关键字和前后的空格与\"\"中的空格外,其他的空格将被忽略。使用空格纯属个人编程风格的问题,为了美观或者区分括号(“(”“)”)的对应。在后面相关章节,我也会陆续介绍一些好的编程习惯。

A) 对integer(整数)型的赋值:可使用10进制、8进制、16进制和asc字符,如:
  set i=65 //10进制
  set i=0101 //8进制,以0开头,这个值换成10进制是65
  set i=0x41 //16进制,以0x或0X开头(10进制65)
  set i=\'A005\' //asc字符,写在单引号“\'\'”之中,由4个字符组成,这个值是 1,093,677,109

* 补充1:asc字符的命名主要是用来获取object editor(对象编辑器)中的内容,有两种方法看值,一种是在View(查看)菜单中选中Display Values As Raw Data(sorry我没有中文版不知道这个怎么翻译的)或者按ctrl+d,这时在oe的左边列表中的名称都变成了“****:XXXX (....)”(出现:XXXX为自定义的东西,XXXX是源ID,括号中的内容是那个技能的名称),这里的****就是这个东西的整数id。另外一种情况是,例如一个单位如果有技能,我们想知道这个技能的id,那么我们可以选中那栏,shift+enter,然后我们就看到了这个单位的技能id,中间是用“,”隔开的,例如locust技能(caster经常使用,用来取消caster的pathing(碰撞),使该单位不能被选去或击中)的id就是Aloc(注意区分大小写)

* 补充2:观察oe我们会发现,这些unit、item等等的id非常有规律,例如ability(技能)一定是A开头,buff(不知道怎么翻译更好)一定是B开头,upgrade一定是R开头等等。前面已经说过,integer是32bit的整数,这意味着这些ID的值一定会大于65*256*256*256,也就是说,低30位的integer不会被表示成这些特有的id,符号位(表示正负号的,对于整数来说都在最高位)不会被这些id占用(z的asc码为122,小于128,即2^32这位不会被占用)。说的可能有点多了,而且对于实际来说可能没有什么意义,但是这里也充分说明了blz在做jass时的严谨性

B) 对real(实数)型的赋值:
  set f=1 或 set f=1.0 //可以不输人小数点和小数位

C) 对boolean(布尔)型的赋值:只能去true(真)或false(假两个值),注意区分大小写
  set bool=true
  set bool=false

* 补充:在common.j(以后简称cj)中有如下声明
    constant boolean            FALSE                           = false
    constant boolean            TRUE                            = true
  这意味着全用大写也可以。

D) 对str型的赋值:写在双引号(\"\")之间;如果想在字符串中使用双引号“\"”,需要用\\\",如
  set s=\"abc\\\"\"


3. 基本运算符与运算规则:
    jass支持四则混合运算、逻辑运算、字符串运算,具体如下:
a. 四则混合运算: + - * /
    适用与integer型与real型,其中/(除法)运算对整数来说,将表示整除,即返回整数部分,如7/2得到3,(-5)/2得到-2。另外,要小心0除数错误,war3有一定的容错机制,出现除数为0的时候,不会导致fatal,但是该语句之后的语句将不会被执行。

b. 逻辑运算: >   <    ==     >=      <=      !=     and   or   not
    分别是  大于 小于 等于 大于等于 小于等于 不等于 与(且) 或   非
    返回类型为boolean类型,即true或false
    现在用0代表false,1代表true,讲解一下后三种运算:
and 0 1 | or 0 1 | not
0  0 0 |    0 1 |  1
1  0 1 |    1 1 |  0

*注意:后3种运算只用于boolean型,jass不支持位运算

c. 字符串运算:
    使用“+”连接两个字符串 set s=\"a\"+\"b\"

-----------------------------------------
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:28:08 | 显示全部楼层
4. 运算的优先级:
a.对于四则运算:
    与人们日常习惯相同,* /优先
b.对于逻辑运算:
    符号 > not > and or
c.对于混合四则运算的逻辑运算:
    四则运算 > 逻辑运算
  如:15+4>9-1
d.提高运算的优先级:
    使用括号“()”,在j中只能适用圆括号来表示提高运算优先级,方括号([])表示其他含义,花括号({})无意义。

* 注意:j只支持asc码,不支持中文字符(全角字符),如aB(=1等等,所以在使用j的时候,请尽量不要用输入法,如果使用,请确保不处于全角状态(一般快捷键时shift+空格,输入法有个图标会变圆,正常应该是月牙形)

    最后要说明的是,使用括号不会降低运算速度,所以适量的使用括号可以提高代码的可读性。

-----------------------------------------

5. 常量:
    顾名思义,就是用一个字符来表示一个固定的值,如PI(π=3.141592653589793)。jass不支持居于的常量,而且也不支持手动添加全局常量,所以这里只做一个简单的介绍。
    常量是只读的,换句话说,无法改变已定义常量的值。查看已定义的常量,可以用je或者jw选 constant 或 常量 来查看。常量也写在globals/endglobals之间,如blz.j的第一行:
    constant real      bj_PI                            = 3.14159

-----------------------------------------

6. 基本运算法则:
    前面已经介绍了基本类型和赋值方法,而且也对运算符号做了介绍,下面要具体讲解一下运算法则的问题。
    首先要明确一点,千万不要以为计算机会做很多东西,其实丫就是一傻瓜,一定要把它当成白痴来看待,从最白痴的角度把自己的思路告诉它,它才能明白你说的话。(实际上计算机算10的100次方的办法要比人直接想到1后面100个0或者直接表示成1*10^100方慢很多,他得先把10变成2进制的1010,然后再进行100次的乘法运算)。明确了这一点后,下面我就来带领大家一步步的玩弄这个白痴。

A. 运算的传递方向
    前面已经提到过,赋值是由等号右边向左边传递的,也就是说,计算机会把等号右面所有的数都算好,然后才把值给左边的变量。例如:
  local integer i = 2
  set i = i + i*2 + i/2
    可以理解成计算机首先会把等号右边的i换成2,然后让i=2+2*2+2/2(事实上计算过程不是这样)。

B. 不同类型之间的变量不能进行运算
    不知道在看数据类型的时候,你会不会觉得奇怪,为什么计算机一定要把整数和小数分开呢?实际上,计算机的大脑(CPU)也分脑半球的,计算机只会做0、1之间的运算,而10进制的小数转换成2进制之后,常常是无限循环的小数,所以计算机采用了一种特殊的浮动小数点位置的办法来计算小数(汗,再深了我也不知道了,我不是学计算机专业的),在处理小数的时候,专门使用了浮点运算器来计算(所以我们可以看到一些专业测试CPU运算速度的软件会提供FPU的计算速度)。举例来说,学过c的人都知道,如果是float的2.0/2.0,得出的结果可能不是1,而是0.99999或者1.00001这类的数,判断浮点数的时候不能那么简单的进行判断??当然blz已经考虑到这种问题,在jass中不存在这种现象。理论上来说,整数的运算速度要大于小数的运算速度。
    其实,我要说上面这段话的目的是让各位更了解计算机的工作原理,以便能提高代码的效率。下面进入正题。
    对于不同类型的数,首先要进行类型的转换,然后再进行运算。jass提供了3组类型转换的API函数:

a) integer <-> real
    I2R()与R2I(),意思很简单,integer to real。需要注意的是,R2I()函数只返回整数部分,如R2I(7.5)得到7,R2I(-6.1)得到-6。没有四舍五入的API函数。

*注意:
    往往一些朋友会按照自己的思路去进行类型的转换,例如这样的运算:
  local integer iLevel = GetUnitAbilityLevel(GetTriggerUnit(),\'A000\') //得到技能A000的等级
  local real f = I2R(iLevel*1.5) //希望使fWait为等级的1.5倍
  ...
    但是实际上,如果技能等级为1的话,f的值为1,而不是希望的1.5。所以要注意运算的先后顺序,不要被白痴电脑给玩了:)

b) integer <-> string
    I2S()与S2I()

c) real <-> string
    R2S()与S2R(),另外还有一个函数
native R2SW takes real r, integer width, integer precision returns string
                                  宽度              精度

    对于string,jass提供了另外三个函数,用来做string的运算:
a) native SubString takes string source, integer start, integer end returns string
    得到子字符串,从start开始,到end结束,字符串的第一个字符实际上的序号为0。如果不习惯的话,可以用bj里面的函数:
function SubStringBJ takes string source, integer start, integer end returns string
    return SubString(source, start-1, end)
endfunction

b) native StringLength takes string s returns integer
    得到字符串的长度,即共有几个字符

c) native StringCase takes string source, boolean upper returns string
    没用过这个,但是估计是判断包含一个字符串,upper是用来进行是否忽略大小写的

-----------------------------------------

第一章结束
    本章主要介绍了jass的基本数据类型和如何对数据进行操作。重点是理解声明、逻辑运算的法则和数据类型的转换。
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:29:40 | 显示全部楼层
=================================================

                              第二章 jass语法结构
0. 本章概述
    本章介绍jass语言的基本语句,包括判断、循环、判断和循环的嵌套、数组

-----------------------------------------

1. jass的判断语句
    与大多数的语言一样,jass使用了if作为判断语句。jass的if与passcal的判断语法相似(这里不得不b4 basic一下,赋值与判断相等居然都使用“=”,否则的话这里把basic也加进去了)。

语法:
a)
  if 判断式 then
    ...
  <else
    ...>
  endif

    当 判断式 的值为 true 的时候,执行语句
    若出现else,则为当 判断式 的值为 true 则执行到 else 之前的语句,否则的话,执行 else 到 endif 之间的语句

b)
  if 判断式 then
    ...
  elseif 判断式 then
    ...
  elseif 判断式 then
    ...
  <else
    ...>
  endif

    实现多重判断,不做更多的解释了。


-----------------------------------------

2. jass的循环语句
    一般的语言会提供for循环和loop循环两种循环,另外还有结构化语言中不提倡使用的goto这个转向语句。for循环实际上是一种最简单情况的loop循环,而靠goto控制的转向可以用loop来完成。遗憾的是jass没有提供另外两个循环中非常方便的关键字continue和break,不过靠if和exitwhen也可以完成的。

语法:
  loop
    ...
    exitwhen 表达式
    ...
    <exitwhen 表达式>
    ...
  endloop

    当表达式的值为true的时候,退出该循环。需要说明的是,可以有不止一个exitwhen,并且exitwhen可以在loop中的任意位置。在使用循环的时候,要注意避免死循环。所谓死循环,就是一直循环下去不会满足退出的条件,这在使用loop循环的过程中非常常见,例如:

  set i=1
  loop
    exitwhen i>5
    ... //中间忘记了set i=i+1
  endloop

    war3有针对死循环的容错机制,当达到一定循环次数的时候会自动退出function,也就是说endloop后的语句不会被执行。


-----------------------------------------

3. 判断与循环的嵌套
    往往一个判断或者一次循环不能够满足我们想要做的事情,这个时候就涉及到了嵌套。嵌套不难理解,就是在一个判断或循环中,使用了另外一个判断或循环,如:

  loop
    ...
    if ... then
      loop
        ...
      endloop
    else
      if ... then
        ...
      endif
    endif
    ...
    loop
      ...
    endloop
  endloop

    为什么要把嵌套单独拿出来讲一节,就是因为对初学编程的人来说,使用嵌套的时候常常会出现一些错误。一旦出现了嵌套,一定要先结束里层,然后再结束外层,如下面的语法是不正确的:

  loop
  if ... then
  ...
  endloop
  ...
  endif

    可能看到我前面写的东西,各位也比较喜欢空格了吧。在循环和判断的时候用空格来缩进,这样能够更直观的现实逻辑关系,使代码的可读性大大加强。但是缩进也可能导致一些主观的判断错误,例如:

  if ... then
    ...
    if .... then
    ...
  else
    ...
  endif

    在储存的时候这段代码会报错。问题比较容易,自己看吧(j需要endif的语法结构事实上能够比较好的避免大多数问题,像在c里面考试还会常常出这类的题hoho)。

-----------------------------------------
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:30:18 | 显示全部楼层
4. 数组的使用
    有些时候,你会不会觉得一些重复的操作很烦呢?比如把一个单位身上的物品丢在一个地方,过一段时间之后再放回身上,例如:

    local unit u = GetTriggerUnit()
    local item item1 = UnitItemInSlotBJ(u, 1)
    local item item2 = UnitItemInSlotBJ(u, 2)
    local item item3 = UnitItemInSlotBJ(u, 3)
    local item item4 = UnitItemInSlotBJ(u, 4)
    local item item5 = UnitItemInSlotBJ(u, 5)
    local item item6 = UnitItemInSlotBJ(u, 6)
    ...

    这种时候,我们可以用一个array(数组)来表示一组东西。

1) 数组的声明:
    数组也是变量,所以也有全局和局域之分。全局的数组只要在全局变量的设置中,把array勾上就可以了,实际上是在变量的声明过程中,在变量类型和变量名称之间加入array关键字:

  local item array items

* 补充:在全局变量的声明中,还有个size选项,那个选项实际上是假的,只是你自己输入一个数字提示自己这个数组应该有多大而以。jass为了防止mapper不熟练使用数组产生的bug,采用了自动增加数组长度的方式。

2) 数组的引用:
    使用方括号“[]”作为数组的索引,如访问index=1的变量

  set item[1] = CreateItem(...)

    数组索引的最低值为0,最高值俺没测试过,不过估计没谁那么疯狂,不断的往数组里面添东西。
    结合if判断和loop循环,数组可以减少我们大量的重复工作。

-----------------------------------------

第二章结束
    本章主要介绍了jass的主要语法结构和数组。
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:30:40 | 显示全部楼层
=================================================

                             《jass使用教程?高级篇》

=================================================

                              第三章 过程、函数,Trigger与指针
0. 本章概述
    本章设计到jass语言中更复杂的一些东西,熟练使用好这些东西,你将体会到更多作图的乐趣。从本章开始,我会尽量结合自己在作图的一些经历,配合范例讲解j的实际应用

-----------------------------------------

1. function概述
    结构化语言的特点就是,使用判断、循环和独立的过程来完成所有的功能。function是一段用来完成一个独立功能的代码,使用好j,就要学会用好function

1) function的构成
    一段function,由声明、函数体两部分构成:

  function 名称 takes [nothing | 参数列] returns [nothing | 返回类型] //声明
    ... // 函数体部分
    <return [返回值]>
  endfunction //标志一段function的结束

2) function的分类

a) 过程与函数
    在passcal中,声明一段函数只用这个关键字,而声明过程则用另一个关键字;在c中,则直接使用变量类型来声明函数,可以把无返回类型的函数(void类型)理解成过程。我在这里沿用passcal的概念,将不返回值的函数定义为过程。

b) 常函数与变函数
    常函数返回一个固定的值,而变函数则根据参数的不同和函数体内的运算。常函数是在function的声明前加constant关键字:

  constant function

c) API与自定义function
    API(Application Programming Interface,应用程序接口)是指包含在common.j中的函数,该部分函数实际上是一些已经由系统封装好的函数,用户只要知道function名、功能、参数,就可以利用这部分function做到自己想要做的事情,通常API是实现用户无法通过自定义function来实现的功能,在cj中,API的声明形式如下:

  native          CreateItem      takes integer itemid, real x, real y returns item

    自定义function是指用户在使用过程中,自己定义的一些过程或函数,如我们用到一个地图初始化的trigger中注册trigger的部分:

function InitTrig_MapInit takes nothing returns nothing
    set gg_trg_MapInit = CreateTrigger(  )
    call TriggerAddAction( gg_trg_MapInit, function Trig_MapInit_Actions )
endfunction

3) function的参数
    在function的声明中,takes与returns之间的部分为参数部分。
    nothing关键字表示无参数。传递参数的时候,要写明传递的参数的类型和参数在函数过程中的名称,如传递几个参数,不同参数之间用逗号(,)隔开:

a.  function **** takes integer i returns **** //传递一个integer型参数
b.  function **** takes real x, real y returns **** //传递两个real型参数
b.  function **** takes nothing returns **** //不传递参数

4) function的返回值类型
    返回值类型是在声明中returns后面的部分。
    如使用nothing关键字,则表示不返回任何数值,该function为一个过程。函数只能有一个返回类型,可使用任何在cj中声明的变量类型:

a.  function **** takes **** returns integer //返回integer型
b.  function **** takes **** returns nothing //过程
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:31:26 | 显示全部楼层
-----------------------------------------

2. function的使用

1) 过程体与函数体
    过程体由变量声明、代码两部分组成。变量声明部分是对本地变量的声明,前面已经介绍过,用local关键字来声明
    函数体除了过程体以外,还需要有返回函数值的语句,这是函数与过程的区别。

2) function的可见度
    只有一个function在前面已经声明过,后面的代码才能使用这个function。一个function对整个map都是可见的。

3) 调用function,传递参数,在function中使用参数,取得函数返回值
    我们定义了一个function,那么如何在后面的过程中使用呢?
    对于过程和函数,都可以使用下面的方法来调用:

  call 名称(<参数列>)

    参数列中参数的数量必须与声明中的数量相同,并且类型一一对应,如有一个函数:

  function xyz takes integer a, real b, string c returns integer
    ...
  endfunction

    我们在调用这个函数的时候,必须如下形式传递:

  call xyz(1,0.5,\"c\")

    传递的参数可以是数值(如1 0.5 \"a\"等)、常量(如bj_PI、UNIT_STATE_LIFE等)、变量。在function使用参数的时候,直接使用声明function时候的变量名称:

  function *** takes integer i, integer j returns ***
    local integer a = i + 1
    set j = j + a
    ....
  endfunction

    需要说明的是,参数的传递都是按值传递的,假如有这样2个funciton:

  function add1 takes integer i returns nothing
    set i=i+1
  endfunction

  function b takes nothing returns nothing
    local integer t = 1
    local force foc = GetPlayersAll()
    call add1(t)
    call DisplayTextToPlayer(foc, I2S(t))
    call DestroyForce(foc)
    set foc = null
  endfunction

    那么函数b的最后在屏幕上的显示结果将是“1”,换句话说,add1函数在创建的时候,首先申请了一个整数的空间并命名为i,然后把b函数传递来的值复制给i,在add1函数使用完毕之后,i的数值被销毁,而b函数传递的t的值并没有改变。
    这样的话,我们如何得到函数的计算结果呢?通过返回值!如上一段函数,把add1改为:

  function add1 takes integer i returns integer
    return i+1
  endfunction

    其中,return就是返回语句。return有两个功能,第一是推出函数,第二是返回函数结果。在过程中,可以不使用return,但是一旦使用,不能接任何参数;在function中,至少要有一个return加返回值用来返回函数值。调用函数时,不加call则返回函数值,也就是说,我们可以把调用的函数看成一个变量:

  set i=add1(i)
  call DisplayTextToForce(GetPlayersAll(),add1(i))
  call add1( add1(i) )

* 注意:
    return的参数不是用括号(“()”)来传递,而是用空格。因为return后面的语句都不会被继续执行,所以常常被用来提前退出function。

* 补充:
    得到函数返回值的速度要比变量慢很多,所以不推荐使用常函数。

4) function的执行过程
    当我们在一段代码a中调用function b的时候,a会首先把参数传递给b,然后把控制权交给b,b把所有的东西都处理完之后,再把控制权交回给a。也就是说,一条语句调用一个function,就意味着只有那个function所有语句都执行完毕之后,才会继续运行调用语句后面的语句。
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:32:10 | 显示全部楼层
-----------------------------------------

3. Trigger(触发)
    可能各位会奇怪,明明是一篇jass的教程,怎么突然把trigger又拿出来了而且作为一部分内容呢?实际上,在war3中,trigger是一种function的一种特殊执行方式。刚才已经说了,当调用函数的时候,系统会等待这个函数执行完之后才执行下一段代码;前面也介绍过,function是语言的主体,trigger只能看作是辅助function完成功能的一部分。所以这一节我将对trigger的工作原理做一个介绍。

1) trigger的构成

    现在我们随便创建一个t,加入event(事件)和conditions,然后转化成j来看:

//condition
function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
    if ( not ( IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE) == true ) ) then
        return false
    endif
    return true
endfunction

//action
function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), \"TRIGSTR_2565\" )
endfunction

//Regist
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  ) //1
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_DEATH ) //2
    call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) ) //3
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions ) //4
endfunction

    前面已经说过了,函数必须在前面声明过才能被引用。在这个“Untitled Trigger 001”中,conditon部分用来判断是否满足trigger的条件,action部分是这个t的主体,那么event在哪里呢?就是在最下面我标注成Regist的部分了。好了,我们现在就开始研究这部分每条语句的功能:
    语句1用来创建一个trigger,如果打开这个map的.j文件,我们会发现在globals部分会有这么一句:

trigger gg_trg_Untitled_Trigger_001 = null

    换句话说,实际上这个trigger也是一个全局变量。在我们通过trigger editor创建一条t的时候,we会自动在全局变量中加上一个类型为trigger的带gg_trg_前缀的变量,t命名中的空格将被转化成下划线。这条语句是给 gg_trg_Untitled_Trigger_001 创建一个trigger。
    再来看语句2,这条语句的意思是,为所有的unit注册一个事件,这个事件用EVENT_PLAYER_UNIT_DEATH常量来表示。打开bj,我们可以看到这样的声明:

function TriggerRegisterAnyUnitEventBJ takes trigger trig, playerunitevent whichEvent returns nothing
    local integer index

    set index = 0
    loop
        call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)

        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
endfunction

    也就是说,当我们注册了一个任何unit身上发生的事件的时候,实际上是为游戏中所有的player都注册了一个PlayerUnit事件。可以看到,所谓的AnyUnitEvent事件是个效率非常低的事件。
    语句3和语句4看字面意思就很容易理解了,分别是给 gg_trg_Untitled_Trigger_001 这个trigger加上条件和事件。要注意的是,condition函数无参数,返回类型为boolean,action函数无参数无返回值。
    好了,看了这写,相信你也大概明白一个trigger的工作原理了吧?
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:32:24 | 显示全部楼层
2) trigger的执行过程
    首先,trigger需要有一个trigger类型的变量。trigger类型是个指针,所以要创建一个trigger的实体,并让这个变量指向这个实体(下一节会深入谈一下指针类型变量),这个时候我们就要用到CreateTrigger()函数。
    这时候,已经在war3的执行队列中添加了一个trigger,但是war3在执行的时候并不会理他。想要war3理它的话要怎么办呢?这个时候就要为trigger注册,告诉war3,在这个这个事件发生的时候,你就要理这个trigger。
    然后在trigger注册的事件发生的时候,war3会首先检查,这个trigger是否注册了condition,如果注册了就判断condition是否满足。满足了之后,war3会进行下一步,看看这个trigger有没有action,如果有的话就执行action。这里要说明的是,一个trigger可以有多个conditions和actions函数,只要注册了,war3会检查每一个conditions,如果每个conditions都满足的话,则会执行每个actions。换句话说,conditions之间的逻辑关系是and。除非是比较特殊的控制,否则不建议使用多个conditions或actions,这样会增加代码的控制难度。
    没有conditions的t会直接运行actions。
    trigger是有单独的进程的,一个t的运行不阻碍其他t的执行。

3) 常用的trigger函数与功能
    这里我会对经常用到的几个trigger函数做简要的说明:
    打开cj,我们可以找到管理trigger类型的API部分:

//创建一个trigger
native CreateTrigger    takes nothing returns trigger

//销毁一个trigger,销毁这个t后,这个t将永远不会被再次使用,但不会中断已经运行中的t
native DestroyTrigger   takes trigger whichTrigger returns nothing

//使一个trigger失效,也就是说,war3会忽略这个t
native DisableTrigger   takes trigger whichTrigger returns nothing

//使一个已经被disable的trigger生效
native EnableTrigger    takes trigger whichTrigger returns nothing

//判断一个trigger是否出于有效的状态
native IsTriggerEnabled takes trigger whichTrigger returns boolean

//立即触发一个trigger
native TriggerExecute       takes trigger whichTrigger returns nothing
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:33:10 | 显示全部楼层
下面结合两个范例来消化前面学过的东西

================= 范例 1==========
    前面已经说过,function是线性运行的,而trigger是并行运行的,那么假如我在做一个既能的时候,想要生成一个范围内的效果。但是如果在程序中用PolledWait执行的时候会发现,实际上的时间没法和真实的技能效果时间对应上,那么怎么解决呢?
    一般的方法是另外做一个trigger用来产生效果,在技能触发了t之后,使用来显示效果的t生效,然后再在本段t中进行伤害,到了一定时间之后在将那个效果t禁止。
    用j的话,我们可以利用trigger的工作原理,将两段代码合并在一起。
    首先,我们需要一个使function立即执行并不等待function结束的功能,下面介绍这个功能的实现:

// 参数用来传递一个function
function RunFunctionAsTrigger takes code cFun returns nothing
  //申请一个居于的trigger,因为我们只要求这个trigger运行一次,所以不需要全局变量
    local trigger trg = CreateTrigger()
   
  //把参数传递来的function作为新创建的trigger的action
    call TriggerAddAction(trg, cFun)
  //立即触发这个trigger,实际上就是让function作为另外一个进程运行,不需要等待执行结束
    call TriggerExecute(trg)
  //由于这个trigger已经在运行中,我们不再需要它了,所以马上毁掉这个trigger,并释放内存
    call DestroyTrigger(trg)
    set trg = null
endfunction

    第二步,我们要想要在一定范围内持续出现一个效果,那么这个效果应该是每一定时间出现的,所以要创建第二个基础函数。另外考虑到,这个trigger持续一段时间后怎么结束呢?所以要让这个function有返回类型:

//传递一个function的参数,一个real型是说明多少秒之后执行,bool型说明是否只执行一次
function RunFunctionAsTriggerWithTimer takes code cFun, real timeout, boolean periodic returns trigger
  //与前面的过程一样,只对变动部分进行讲解
    local trigger trg = CreateTrigger()
   
    call TriggerAddAction(trg, cFun)
  //给这个trigger注册一个事件,这个函数的意思就是用于多少秒的时候执行,是否只执行一次
    call TriggerRegisterTimerEvent(trg,timeout,periodic)
   
  //返回我们做好的这个trigger
    return trg
endfunction

    准备工作已经做好了,以后实现类似功能的时候,我们都可以调用这两个函数。使用j的好处就是,我们可以把大量重复的工作封装成function,用到这个功能的时候,只要调用这个function就可以了,而不用一遍一遍的复制,做重复的工作。
    下面的是在一定范围内不断产生lich的frost nova的效果:

//用来产生效果,围绕一点(udg_fFSLocX,udg_fFSLocY)(两个都是全局变量),产生一个效果
function FSEffectWithTimer takes nothing returns nothing
  //随机产生一个x和y,可以看成近似一个圆,因为出现在这个圆之外是个小概率事件
    local real x = udg_fFSLocX+GetRandomReal(0,400)*SinBJ(GetRandomReal(-180,180))
    local real y = udg_fFSLocY+GetRandomReal(0,400)*SinBJ(GetRandomReal(-180,180))
  //产生效果
    local effect eff = AddSpecialEffect(\"Abilities\\\\Spells\\\\Undead\\\\FrostNova\\\\FrostNovaTarget.mdl\",x,y)
   
  //等待2秒
    call PolledWait(2)
  //销毁效果,释放内存
    call DestroyEffect(eff)
    set eff = null
endfunction

//用于在技能中拿来RunFunctionAsTrigger的
function FrostSpaceEffect takes nothing returns nothing
  //把效果函数注册成每0.1秒运行一次,即每0.1秒产生一个效果
    local trigger trg = RunFunctionAsTriggerWithTimer(function FSEffectWithTimer, 0.1, true)
   
  //等待5秒
    call PolledWait(5)
  //摧毁效果trigger,释放内存
    call DestroyTrigger(trg)
    set trg = null
endfunction

    好了,现在你可以做一个技能,然后把上述的jass放到里面,看看效果如何了:)
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:34:03 | 显示全部楼层
==================范例 2 ===============
    在这个范例中,我将带大家实现一个多重物品栏的功能。
准备知识:
    在war3种,blz为每个unit和item都提供了一个integer型的UserData,让各位mapper可以很方便的做一些事情(有点像windows标准控件的tag属性)。

思路:
    首先是要在hero使用技能的时候触发这个trigger,然后把hero身上的item移走物品,下次的时候再把物品取回来,所以我们要给物品创建一个数组来保存。一个物品栏有6个物品,每个hero有n1个物品栏,又有n2个hero,可是jass只提供了1维数组,怎么做呢?聪明的你一定想到了x*a*b+y*b+z的办法,的确这样,我们就给每个hero一个特定的整数标志,每个物品栏一个数字,这样就可以了。下面是利用unit的userdata的实现方法。这个方法共支持8个物品栏

    全局变量:
integer udg_iUnitIndex //用来使每个unit有独特的id
item array udg_itemGoods //用来存取所有的物品栏中的物品

    函数:

//unit物品栏ID:使用userdata的1-3 bit;共支持2^3=8个物品栏,如果想再多的话,就把里面的数字乘以2就可以得到16个了
//用来读取物品栏id:
function fiGetUnitDataGoodsID takes unit whichUnit returns integer
  //由于只是用0~7这几个数,所以我们只要去modulo(模,可理解为余数)就可以了
    return ModuloInteger( GetUnitUserData( whichUnit ), 8 )
endfunction

//用来存放物品栏id:
function pSetUnitDataGoodsID takes unit whichUnit, integer newData returns nothing
    local integer it = GetUnitUserData( whichUnit )
  //新的id只要用newData再加上原来的数字除以8的整数部分乘以8就够了
    call SetUnitUserData( whichUnit, ( it/8*8 + newData )
endfunction


//Unit - UnitID:7~ bit;用来记录单独的Unit
function fiGetUnitDataID takes unit whichUnit returns integer
  //只要整数部分就可以了
    return GetUnitUserData( whichUnit )/8
endfunction

function pSetUnitDataID takes unit whichUnit, integer newData returns nothing
    local integer it = GetUnitUserData( whichUnit )
  //将新的数字乘以8之后,再加上余数部分就可以了
    call SetUnitUserData( whichUnit, newData*8+ModuloInteger( it, 8 ) )
endfunction

    下一步,我们要设定一个全局变量,在每个hero出现的时候,加上这么两句:
    set udg_iUnitIndex = udg_iUnitIndex + 1
    call pSetUnitDataID( udg_iUnitIndex )

    然后是unit切换物品栏的action:

  //得到当前的unit
    local unit u = GetTriggerUnit()
  //得到下一个物品栏的id,由于只是0~7这几个数字,所以只要在每次+1之后取除以8的余数就可以了
    local integer index = ModuloInteger(fiGetUnitDataGoodsID(u)+1,8)
  //得到userdata,由于这个data已经有独立的unit的id和item的id,所以不用分别取了
    local integer id = GetUnitUserData(u)
    local integer i = 0
    local integer it
   
  //保存当前身上的物品,并丢在隐藏的地方
    loop
        exitwhen i>5
        set it = id*6+i
      //设定物品为当前物品栏第几栏的物品
        set udg_itemGoods[it] = UnitItemInSlot(u, i)
      //移到地图隐蔽处
        call SetItemPositionLoc(udg_itemGoods[it],udg_locItemTemp)
        call TriggerSleepAction(0)
      //使物品无敌
        call SetItemInvulnerable(udg_itemGoods[it],true)
      //使物品不可见
        call SetItemVisible(udg_itemGoods[it],false)
        set i = i + 1
    endloop
   
  //设定下一个物品栏标号
    call pSetUnitDataGoodsID(u,index)
    set id = GetUnitUserData(u)
    set i = 0
  //取回物品
    loop
        exitwhen i>5
        set it = id*6+i
      //使物品可见
        call SetItemVisible(udg_itemGoods[it],true)
      //使物品不无敌
        call SetItemInvulnerable(udg_itemGoods[it],false)
        call TriggerSleepAction(0)
      //放回物品栏
        call UnitAddItem(u,udg_itemGoods[it])
        set i = i + 1
    endloop

* 补充: ModuloInteger是bj函数:
function ModuloInteger takes integer dividend, integer divisor returns integer
    local integer modulus = dividend - (dividend / divisor) * divisor

    if (modulus < 0) then
        set modulus = modulus + divisor
    endif

    return modulus
endfunction

    前面介绍过if语句的速度要比运算满很多,因为我们这个函数里面不涉及到负数,所以在这里我们可以自定义一个自己的函数:
function MyModuloInteger takes integer dividend, integer divisor returns integer
    return dividend - (dividend / divisor) * divisor
endfunction
回复

使用道具 举报

 楼主| 发表于 2006-3-28 11:34:20 | 显示全部楼层
3. 指针变量
    在jass中,有50多个继承自handle类型的指针类型变量。按照习惯handle翻译成句柄,但是我觉得这里的句柄不是太符合句柄的含义,还是用更根本的pointer更适合,所以我就直接称之为指针类型了。
    前面已经说过,指针用来指向内存中的一块内存区域,这块区域有可能是个自定义类型的变量,有可能是一个function(code),有可能指向一个复合的单位(如unit,包括在we中看到的所有数据,这种类型应该是war3中最复杂的)……
    在使用指针类型的时候,我们不能进行数学运算,只能通过API函数来存取。而在war3中,所有的单位都是只给一个句柄,如unit、item、region、camera……在给指针类型赋值的时候,大多数的函数过程是这样的:
    首先分配给这种指针类型所指向的实体类型申请一块内存空间,然后把照着参数把相应的资料给这个实体变量,然后再返回这个变量在内存中的地址,也就是指针变量,最后这个被赋值的变量指向那块内存地址。如果在一段函数中使用了指针类型得到了一个值,使用完之后不做任何处理的话,那块被占用的内存虽然不会再使用了,但是由于前面已经申请了,所以系统会认为这块内存是被占用了的,不会再动用这块内存空间,这就是所谓的内存泄漏问题。
    t虽然能够完成绝大部分的功能,但是对于内存处理这部分却是无能为力。例如存在最严重的就是location变量的内存泄漏问题,虽然一次才8个字节,但是常常我们一次性就使用了大量的location,导致时间越长,内存泄露越多。用j的话,就一定要记住:
               释放内存!
    什么样的变量需要释放内存呢?我们可以在je的搜索栏输入destroy和remove,destroy中的绝大部分都是需要释放内存的(也有少部分一旦释放,就是去了我们原来想要的效果了,例如fogmodifier类型),remove中location是一定要释放的,region和rect就要根据具体情况了,否则也会跟fogmodifier出现一样的情况,这些东西就要靠经验了。
    最后要说的是,既然指针变量要用API,那么应该怎样得到自己想要的函数呢?最好要先对jass的API有一定的了解,例如soarchin写的API函数介绍(本教程不会提供jass的API函数介绍)。在最开始使用的时候,可以利用we的t,现做一个自己想要的语句,然后转成j,把语句拿过来用??不过要注意的是,trigger编辑器使用的绝大部分是bj里面的函数,最明显的是带有BJ后缀或者Swap(ped)后缀,一般都会有对应的cj函数,而两者几乎无差别(这个最好用jw来看)。等熟练一些之后,你就会发现一些blz的命名规则了,如判断一个unit,就是IsUnit***,得到unit的一个什么东西,就GetUnit****,设置就是SetUnit***,想要得到一个group,就GetUnits***,得到force就GetPlayers***……总之这个要靠经验的,旁边开着个金山词霸,基本上就能把一个函数的功能猜个八九不离十。当然,最好还是看看jass API的中文介绍,这个最保险,然后可以照着别人的jass学习。了解了jass的动作原理后,你会发现通过jass来学习别人的东西要比看别人的trigger来学习快很多??我经常是把t的演示先转成j然后再看的。

-----------------------------------------

第三章结束
    本章剖析了jass的工作原理,熟练掌握了这三章的内容,实际上你就已经是个jass高手了,剩下的只差实际做几个东西的经验和对API函数的了解和熟练程度了。
    本教程实际上到这里已经结束了,后面还会有一些分享我这段时间的做图经验,

=================================================
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-3-29 03:23 , Processed in 0.047346 second(s), 18 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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