偶久网

 找回密码
 注册会员

QQ登录

只需一步,快速开始

搜索

网站魔兽热门地图

查看: 58559|回复: 276

JASS入门到提高教程,想学J的进来!

  [复制链接]
无界の灵
发表于 2011-9-16 21:54:46
本帖最后由 无界の灵 于 2011-9-16 22:08 编辑

想学习又不想回复的就等我把他出个TXT文档去下载吧!



前言:从我开始学习JASS开始,就打算在我学成以后也写一部教程,目的是让初学者能一看即懂,一学即会。现在这个教程已经基本上完成,现在只有一小部分还在创作中。
本教程与其他教程的不同之处就在于:我是将我自己的学习历程,以及自己在平时做图过程中的理解和体会都写了进去。并且我是以C语言的教学方式去写,因为在我所接触的C语言课本中 潭浩强版 让我学起来感觉很快。于是就按这样的一个思路去写。希望读者能够循序渐进,一步一个脚印慢慢地走上JASS之路。 由于个人水平有限,教程中难免会出现这样那样的不足,还请高手指正。
       这是一个由入门到提高的教程,无论你是否接触过JASS,都可以阅读,只要你觉得此教程对你有用处。这个教程是我用WORD事先编写好再复制上来的,在论坛上会存在一点格式问题,因此还请大家多多包函,待我完成之后会将WORD文件发布上来,或者做成电子书发布上来。


Jass入门到进阶教程

什么是JASS
JASS是魔兽争霸脚本语言,这种语言只会在魔兽争霸里面使用。如果你想自已编写属于你自己的魔兽争霸地图,那么,JASS是你必需了解和学会使用的。
JASS从某种意义上讲,也是一种计算机语言,虽然它不像C/C++那样应用广泛,但是至少它也拥有C/C++那样样的特性——用语句向电脑发布命令,在什么时候怎样做,这样做持续多久。
这就是JASS,一门只能用在魔兽争霸里面的计算机语言,学习它是为了更好地做出自己的魔兽地图。比起C/C++,它简单多了,甚至它比BASIC还要简单。因此不要认为这是多么难学的一个东西。
那么我们开始学习它了。学习的过程也是了解的过程。
在跟我学习JASS的过程中,我希望大家把Ttrigger)也就是触发忘掉。因为我不打算一开始就与T结合起来讲JASS,但是我会在适当的时候与T结合起来。

第一章:变量
    我为什么要最先讲这一章呢,是因为很多人无论是用T做图还是用JASS,都会遇到一个词——变量。
    那么什么是变量呢?
    变量,其相对词在计算机中反应出来的是常量,要想了解变量就要先了解常量。
常量,顾名思义:就是经常一样的数量。用专业一点的话来说就是一个稳定的,确定的,已知的数据。例如25c2.5等等,这些确定的数据,用一个概念来说叫做元素。
那么什么是变量呢?常量的相反面,那么就是“不稳定”,“不确定”,“未知”的数据。
这些数据我们不知道它是什么,最容易了理解的概念就是“未知数”。
    变量就是未知数,例如:在小学5~6年级的时候我们就开始学习方程了,eg:5 + x = 12
那么在这个方程里面的x就是一个变量,我们很容易求出这个变量的值,它是7。再来一例吧,在初一的时候我们开始接触二元一次方程,形式就像 x + y = 12 。那么在这个二元一次方程里面,x y都是变量,这两个变量同前面那个一元一次方程有什么不同?聪明的你一定发现了,前面那个只有一个值,而后面这个可以有很多值。是的,这就是变量。
    变量,它代表着具有相同特性的一群数据,比如上面的那个等式,如果x y都是整数,那么x,y可以是1,11 or 2,10 or 3,9等等很多组合。而这个整数就是这些组合的同一特性,而xy只不过是用来表示具有“整数”这一特性且满足x + y = 12这个条件的所有整数的一个群体,也叫集合。
    现在,理解什么是变量要容易多了吧。
我们可以给变量下一个定义:具有相同数据特征的所有数据的集合。
通俗一点,变量就是有着相同特征的数据的代表,在计算机里面把这种代表也叫做符号,类似人的名字,用以区分其他的变量。
JASS中的变量一般有全局变量与局部变量之分。
全局变量:能供整个地图使用的变量
局部变量:只能在本函数中使用的变量。
那么什么是相同数据特征?请看下一章。
第二章:数据类型
数据类型简介
   在上一章里提到相同数据特征这一词,通俗点就是这些数据有一个共同的特点。打个比方:鸡,这一名词让我们一听到这个词就会在脑海里面出现一只鸡的样子的图片——有两只脚,每只脚只有3个爪子,一双翅膀,有羽毛,嘴是尖的,颈有点长,眼睛很小,有冠,且是红色的,能进行短且低距离的飞行。那么“有两只脚,每只脚只有4个爪子,一双翅膀,有羽毛,嘴是尖的,颈有点长,眼睛很小,有冠,且是红色的,能进行短且低距离的飞行”这就是所有鸡的共同点。一说鸡就表示天下鸡都是这样的,因此鸡就是代表有着上面那些特征的所有动物的变量。那么说到这个共同点,共同特征,在计算机里面怎么讲的呢?
我们称之为:数据类型。
   作为一门计算机语言,其最基本的就是数据类型,那么我们就开始来接触JASS的数据类型吧。
jass提供的功能足够丰富,共提供了6种基本数据类型:
1) integer
整数型,例如:012-1-2 相当于C语言中的int
2) real

实数型,例如:0.1,0.001
相当于C中的float

3) boolean 布尔型,一般只有两个值就是:true false
4) string
字符串型,用来记录字符

5) handle
数据指针型,是jass语法的一个基础类型,相当于C中的poin类型。用来指向内存中的一个数据地址。

6) code

函数指针型,用于指向内存中的函数地址


基本数据类型最主要的特点是,其值不可以再分解为其它类型。也就是说,基本数据类型是自我说明的。

同所有语言一样,定义数据类型就要通过以上几种数据类型关键字来进行定义。
比如,定义一个整数型的变量I integer
i
,然后我们一看到i就知道是整数型,代表着一群整数。

可见定义一个变量的格式可以写成这样:
  
变量类型关键字
变量名
同所有语言一样,要想使用一个变量,我们就必需先定行定义然后再使用。什么?为什么要这么做?这么讲吧,我们要给胡锦涛打电话反映一下我们WAR3做图的艰辛,那么首先我们要知道小胡的电话号码,有了这个电话号码才能给他打电话。那么小胡的电话号码就是一个变量,我们要使用电话号码这个变量,就必须有人告诉我们是哪个号码。变量的定义就是告诉电脑,现在有这么一个变量。
只有存在的东西才能被人看到,才能被人使用。定义变量就是让某个变量存在。


整数型(integer

WE里,整数型可以是以以下情形出现:

1.
十进制
如:102030也就是我们常用的数字。
2.
八进制
如:010110进制65),01510进制13),017777710进制65535

可见8进制是以“0为前缀的,后面跟的数每一位必须小于8


3
十六进制 如:0X2A(十进制为42)  0XA0 (十进制为160)  0XFFFF (十进制为65535)可见十六进制整常数的前缀为0X0x。其数码取值为0~9A~Fa~f

4
ASC字符
’Hmag’’Edem’’Oblm’等等
             这类数的特点是写在“’’”(半角单引号之内)的,以上三个分别表示:人类大法师,恶魔猎手,剑圣的ID(身份证号码,哈哈这样更好理解一点。一个单位对应一个号码,和我们的身份证号码有着共同的特点。)
实数型(real)
   在高中就学习过实数,实数就是所有有理数的集合,它能在一维数轴上用点标示且有且只有一个点与之一一对应。

  
在计算机里面的实数也这样的,不同的是他不像我们数学里面表述得那样随便,在计算机里面更讲究精确,因此C里面将这一类型称为float型,单精度型一般都要有小数点和小数位。例如:1.25 , 0.67 , 3.14159265等等。
   在计算机里面实数有两种表现形式:十进制数形式和指数形式。后者由于我们用来写图所以用得很少,最常见的是前一种形式。


十进制数形式:由数码0~ 9和小数点组成。例如:0.0.255.7890.135.0300.-267.8230等均为合法的实数。
   指数形式:由十进制数,加阶码标志“e”“E”以及阶码(只能为整数,可以带符号)组成。其一般形式为a E n a为十进制数,n为十进制整数)其值为 a*10,n 如: 2.1E5 (等于2.1*10,5), 3.7E-2 (等于3.7*10,)-2*) 0.5E7 (等于0.5*10,7), -2.8E-2 (等于-2.8*10,)-2*)。以下不是合法的实数 345 (无小数点) E7 (阶码标志E之前无数字)  -5 (无阶码标志) 53.-E3 (负号位置不对) 2.7E (无阶码)
   注:指数形式可以不用掌握,只要了解就可以了。
   在给一个实数型变量赋值的时候,例如:

set f = 2.0 (
这里的2.0可以写成2但是电脑要先把2转成2.0再进行相关计算,因此为了让电脑少走点弯路,我们还是写成2.0吧。)
布尔型(boolean
   布尔型,只有两个值,true false (或TRUE,FALSE)。无论怎么样,只要定义为布尔型的变量,它的取值情况只可能是这两种情,没有第三种。
   这是一个起判断标识作用的一种数据类型。有时候用来表示两种状态,如有英雄就用true表示,没有就用false表示。
字符类型(string)
   这个类型在C里面是字符串,因此不难理解,就是很多个字符串连在一起组成的字符。
   那么在JASS里面一般是写在””双引号里面的内容。例如”I am stupid!”那么这就是一个字符类型的数据。
   定义一个字符型变量:

string str1 = “I am not a girl.”

   以上就是一个定义字符型变量并为其赋值的例子。
handle code 这两种类型
   这两种类型我认为可以归为一类来理解(只是理解,用还是要分开来用的)——指针。
   什么是指针,指针就是指向内存中的数据对应的地址。
  
handle
可以认为是指向内存中变量地址;code则可以认为是指向内存中函数的地址。
   至少我是这么理解的。
   指针的作用,通过指针能更准确,更快速地从内存中读取出相应地址的数据。
其他类型
  
unit
(单位)group(单位组) item(物品) location() 等等,这些在以后的编图中会慢慢遇到,而这些数据类型的定义,使用同以上几种是一样的。
第三章基本运算符与运算规则
jass支持四则混合运算、逻辑运算、字符串运算,具体如下:
1. 四则混合运算: + - * /
适用与integer型与real型,其中/(除法)运算对整数来说,将表示整除,即返回整数部分,如7/2得到3(-5)/2得到-2。另外,要小心0除数错误,war3有一定的容错机制,出现除数为0的时候,不会导致fatal,但是该语句之后的语句将不会被执行。
2. 逻辑运算: >(大于)<(小于) ==(等于) >=(大于等于) <=(小于等于) !=(不等于) and() or() not()
这种运算的结果只能是布尔型(truefalse),后三种分别表示:
and: 所有都是true结果才是true否则结果false,如:2>1 and 3>2 这个结果就是true,否则就是false.
or : 只要有一个是true那么结果就是true,如:2>1 or 2>3 这个结果也是true,因为2>1true.
not: 表示不是not true 就是false,同样:not false就是truenot(not false)还是false.
3.
字符串运算:使用“+”连接两个字符串

例如:string a =“I am”


string b = “a boy”


string c

set c = a + b

那么c的结果就是”I am a boy”

4.运算的优先级:
a.对于四则运算:
   
与人们日常习惯相同,先* /+ -
b.对于逻辑运算:符号(>,<,>=,<=,==) 优先于not优先于and or
c.对于混合四则运算的逻辑运算:
   
四则运算 > 逻辑运算
   
如:15+4>9-1

d.提高运算的优先级:
  
使用括号“()”,在j中只能适用圆括号来表示提高运算优先级,方括号([])表示其他含义,花括号({})无意义。

* 注意:j只支持asc码,不支持中文字符(全角字符)。最后要说明的是,使用括号不会降低运算速度,所以适量的使用括号可以提高代码的可读性。

基本运算法则:

按优级先后顺序来进行计算


注:赋值运算的优先级是最低的,也就是=(等于)运算优先级是最低的。例如。

      
boolean
t = 2>6


那么这个赋值结果就是 t = false 因为>号的优先级高于=号,因此先计算出2>6结果是false,然后再把这个false赋值给t


JASS
的赋值方式是从右往左赋值的,但是JASS不支持类似 I = j = l = 6这种赋值方法,会报错。


不同类型之间的变量不能进行运算:

这就是说在JASS里面只有同种类型的数据才能进行运算,例如:4/2.0,在我们看来,这完全是可以运算的,但是计算机则不这么认为,如果能运算的话,那么计算机则先将4转成4.0然后再进行4.0/2.0进行运算。然而在JASS里面整数型如果想与实数型数据进行运算就要先转化成实数型,然后再参与运算。(转换方法在以后放出)


例:


local real f = 2.4


local integer i = 2



local integer j


local real t


set j =R2I( f /I2R(i) )
//
如果这个要进行运算的话,那么要先把i转成real型,然后再参与运算,然后再把f/i的结果这个实数型转换成整数型赋值给j那么这样一来,j就是1了。(在电脑里没有四舍五入这一说,电脑没人脑那么聪明灵活的。)


set t = f/I2R(i)
//
这个就是先把i转换成real然后再参与运算,结果赋给real型变量t,这结果就是1.2


注:例中的R2I() I2R()分别是real to integer integer to real这两个转换函数,很好记的。前者是把实数转成整数,后者是把整数转换成实数。

评分

1

查看全部评分

无界の灵
 楼主| 发表于 2011-9-16 21:56:02
本帖最后由 无界の灵 于 2011-9-16 22:01 编辑

第四章 JASS程序初步设计

第一节
JASS
语法结构

本节介绍JASS程序设计的基本方法和基本的程序语句。本节介绍一些基本语句及其应用,使大家对JASS程序有一个初步的认识, 为后面各章的学习打下基础。
A.JASS程序的执行部分是由语句组成的。 程序的功能也是由执行语句实现的。
JASS语句可分为以下五类:表达式语句;函数调用语句;控制语句;复合语句。
1.表达式语句JASS中的表达式语句是同赋值语句一样的,都用set 起始。例如:set
x = 5*i.

2.函数调用语句
JASS中的函数调用语句一般是call起始。例如:call GetHeroInt(whichHero)(这句是得到whichHero这个英雄的智力)
3.控制语句这类语句一般包括循环语句和判断语句。例始loop
exitwhen i == 5

if
GetHeroInt(whichHero) > 200 then

…………………

…………………

set
i = i + 1

else

…………………

endif
endloop

4.复合语句
这类语句通常指是多层嵌套里面的语句。例如

loop

exitwhen …

loop

exitwhen …

if ………
then

……………

if……… then

…………..

endif

endif


endloop


endloop

像这样多层嵌套里面的语句,可以看成是相对独立(实际上无法独立)的一块。
  从3和4两个例子中我们可以看出JASS支持嵌套判断和嵌套循环。而在JASS里面,循环只有loopexitwhen 判断式……endloop以上表示当判断式为true的时候跳出循环,否则执行动作不跳出循环。JASS里的判断有
if
判断式 then

…………

else

………

endif
   

还可以是
if
判断式1 then

…………

elseif
判断式2 then

………

elseif
判断式3 then

………

………

elseif
判断式n then

………

endif

以上表示:如果判断式结果为true那么执行then后面的语句,否则执行else后面的语句;多重判断的时候则是再判断elseif后面的判断是否为true如果是的那么执行then后语句,如果不是依次执行。

在例34中,将循环和判断嵌套可以实现多种功能,这在熟练之后能写出功能强大的函数。

B.
变量的定义与声明
JASS中的变量与其他高级语言一样都要进行定义与声明才能使用。
在第一章的时候提到JASS中变量有全局变量与局部变量之分。那么在这里进一步阐述这两个变量之间的区别。
全局变量在地图中一般是定义在最前面放在gloable….endgloable之间,并且是以udg_为前缀,或者以其他类型为前缀(gg_rec_表示一个全局矩形区域;gg_trg_表示全局触发器;等等),其定义格式为变量类型udg_变量名
而局部变量在定义中只能是在函数体的最前面部分,而且以local 变量类型 变量名的形式进行定义与声明的。
例:
+  Shingo Jass Highlighter 0.41
globals

integer udg_heroInt

integer udg_heroAgi

rect gg_rct_heroReviveAre

trigger gg_trg_herodie

endglobals



以上是全局变量的定义与声明


+  Shingo Jass Highlighter 0.41 function selectplayerHero takes player currenPlayer returns unit

local unit heros
local integer i = 0
//定义的时候赋值

local  boolean t

set t = false
//定义之后赋值,这两种赋值方法、//都是正确的,只是要看情况使用。

…………..

endfunction



以上是局部变量的定义与声明。

全局变量,能供全地图所有函数使用;而局部变量只能在本函数中使用。

从以上例子中的局部变量的定义中我们可以看到function selectplayerHero takes player currenPlayer returns unit 这一条语句,聪明的你们,一定猜到这是一个函数。对的,这是一个函数的定义,这句是要告诉WE
,“马上要有一个叫做 selectplayerHero的函数产生了!这个函数有一个玩家类型的(也是一种数据类型)形式参数,这个函数返回一个单位型值。”那么这里出了一个叫做形式参数的东东其实这个东东也相当于一个局部变量,前面的player是它的数据类型,注意这个参数也只能在当前函数中使用。
C.函数的定义与声明
从以上局部变量的定义与声明中的例子里面我们接触到了“function
selectplayerHero takes player currenPlayer returns unit
”这样的语句。刚才也略讲了一下这个语句的作用,那么现在接着细讲这个语句。

function是函数定义的关键字,表示这后面要开始定义一个函数了;selectplayerHero是这个函数的名字;takes player currenPlayer表示的是,这个函数在调用的时候要传递一个palyer类的参数;returns unit,表示的是,这个函数在执行完毕之后要返回一个值,这个值的类型是unit(也是一种数据类型)类型。
那么至此,我们可以得到定义和声明一个函数的格式:
+  Shingo Jass Highlighter 0.41 function 函数名takes <数据类型1 参数1>,<数据类型2 参数2>…<数据类型n 参数n> returns <返回值数据类型>

< 语句……..

………….>


endfunction


<>里面的内容是可以没有的,需要注意的是,如果没有<>里面的内容,要用nothing代替。如 function abc takes nothing returns nothing。如果函数是returns nothing的,那么函数里面最后一句就不要return,如果函数有返回值,那么函数里面一定要有return(注意:哪里是returns,哪里是return不要搞混淆了)。定义一个函数之后,要用endfunction结尾,在JASS里面很多都是这样,如gloable….endgloable
if …..then
…..esle…..endif
loop….exitwhen…
……..endloop
;因此这点不能忘记否则有可能倒致你的WE崩溃掉。
一个函数里面可以没有语句,就是一个空函数,什么都不做,也不返回什么。Eg:
+  Shingo Jass Highlighter 0.41 function nothingdo takes nothing returns nothing
endfunction


这个nothingdo函数就是一个空函数,里面什么都没有,所以它什么都不做,也不返回什么。
并非它没有意义,在特定的情况下还是有用的。比如你写了一个系统,当你写到某个函数的时候突然有事情要去做,而你这个函数刚好就写到声明部分,那么为了不让WE出现BUG,你就姑且这样定义一个空函数吧,以便你做完事情之后回来能接着自己的思路去写。
D.函数的调用
函数的作用主要就是完成某个功能,而在一个系统中有很多地方要用到这种功能,因此写成一个函数来减少自己的工作量。
那么当你写好一个函数之后,最重要的就是让他在你的系统中实现他的功能,不然你写这个函数做什么呢?那么,如何实现呢?——函数调用!
函数调用:就是指在一个函数中引用另一个函数,这另一个函数可以是自身。
调用方式就是:
1call 函数名(<参数1>,<参数2>,……,<参数n>,)
例:调用上面的那个空函数:call nothingdo()这个函数没有参数,因此括号里面空着;再调用上面的selectplayerHero函数:call selectplayerHero(whichplayer)这个函数有一个player类型的参数,那么调用的时候就要在括号里面带上一个player类型的参数。
2.还可以赋值。例如:
假如我已经定义了一个unit类型的局部变量u那么调用上面那个调数可以这样写:
set
u = selectplayerHero(whichplayer)
在这里要注意的是:selectplayerHero这个函数的返回值是一个unit类型值,那么赋值也就只能赋给一个unit类型的变量。
以上两种都是调用函数。
3.调用函数自身(递归调用)
早在初三或者高中就接触过“递归”这个词,因此听起来并不陌生。
在此简单地对这一概念做一个解释:递归就是“递推,归纳”有两个步骤,1是递推;1是归纳。
函数调用自身可以实现高效,快速,唯一不足之处就是很占用资源。例子说话:
1+2+3+……+100,这个是如何实现的呢?可不可以这样理解:sum(100)可以认为是sum(99)+100;sum(99)又是sum(98)+99,依次递推下去,最后有一个值必需知道,那就是sum(1),很显然sum(1)就是1。这样一来,从一个结论往原因追溯,得到一个最根本的结果,再从这个结果出发,归纳出自已想要的结果,这就是递归。
+  Shingo Jass Highlighter 0.41 function sum takes integer i returns integer
local integer   f
if i == 1 then
set f = 1
else
set f = sum(i-1)+i
endif
return
f
endfunction


这样,在求解以上问题的时候,只要调用这个sum函数就可以了,只需写 call sum(100)就可以了。
注:递归调用可以是直接调用,也可以是间接调用,以上例子就是直接调用;再列个间接调用的例子:a函数调用b函数,而b函数又要调用a函数,那么在这样的一个过程中,a函数也可以说是递归调用。
好了,在这节就不对有关函数的东西进行过多的讲解,在后面的章节里面将会对函数进行重点讲解的。

无界の灵
 楼主| 发表于 2011-9-16 21:56:15
本帖最后由 无界の灵 于 2011-9-16 22:07 编辑

第二节:一些JASS之外的,但是与JASS密切相关的

在写这一节的时候,我被卡住了,因为这一章名为《JASS程序初步设计》,在经过第一节的讲解之后,我突然发现,这一节没法写了,因为第一节的语法结构一给出,聪明的你一定能写出一些简单的函数,实现一定的功能,从而已经实现了本章的任务——初步设计JASS程序。但是,但是我突然发现,我前面讲的结合起来只能让你们知道JASS可以这样写,而不能做到怎么写。“靠,你这不是一回事嘛?‘这样写’和‘怎么写’不就是一回事嘛,你Y的是唐僧啊?”其实不然,“这样写”只是说只要你在这样的一个规则下,可以这样做也可以那样做;“怎么写”不仅包括上面的“这样写”还包括“怎么想”,有了想法才能指引我们去做。那么这一节就来实现“怎么写”这样的一个任务。

工具。

“工欲善其事,必先利其器”。嗯,工具很重要,无论是做什么我们首先都要有个工具,没有工具有些事无法完成,有些事很难完成。既然工具如此重要,那么JASS工具有哪些呢?

1.
Jass Shop Pro 这是一个很棒的Jass工具,能直接通过读war3图来读取图中war3map.j文件,从而看到图的代码,无论作者是如何加密的,都可以通过这种方法看到地图的代码。网上有下载。
2.
Jass Craft 这也是一个很好的Jass工具,jass shop pro一样,都是用来编写JASS程序的,我用了JASS CRAFT之后总觉得有个不好的地方,就是他不能直接读取WAR3地图,如果直接读图的话,出现乱码,我不知道是不是只我的jasscraft是这样。网上也有下载。
3.
WE也就是魔兽争霸III世界编辑器。这个是无可替代的。你所有的函数必须拿到这里来实现功能,没有WEJASS的一切都是空谈,变得无意义。这也是我们学习JASS的主要原因。
那么,我希望以后在讲课的时候大家准备好Jass Shop ProJass Craft WE

算法。

“三思而后行”,这个词本意是让人先考虑这样做的后果然后再行动。那么用在这里应该

就不是这个意思了,我的本意是在写程序的时候,你首先要想好这个程序的思路,然后

再动手写。
在这里,这个思路就是你打算让电脑怎么去做,做什么。这个思路就是你给电脑下达的命令。这种思路,我们称之为算法。

算法是程序的灵魂,因此很重要。程序可以表示为:

程序 = 算法+数据结构

那么,什么是算法?简而言之,算法就是处理问题的方法与步骤。比方说:在ATM机上取钱。我们的所遇到的问题是,我要从ATM机上取出钱;如何处理这个问题呢?首先,我把磁卡放进去,然后输入密码并确认,再输入取款金额并确认,等待若干秒后取走现金,取回磁卡。这样就处理完毕“取钱”这个问题了。我们的方法就是用磁卡来取,步骤就是上面所说的。那么这就是一个算法。

处理任何事都有其方法和步骤,我们就把这个方法和步骤叫做算法,世界上事情之多,那么算法也无从计数,因此我这个小小教程里面不可能一一讲完,只做一些讲解。
1.
算法的特性
a.
有穷性:一个算法所要解决事情所要进行的步骤必须是有限的,就是进行到什么程度必须终止。我们不能让一个程序无止境地进行下去,这样不合理,因为程序无止境地进行下去也就意味着问题永远也无法解决,那么显然是个无效的算法。
b.
确定性:这就是说,一个算法里面的每一个步骤必须是明确的,一是一,二是二,不能含糊不清。例如,上面的取钱例子中,如果我们不输入取款金额我们就不可能取到钱,因为金额一旦不确定,那么ATM机就不去执行下一步命令,要想取到钱就必须给它下达一个确定的命令。
c.
0个或多个输入:这就是说,算法要从外界取得必要的信息。还以上面取钱为例:步骤中的,插入磁卡,输入密码,输入金额,这些就是“输入”,ATM机也只有获取了以上这些关键信息才会给我们钱,我们才能完成“取钱”这个动作。
d.
有效性:算法的每一步必须是有要效的,能执行的。同样以取钱为例:我们在输入金额的时候不能输入负数,也不能输入超过你卡里面总金额和银行规定的每次取钱最高金额。否则就是违法操作,将不被执行。再来个更浅显的例子:我们都知道除数不能为0,因此你不能让电脑进行 2 / 0这样的一个操作,你都算不出来的一个东西,电脑如何去做。

2.算法举例

在这一块就给一些简单的算法示例,以让大家对算法有个更好的消化。

Eg 1.
1X2X3X4X5


我们很容易地想出一个最原始的做法,就是:第一步:算出1X2 = 2;第二步:算出第一步得到的结果再与3相乘得到 2X3 = 6;第三步用第二步算出的结果与4相乘得到6X4=24;最后一步用第三步得到的24X5 = 120。到此,就算完了。这个方法很有效,也很简单,但是很麻烦,不是吗?如果是1X2X3X4X…X100呢,我们是不是要写99步呢?当然不能写这么多步,非把人累死不可。

聪明的你也许早就发现,每步都有一个相同的操作,那就是:由上一步得到的结果再与本步骤的数相乘,那么我们可以这样做:

S1
定义一个变量用来做乘数令p=1S2再定义一个变量做另一个乘数令i=2S3用表达试写出来pXiS4然后我们再把pXi的结果赋给pS5然后再令i的值加上1,也就是i = i + 1;这样,当i不大于5的时候重复S3~S5步骤,否则输出结果p,那么p就是1X2X3X4X5了。

举一就要反三,那么要你计算100!(阶乘,就是1X2X3X…X100)该怎么做呢?你也许已经知道了,只要当上面那个i不大于100重复S3~S5步骤,否则输出的p = 100!了。

同样,如果要你计算1X3X5X7X9X11X13X15这怎么办呢?同上,我们只要在S2的时候令i=3,然后在S5的时候令i = i + 2这样,当i不大于15的时候重复S3~S5,否则输出p,同样的,这个p就是1X3X5X7X9X11X13X15


eg 2.
一个班上有40个学生,要求将他们之中数学成绩在80分以上的打印出来。

我们的算法是这样的:n表示学生号,ni表示第i个学生的学号。用g表示他学生成绩, gi表示第i个学生的成绩。

S1i = 1S2如果gi>=80成立那么打印gini,否则不打印;S3i = i + 1S4如果i<=40返回S2步继续执行,否则算法结束。

本例中,用i作为下标来控制学生的序号,(第几个学生,第几号成绩),当i大于40的时候,表示操作已经结束。

Eg 3. 1-1/2+1/3-1/4+…+1/99-1/100

对这个题目进行分析,里面有加有减,都是分数(1=1/1),且分子都是1,分母分别是123,……,100和我们前面所遇到的问题有所不同的是,这里面不只一种运算符号,有加有减。

但是这并不能难倒聪明的你,对吧?

我们知道,减号其实就是负号,减一个数其实就是加个这个数的相反数(什么是相反数?我建议你回去看看现在各版本的小学数学。)那么其实这个题目可以写成这样:

1/1 +(-1/2)+1/3+(-1/4)+…+1/99+(-1/100)
同时我们也知道,负数与负数相乘得正数,正数与负数相乘得负数,那么一个数a可以写成a = (-1)*(-1)*a。在题目中可以反映为:-(1/2)=(-1)*(1/2) 1/3 = -1)(-1*1/3)。那么,在您脑中一定已经形成这个题目的算法了。可以表述为:

S1
sign = 1S2sum = 1S3deno = 2S4sign = (-1)*signS5term = sign *(1/deno)S6sum = sum + termS7deno = deno + 1S8:如果deno<=100成立,那么返回S4继续执行,否则输出sum,算法结束。


以上3个例子我是用文字将这个算法描述出来的,各学读者们,你们现在的任务就是将上面的算法结合前面讲的,用JASS语言表现出来,如果你们能准确无误地写出来那么你该高兴了,你又进了一步了,向JASS走近了一步。

思考题:
求出从2000~2500年中的所有闰年,并输出闰年号。(闰年的特点就是1.能被4整除但不能被100整除的年份就是闰年;2.能被100400整除的年份也是闰年)  


无界の灵
 楼主| 发表于 2011-9-16 21:56:35
由于这一节的内容都是图片,而从WORD上复制过来的图片无法直接上传到论坛上去,所以我截了图贴上来。

20090208_73505384d8de1766391e4EPVRiOcPQq3.jpg
20090208_3c79d2e79321367dcc7fFzgw1iw8ZwRl.jpg
20090208_d749cfd48c5c2fdbb102wviauC018gus.jpg
20090208_7a38b784e47238b4fd4aFdC0HOWZzzdP.jpg
20090208_e21e52bc3e3e2862e207h7DhJrAp1SDF.jpg

20090208_30c1788f4b5af0e71352ZFchaImOa7Vj.jpg
20090208_8a9af89e45ecce0ac238jpRU2LSs5Xvt.jpg

无界の灵
 楼主| 发表于 2011-9-16 21:57:01
第五章
数组


从现在开始,我们将学习JASS中相当重要的一种变量,从数据类型上讲,数组它不是任何一种数据类型,它应该是一种数据结构类型,因为数组可以分裂为很多个基本数据类型。按数组元素的类型不同,数组又可分为数值数组、字符数组、单位数组,单位组数组等各种类别。
这里我们主要介绍几种我们在JASS里面常用的数组。
数组的定义
JASS中,数组的定义是这样的:
全局变量数组:
+ Shingo Jass Highlighter 0.41
globals
integer array udg_intg

//全局整数数组intg

real array udg_realn
//全局实数数组realn


unit array udg_units

//全局单位数组units
endglobals



在局部变量中数组的定义是这样的:
+ Shingo Jass Highlighter 0.41
local integer array inte

//局部整数数组inte

local real array realn

//局部实数数组realn

local unit array units

//局部单位数组units



那么我们可以看到数组的定义与变量的定义的区,那就是在变量数据类型与变量名之间多了一个关键字’array’,而这个array在英文中就是数组的意思。那么定义数组变量的格式就是:

数组数据类型
array
数组名
Jass里面对数组的定义规定很松,因为它没有要求在数组定义的时候写上数组的最宽度,也就是数组的长度,数组的长一般表示这个数组能容纳的最大元素个数。
Jass中明确规定“constant integer


JASS_MAX_ARRAY_SIZE
= 8192这就是说,JASS
里面任何数组的最大尺寸是8192,因此只要你的数组元素不超过这个数,就可以了,如果超过了那就会出现不可预料的错误。
唯一不足的地方是,JASS里面的数组只能是一维数组,因此在处理一些问题的时候确实有些不足,但是这些不足BLZ已经为我们想出了一个解决的办法,当然这个解决办法不是这章的内容。总之一维数组在JASS中已经足够我们使用的了。


数组的赋值

先什么都不讲,先看一例再来看数组的赋值。

例如把11010个整数分别赋值给一个整数数组
+ Shingo Jass Highlighter 0.41
function SetValue takes noting returns nothing
local integer array integerArray
local integer i = 1
loop
exitwhen i >10
set integerArray[i-1] = i
set i = i + 1
endloop
endfunction



从上面的例子可以看到,数组元素的引用方式是
数组名[下标]下标通可以是常量(一般是不小于0的整数),整数型的表达式,例如我上面的下标i-1就是一个整型的表达式,并且i-1最小值是0不会比0小。

那么在JASS里面给数组赋值就是

set
数组名[下标]
=
数组所对应的数据类型常量或变量

“数组所对应的数据类型常量或变量”是指:这个数组的数据类型,比如我上面那个例子中,数组integerArray就是整数型,那么给这个数组赋值也只能是整数型的数据。“常量或变量”,指这个数值可以是一个定值,如123之类,也可以是一个变量,如上例:i


数组的应用

本小节其实没有多少内容可讲,只是加强大家对数组的认识,分别以整数,单位这两种数据类型的数组的应用为例,对此进行数组的应用的讲解。
1.
整数

比较整数10520357941这些数的大小,并以降序排列。

分析:所谓降序排列就是将数以从大到小的顺序排列。那么这些数我们就可以用数组来进行存贮。然后每两个数进比较一次。我们可以用传说中的冒泡法进行比较。

冒泡法:将相邻的两个数进行比较,将大数放在前面。
20090208_06d136304aed8531b1327lb9NDRhTcJv.jpg

以上就是一趟比较的冒泡图,41从最底下经过6次冒泡之后,浮到了最顶上,这就是冒泡了。

那么如何用JASS实现呢?

分析:现在有105203579417个数,先将第7和第6个数比较,将较大数41调到前面去;接下来将第7个数与第5个数对调,将较大数41调到前面去……如此进行6次,就得到了41105203579这样的顺序,我们可以看到最大数41已经到了最顶峰。经过6次比较,已经得出最大数41了。然后再进行第二次冒泡。将剩下的6个数按照上面的方法再进行冒泡。经过5次比较,得到次大数35,依次类推,7个数要进行6趟冒泡,才能使这7个数按顺序排列,在第一趟的冒泡中,要进行6次比较,而在第二趟冒泡中要进行5次比较,……,第6趟则要进行1次比较。我们可以推导出,n个数,就要进行n-1趟冒泡,而在第1趟冒泡中要进行n-1次两两比较,那么在j趟中则要进行n-j次两两比较。

有了上面的分析,办起事情来就容易了。
+ Shingo Jass Highlighter 0.41 function FloatPop takes nothing returns nothing
local integer array int
local integer tempInt
local integer i = 6
local integer j = 0
//先给int数组赋值
set int[0] = 10
set int[1]= 5
set int[2] = 20
set int[3] = 35
set int[4] = 7
set int[5] = 9
set int[6] = 41

loop
set j = j + 1
exitwhen j > 6
loop
exitwhen i < j
if int > int[i - 1]
then
set tempInt = int

set int = int[i - 1]
set int[i -1] = tempInt
endif
set i = i – 1
endloop
set i = 6
endloop

endfunction


2.单位

War3中,BLZ给我们定义了一个好的数据类型——单位组。单位组的概念就类似于组队:将符合某种条件的单位放在一起,作为一个分组。而这里我们要讲的是单位数组,这个与单位组有所不同,也略有相同,但是要明确的是:这两个东西不是一回事。


类别

名称
相同

不同

单位组

1.成员相同:都是由一个个单位组成;
2.都可以从中取出某个单位;

1.数据类型为:单位组
2.单位组是一个整体
单位数组

1.数据类型为:单位
2.单位数组依旧是一个一个的单位


单位数组,其实就是N个变量。好处就是不用重复定义变量,节约资源。如下例:

例:我们假定玩家1,有四个英雄,我们现在要分别找出这四个英雄:让第一个
雄去A点打怪,让第二个英雄去B点打怪,让第三个和第四个英雄分别去C
D点打怪;然后分别再做点其他的事情。
分析:我们该怎么去做呢?首先,选取玩家1的所有单位并添加到一个单位组;
然后将这个单位组中的英雄类别的单位分别将这赋予一个单位数组的各个元素;接
下来,将这个四个英雄分别命令攻击到ABCD四个点;这件事之后再去做点其他
的事情。这样下来,单用单位组是无法完成的。单位组只能统一命令,统一行动。
总之由于单位组是一个整体,它无法做到像数组那样方便,便于单一分配任务。从
这层意义上讲数组要灵活得多。
本例主要是讲如何去运用单位数组,但是其中也会用到其他的数组,目的是想大家
能够灵活地运用数组这一结构体。
+ Shingo Jass Highlighter 0.41 function DosthElse takes unit u returns nothing
call IssueImmediateOrder(u,”stop”)
endfunction
function OrderDo takes nothing returns nothing
local unit array ArrayUnit
local unit U
local location array ArrayLoc
local group UnitGroup
localintegeri= 0
//选取玩家1的所有单位进单位组UnitGroup
setUnitGroup = GetUnitsOfPlayerAll(Player(0))
//点数组赋值
setArrayLoc[0] = A
setArrayLoc[1] = B
setArrayLoc[2] = C
setArrayLoc[3] = D
//单位数组赋值
loop
set U = FirstOfGroup(UnitGroup)
exitwhen UnitGroup == null
if U != null and IsUnitType(U, UNIT_TYPE_HERO) == true then
set ArrayUnit = U
set i = i + 1
endif
call GroupRemoveUnit(UnitGroup,U)
endloop
//命令这四个英雄分别攻击移动ABCD这四点
set i = 0
loop
exitwhen i == 4
call IssuePointOrderLoc(ArrayUnit,”attack”,ArrayLoc)
set i = i + 1
endloop
………………………………………………………………
………………………………………………………………
//英雄将ABCD这四个点的所有怪都杀掉了,再命令他们做以下动作:
set i = 0
loop
exitwhen i == 4
call DosthElse(ArrayUnit)
set i = i + 1
endloop

//所有动作都完成之后,该做的事情就是排泄。
//销毁并清空单位组
call DestroyGroup(UnitGroup)
set UnitGroup = null
//清空单位数组,清除及清空点
set i = 0
loop
exitwhen i == 4
call RemoveLocation(ArrayLoc)
set ArrayLoc = null
set ArrayUnit = null
set i = i + 1
endloop
set U = null
endfunction



如此一来,这个例题就完成了。这一例中运用了单位数组,点数组,单位组,循环。不知道你学习得怎么样了呢?那么数组就到此告一段落。

无界の灵
 楼主| 发表于 2011-9-16 21:57:13
本帖最后由 无界の灵 于 2011-9-16 22:13 编辑

第六章
函数


在第四章中,我们初步地接触了函数这么一个概念。对于你来讲,它已经对你不陌生了。在这一章里面我们将进一步地学习函数,不仅包括自己写函数,也包括一些BJCJ的库函数。用以加深大家的理解与运用能力。

对于函数,我们可以从用户角度和参数形式来进行分类:

从用户角度看:
1.
标准函数,也就是库函数。JASS中的库函数应该只有CJ,也就是common.j中的函数,至于BJblizzad.j)那是BLZ为了GUI用户而专门编写的封装好了的一个“伪”库函数。但是无论是BJ还是CJ,我们都可以直接拿来用,而不需要另外再写。但是你要记住BJ库只是BLZ按照他的需求写的,有时候并不能满足我们的需要,因此更多时候还是要自己写的。
2.
用户自己定义的函数。也就是你跟据你自己的需要所写的满足特定需要的函数。

从参数形式上来看:
1.
有参函数,在调用函数的时候,在主函数与被调用函数之间有数据的传递。也就是说,主函数可以将数据传递给被调用函数使用,被调用函数中的数据也可以带回来给主函数使用。
2.
无参数函数。这种函数没有参数列表,就像上例中的OrderDo()这个函数,就是一个无参函数。具体表现就是takes nothing returns nothing一类的。
那么在开始接下来的内容之前,大家先去看看第四章中关于函数的定义,调用等内容。这样有利于我们进行新的内容的讲解。

函数参数与函数值

1.函数参数

在调用函数时,大多数情况下,主调函数与被调函数之前存在着数据传递关系。这就是前面提到的有参函数。

那么,在定义函数的时候,写在takes后面的称作形式参数(简称为“形参”),在主调函数中调用一个函数的时候,被调函数后面括号里面的参数(可以是变量,也可以是表达式),被称为“实际参数”(简称“实参”)。Eg1.

+  Shingo Jass Highlighter 0.41 function Max takes integer i,integer j,integer k returns integer
local integer tempInt
if i >= j then
set tempInt = i
else
set tempInt = j

endif
if tempInt <= k then
set tempInt = k
endif
return tempInt
endfunction


以上函数功能是从三个整数中选取最大的,并返回这个最大数。那么在这个函数中什么是形参呢?

“形参是在函数定义的时候,写在takes后面的”,那么很显然 i,j,k这三个都是形式参数。所谓形式参数,就是在这些参数未被表示成对应的数据的时候,只是一个样子摆在那里,就像是衣服店里面那些穿着衣服的Model一样。他是告诉你,这个函数起作用的时候是需要传递如同i,j,k这三个数一样的数据类型的数据的。Eg2.


+  Shingo Jass Highlighter 0.41 function Caculator takes nothing returns nothing
local integer a = 5
local integer b = 25
local integer c = 21
local integer d
set d = a * Max(a,b,c) + b + c
endfunction



以上函数是完成一个小小计算题目:从abc这三个数中选出最大数,然后拿这
个最大数与a的乘积,再加上b加上c的和。在这里我们调用了上面的Max()函数。在这里,Caculator()就是一个主调函数,Max()则是一个被调函数。在这里Max(i,j,k)的三个形式参数i,j,k被赋予了实际值,分别是a,b,c这个时候,a,b,c就是Max()的实际参数,而Max(a,b,c)这个则有一个结果,就是25,很显然,Caculator最后的d的结果就是5*25+21+25 = 125+46 = 171在这里要注意,实参可以是常量,可以是变量,还可以是表达式,也可以是一个有返回值的函数。
2.形参与实参的区别:

形参只是在定义的时候的一种数据类别声明,在内存中并没有一席之地
只有当实参将数据传递过去之后才会在内存中占有空间。因此,这二者是“”与“”的差别。
JASS中,参数的传递,是单向传递——只能是由实参传递给形参;双向传递——不仅能由实参传向形参,还能由形参影响实参。

比如:
X
--------
à
a
Y
-----------
à
b
假定XY是实参,ab为形参。当XY分别将其实际值传递给ab之后,ab的实际值就变成了XY的值,如若ab再经过一系列的演算之后,a的值变成了X+5b的值变成了Y+7,但是XY的值不会因ab的值的变化而变化,它们依旧是XY这就是传说中的单向传递;以上数值计算方面是单向传递,而如果实参传递的是一个handle型数据,则ab在经过一系列动作之后将会变成另外一个形像,而此时的XY也随之而变化,这就是传说中的双向传递。比如,XY是两个单位,通过某自杀函数传递给ab,运行自杀函数之后,ab已死,很显然,XY也死了。因为XYab所指是都是那两个单位,这就像是一个人有两个名字一样,人死了,无论从哪个名字来喊,那个人都不会回应的。
3.
函数的返回值
在很多时候我们调用一个函数是希望这个函数能完成一定的工作,比如计算出一个值,获取某个单位,等等。这就是函数的返回值了。在上例中的Max(a,b,c)的返回值就是25。在JASS中最明显的有返回值的函数的标志就是在声明的后面有一个returns 数据类型这表示,这个函数会返回一个指定数据类型的数据。如上例中Max()就会返回一个integer类型的数值。而在我们通常的CJ中也有GetTriggerUnit()这个函数是返回一个单位,这个单位就是触发单位;我们做技能,很多技能都是需要一个目标的,那么如何获取这个技能施放目标单位呢?JASS里面也是有这样的函数的,GetSpellTargetUnit()就表示技能施放目标函数了,同样是获取一个单位类型的数据-----技能施放目标单位。还有很多这一类函数,这需要读者自己去总结去摸索。
切记:如果你的函数在声明的时候表示出将要返回一个数据,那么千万不要忘记在函数主体末尾加上return + 你想要返回的数据而这个你想要返回的数据数据类型必须与你声明时候所表示的数据类型一致,否则WE将崩溃。

有时候,我们并不是希望每个函数都返回一个值,这个时候为了表示这个函数没有返回值,我们就在声明的时候用 returns nothing表示该函数不返回任何值,做完动作就完了。


认识一些库函数

有些人说,“天啊,那么多CJBJ函数,我怎么能记得住?”实事上,这些东西没有记的必要,我一般都是用工具查询,我们只要记住一些常用的就可以了。

例如:触发单位
GetTriggerUnit()
;技能施放目标
GetSpellTargetUnit()


其实熟悉这些函数,主要是通过自己多看CJBJ这两个库函数文件。看多了自然也熟悉了,就算你不记得,只要你有印像,用工具查起来也是得心应手的。

那么现在,我就带着大家来认识一些我们常见的库函数吧。

创建单位(坐标)

native CreateUnit takes player id,integer unitid,real x,real y,real face returns unit

这个函数表示,在坐标(x,y)处为玩家id创建一个面向faceunitid类型的单位,单位

创建单位(点)

native CreateUnitAtLoc takes player id,integer unitid,location whichLocation,real face returns unit


这个函数表示,在点whichLocation处为玩家id创建一个面向faceunitid类型的单位,单位

触发单位

constant native GetTriggerUnit takes nothing returns unit


这个函数表示获取触发当前触发器的单位即触发单位

技能施放目标单位

constant native GetSpellTargetUnit takes nothing returns unit


这个函数表示获取单位施放技能的目标单位

施放技能单位

constant native GetSpellAbilityUnit takes nothing returns unit


这个函数表示获取当前施放技能的单位

出售单位(店主)

constant native GetSellingUnit takes nothing returns unit


这个函数表示获取卖东西或单位的单位,一般是响应购买,售出,物品或者单位事件

被出售单位(货物)

constant native GetSoldUnit takes nothing returns unit


这个函数表示获取被卖掉的单位,一般是响应购买,售出,物品或者单位事件

购买者(顾客)

constant native GetBuyingUnit takes nothing returns unit


这个函数表示获取买主,一般是响应购买,售出,物品或者单位事件



那么像这样的函数太多太多,我不可能一一讲解,还请大家在通常实践中慢慢熟悉。


Tips:很多时候,我们并不知道我们想要的是什么样的函数,那怎么办呢?用T来查看吧。比如我不知道贩卖者这个函数在JASS中如何表示的,那么我打开WE,用触发器来查找。先设定一个单位类型的变量,然后用T给这个变量赋值,最后找到贩卖者,确定下来,接下来将这个T转化为代码,于是什么是贩卖者一眼看出来。


小结:到此,如果你理解了前面所讲的1~6章的所有内容,那么恭喜你,你已经开始步入了JASS的殿堂。你已经学会了如何定义局部变量,全局变量;如何给变量赋值,以及变量的运用;还学会了动用循环,判断来进行语法控制;你已经会写自定义函数;会调用函数;你学会了数组的定义,及运用。而JASS最基本的东西也就这么一些。但是,要记住你还不是一名JASS高手。你只不过是一名高新手。你还须要学习,你还需要练习。



接下来的内容是JASS的精彩的地方,你只有完全掌握了并能熟练地运用以下内容中的知识,你才是一名JASS高手。

无界の灵
 楼主| 发表于 2011-9-16 21:57:39
本帖最后由 无界の灵 于 2011-9-16 22:14 编辑

                               JASS教程之提高篇
这部分就是由入门到提高的一个90度坡,如果你能顺利地渡过,你可以出师了。而这部分也将是本教程最精彩的部分,也将是你学习JASS最有挑战性的地方,也是检验你前面学习是否扎实的一个标准。如果你在学习这部分的时候有困惑的地方,不妨多看看前面的内容,也不妨多看看别人的演示,你还可以通过自己动手去加强自己的理解,要知道只有自己动手才能知道自己的不足,才能让自己进步。学习本身就无捷径可走,有的只有多练习,不断地加强自己对知识点的理解与运用。祝你们学习成功。

这部分我将讲解以下知识点:ReturnBugGameCacheTimer动态注册事件。在
你掌握了这部分的知识之后,你就可以出师了,剩下的就是你自己在做演示,向人求助,给
人帮助中去理解去运用了。




第七章ReturnBug初步接触

ReturnBug
,我想大家并不陌生,这个东西在你开始接触WE,在你开始着手用Trigger写你自己的演示的时候你可能就听其他人讲解过了。那么今天,我将带领你们去认识一下什么是ReturnBug

既然这个ReturnBug也被称作Bug那么就说明,BLZ在设计的时候没有想到会有这么一个东西错误出现。但是结果,很显然BLZ很得意于这个错误。也正是有了这个让WE变得如此有生机。

那么什么东西是ReturnBug呢?是怎么造成的呢?要回答这样的问题,首先还是要知道WE的检查机制。

WE
检查机制是从下而上,也就是先查后面,再查前面,当后前不一致的时候就会出错,严重的时候WE会崩溃。同时又由于WE中的return有其特殊性,这种特殊性表现在:在函数执行过程中在任意地方遇到return都将退出函数,不再执行return后面部分的东西。而WE在检查的时候,则只检查到从函数最后面起数的第一个returnendfunction的上一行),如果与声明部分对应,那么将不会报错,表示检查正常;某函数如果在声明的时候表示有返回值,而在函数体最后一行(endfunction的上一行)却没有return那么,将会报错。

那么我们先看看下面这个函数。

+  Shingo Jass Highlighter 0.41
function H2I takes handle h returns integer

return h

return 0

endfunction




不管哪种计算机语言,函数的返回值都只有一个,而在H2I()这里却有两个返回值,按常理,这应该算是错误的。但是由于WE的检查机制上的漏洞,导致这样的一个错误不认为是错误的。这样就实现了不同类型数据向整数的转换。我们先看看,从执行层面上看,函数执行到第一个return h的时候就执行完了,不会再执行return 0;而在检查的时候从后往前检查,因此检查到return 0并且发现与returns integer相对应于是就不会报错了。我们可以看到其实,这个H2I返回的依旧是handle h,只不过将这个句柄数字化了,因此其结果却是一个整数,不再是handle

正是由于这个Returnbug的出现,使得在JASS在不同数据类型之间的转换变得灵活。

我们通常用的ReturnBug函数有:
+  Shingo Jass Highlighter 0.41
function I2U takes integer i returns unit
return i
return null
endfunction

function I2G takes integer i returns group
return i
return null
endfunction

function I2T takes integer i returns timer
return i
returnnull
endfunction

function I2It takes integer i returns item
return i
return null
endfunction

function I2Ef takes integer i returns effect
return i
return null
endfunction

functionI2Li takes integer i returns lightning
return i
return null
endfunction




首先我们要明确一点,unit,group,item,timer,effect,lightning这些数据类型的扩展类别都是handle,以前我自己不明白这一点,一定都写 I2U--->U2I;I2G--->G2I;后来觉得这样确实过于麻烦。但是多看看CJ,就会发现,以上这些数据类型都是Handle类,也就是讲,都可以用H2I()这个函数将以上类弄的数据转换成一个整数。然后下面不同的由整数向其他具体数据类型的函数则是根据你自己的需要来写。但是,你不能将由转换一个unit单位得到的整数,再将之转换成一个物品;要一一对应,你只能将由物品转换得来的整数再转换成物品,而不能转换成单位,其他类型的也是一样。

下面请大家动动手,以加强你们对Returnbug的认识。

打开WE,新建一地图,在地图上随便放两个单位,我们命名为AB,创建两个整数型变量ab。将H2II2U这两个函数写进你的自定义代码区。做一个T事件就是每2秒事件。动作:set udg_a = H2I(udg_A);set udg_b = H2I(udg_B);call IssueTargetOrder(I2U udg_ (a),”attack”,I2U(udg_b));call IssueImmediateOrder(I2U(udg_a),”stop”)

你会看到(前提是AB隔得很近,能打到对方。),每隔二秒A就会打B一下。由此你就能看到returnbug能正常工作,并不是一个错误。值得高兴的是,BLZ没有修正returnbug的意思。
无界の灵
 楼主| 发表于 2011-9-16 21:58:17
第八章GameCache初步接触
Game Cache,也即游戏缓存,简称GC。
那么在WE中,这个游戏缓存是个什么样的角色呢?原本BLZ只是将缓存用来进行战
役之间的数据传递。它最初能存贮的数据类型只有5种:unit(单位)、integer(
整数)、real(实数)、string(字符串)、boolean(布尔)。“这也太少了吧
!”不错,确实少了。但是这个少归少,却已经够我们用的了。
我们先看看用于GC的存贮、读取、清除与同步不同类型数据的相关函数:
   
+  Shingo Jass Highlighter 0.41
存贮:
    native StoreString takes gamecache cache, string missionKey, string
key, string value returns boolean
  native StoreUnit takes gamecache cache, string missionKey, string
key, unit whichUnit returns boolean
  native StoreReal takes gamecache cache, string missionKey, string
key, real value returns nothing
  native StoreInteger takes gamecache cache, string missionKey, string
key, integer value returns nothing
  native StoreBoolean takes gamecache cache, string missionKey, string
key, boolean value returns nothing
    读取:
native GetStoredBoolean takes gamecache cache, string missionKey, string
key returns boolean
native GetStoredInteger takes gamecache cache, string missionKey, string
key returns integer
native GetStoredReal takes gamecache cache, string missionKey, string
key returns real
native GetStoredString takes gamecache cache, string missionKey, string
key returns string
native RestoreUnit takes gamecache cache, string missionKey, string key,
player forWhichPlayer, real x, real y, real facing returns unit
清除
native FlushGameCache takes gamecache cache returns nothing
native FlushStoredMission takes gamecache cache, string missionKey
returns nothing
native FlushStoredBoolean takes gamecache cache, string missionKey,
string key returns nothing  
native FlushStoredUnit takes gamecache cache, string missionKey, string
key returns nothing  
native FlushStoredInteger takes gamecache cache, string missionKey,
string key returns nothing
native FlushStoredReal takes gamecache cache, string missionKey, string
key returns nothing  
native FlushStoredString takes gamecache cache, string missionKey,
string key returns nothing
同步
native SyncStoredBoolean takes gamecache cache, string missionKey,
string key returns nothing  
native SyncStoredInteger takes gamecache cache, string missionKey,
string key returns nothing  
native SyncStoredReal takes gamecache cache, string missionKey, string
key returns nothing
native SyncStoredString takes gamecache cache, string missionKey, string
key returns nothing  
native SyncStoredUnit takes gamecache cache, string missionKey, string
key returns nothing

看到了吧,存贮的数据类型少,但是相关函数却不少呀。我们常用的就只有前三类
,即:存贮,读取,清除。大体上讲这类函数的通用格式是这样的:
存贮
GC存贮数据类型函数名(某缓存,类别名,项目名,值)
读取
GC读取数据类型函数名(某缓存,类别名,项目名)
返回
对应值的类型的数据
清除
GC清除整个缓存函数名(某缓存)
GC清除缓存类别函数名(某缓存,类别名)
GC清除缓存项目函数名(某缓存,类别名,项目名)
就这样,我们只需要记忆以上这种格式,就记住了以上这么多函数。
有人说这个很像Windows的文件夹系统,的确很像;但是我却更愿意将这个比作是
一个表格,说具体点吧,就比作是一个Excel表格吧:缓存-----表格名;类别名-
----某横行的数据名;项目名-----某竖例的数据名。这样一个横例加上一个竖例
就确定了一个格子,那么这个格子中的数据就是我们要的,或者就是我们要存数据
的地方。如下示意图:   下载 (21.02 KB)
2009-2-8 14:41
上面这个图我们就将这个表格的名字称作是缓存名;项目名就是各竖列的名;类别
名就是各行的名称;这样不同类别中会有就有多个项目,各个项目将会对应地存在
一个数据。但是缓存中的每一个空格中可以同时存贮
unit,integer,real,Boolean,string这5个类型数据各一个,老死不相干扰。比如

+  Shingo Jass Highlighter 0.41
call
StoreInteger(udg_GC,"A","A",123)  
call
StoreBoolean(udg_GC,"A","A",false)
call
StoreUnit(udg_GC,”A”,”A”,u)
call
StoreString(udg_GC,"A","A","ABC")
call
StoreInteger(udg_GC,"A","A",234)
以上前四个所存贮的四个不同数据,可以并存,但是如果你后面再存进来一个新的
整数型数据,这个新的将会覆盖原来的已存贮的整数型数据,其他的也是如此。
很多JASS高手把缓存比作一个仓库,什么有用就把什么装进去,在需要的时候再从
里面拿出来,这个比喻再贴切不过了。但是若仅仅是缓存单枪匹马是杀不出如此好
的口碑的。它之所以能有如此好的口碑,全杖在它与ReturnBug结合起来才导致的
。那么当GameCache与ReturnBug结合之后,所起的作用远远超出了1+1=2的效果,
应该是远远大于2。
之前,介绍的那么多与缓存相关的函数,很多时候不那样写,也不用那么多。例如
,通常我都这样写:
+  Shingo Jass Highlighter 0.41
//缓存创建
function game takes nothing returns gamecache  
    if bj_lastCreatedGameCache==null then  
        call FlushGameCache(InitGameCache("Alex.w3v"))  
        set bj_lastCreatedGameCache=InitGameCache("Alex.w3v")
    endif
    return bj_lastCreatedGameCache
endfunction
//存贮、读取与清除
//存贮实数与某handle挂钩
function RealHandleStore takes real i,handle project,string node returns
nothing
    call StoreReal(game(),I2S(H2I(project)),node,i)
endfunction
//存贮整数与某handle挂钩
function IntegHandleStore takes integer i,handle project,string node
returns nothing
    call StoreInteger(game(),I2S(H2I(project)),node,i)
endfunction
//读取与某handle挂钩的整数
function IntegHandleRead takes handle project,string node returns
integer
    return GetStoredInteger(game(),I2S(H2I(project)),node)
endfunction
//读取与某handle挂钩的实数
function RealHandleRead takes handle project,string node returns real
    return GetStoredReal(game(),I2S(H2I(project)),node)
endfunction
//清除与某handle挂钩的缓存类数据
function FlushData takes handle project returns nothing
    call FlushStoredMission(game(),I2S(H2I(project)))
endfunction
//清除缓存
function FlushCache takes nothing returns nothing
    call FlushGameCache(game())
endfunction

当缓存与ReturnBug结合起来的时候,我们只要上面这几个函数便足够了。


无界の灵
 楼主| 发表于 2011-9-16 21:58:33
第九章 Timer初步接触
    皇天不负有心,终于到了Timer了。
    关于Timer,表现于T上就是:时间 - 游戏开始x 秒;时间 -计时器到期;时间 – 每x秒触发事件 这三个事件。但是函数却只有5个:
+  Shingo Jass Highlighter 0.41 native TriggerRegisterTimerExpireEvent takes trigger whichTrigger,timer t returns event
//以上是为某触发注册计时器到期事件
function TriggerRegisterTimerEventPeriodic takes trigger trig,real timeout returns event
    return TriggerRegisterTimerEvent(trig, timeout, true)
endfunction
//以上是为某触发器注册计时器周期事件
native TriggerRegisterTimerEvent takes trigger whichTrigger,real timeout,boolean periodic returns event
//以上是为某触发器注册计时器事件(可以是周期的,也可以是一次性的)
native TimerStart takes timer whichTimer,real timeout,boolean periodic,code handlerFunc returns nothing
//以上:开启计时器(某计时器,开始计时周期,是否周期性计时,运行某函数)
native GetExpiredTimer takes nothing returns timer
//以上:获取一个到期计时器


而前三个都是注册记时器事件的,当我们看了后两个,会不会发现前面的三个都能由后二个演变出来?什么?不明白?且看我讲解:
第一个计时器到期事件:---------->能够获取一个到期计时器
第二个是第三个的演变版。
第三个是否与四个相似?的确,这种相似性我们完全可以用下面的来替代。计时器事件,我们可以用开启计时器来替代,计时器到期事件,我可以用获取一个到期计时器来替代。而注册事件,比单纯的运行一个函数要慢得多。因此我们完全可以抛弃原有的注册计时器事件,改而写开启计时器。

我们以实例说话吧。
比如:我们想每2秒就在显示器上显示:“哈皮牛益儿!”。
在T的形式下我们一般会这样写:
+  Shingo Jass Highlighter 0.41 fucntion TrgAction takes nothing returns nothing
    call BJDebugMsg(“哈皮牛益儿!”)
endfunction
function  ShowTrg takes nothing returns nothing
    set gg_trg_ShowTrg = CreateTrigger()
    call TriggerRegisterTimerEvent(gg_trg_ShowTrg,2.0,true)
    call TriggerAddAction(gg_trg_ShowTrg,function TrgAction)
endfunction



当上面这个Trigger运行之后,每2秒就会在你的显示器上出现“哈皮牛益儿!”了。
我们可以看到,上面这个触发器先是创建一个触发,然后为这个触发注册每2秒事件,接下来再为这个触发添加动作。然而,这既不利于我们绑定数据,速度又不快。那怎么样做才有利于我们呢?看下面的。
+  Shingo Jass Highlighter 0.41 function SayHello takes nothing returns nothing
    call BJDebugMsg(“哈皮牛益儿!”)
endfunction
function ShowAction takes nothing returns nothing
    local timer    tm = CreateTimer()
    call TimerStart(tm,2.0,true,function SayHello)
    set tm = null
endfunction



看,多么简单直接,多么明了,相比之下,不仅能让我们少写很多字,更重要的是容易清理,在运行速度上也较前者快很多。有人会问,为何我老是说前者运行速度低,而后者快,其实这只要你熟悉触发的运行原理,很容易理解。因为触发器要调用多个函数,而Timer只需调用一个函数,单从这层上看,后者也比前者快。总之呢,我们学习到这里就要学以致用,在以后书写中尽量用TimerStart替代TriggerRegisterTimerEvent。

通常情况下,我们要的不是TimerStart的高效,我们要的是它的灵活。这个配合前面学习的缓存,returnbug,进行数据的绑定相当方便,相当灵活。但是如果用TriggerRegisterTimerEvent就没有这么方便了,不是吗?作为一个触发器,只能有一个H2I的标识码,而作为一个局部变量的Timer却能拥有无穷个H2I标识码,这在多变量冲突时代显然是一枝独秀,惹人眼红。
那么综上所讲,在JASS中,真正能用的Timer函数只有两个:
+  Shingo Jass Highlighter 0.41 native TimerStart takes timer whichTimer,real timeout,boolean periodic,code handlerFunc returns nothing
native GetExpiredTimer takes nothing returns timer


你只要能熟练运用这两个函数,可以说Timer你已经学纯熟了。

GameCache,ReturnBug,TimerStart 这三个被人称作是JASS三剑客。这三者的结合,则往往能起到令人吃惊的作用,远远超出了1+1+1 = 3的意念。当你学会了这三者,那么你已经是一名JASS高手了。那么接下来的内容就是将这三者熔为一体的讲解与实例训练了。

Timer的唯一不足之处:就是TimerStart takes timer whichTimer,real timeout,boolean periodic,code handlerFunc returns nothing 这后面的 code handlerFunc这里的HandlerFunc只能是一个无参数,无返回值的函数。但是由于三剑客的存在,让这一不足早已消失得无影无踪。

无界の灵
 楼主| 发表于 2011-9-16 22:33:36
第十章 三剑客“三贱合体”实用技能之一
    “三贱合体”这么牛X的技能,也只有GameCache,ReturnBug,Timer这三者在一起的时候才发动的“秒杀一切”的技能,天下除它们三者之外没有人会,这项绝技也不外传,BLZ祖训:“三贱合体”不传女,也不传男。正因为它谁也不传,于是谁也学不会,那么我们只好花钱雇它们为我们办事喽。
    我们言归正传。在讲这个大招之前,我想引用一下Everguo的教程中一个名词——数据绑定。那么什么是数据绑定呢?数据绑定就是把数据用Return bug+GameCache把数据存储到缓存中“XXX”的类别名下(这里的类别“XXX”,大都是通过I2S(H2I())将触发器、单位或计时器转换得来的),然后再按第八章的方法从缓存中读取出来。
    是的,也许你还不知道,GameCache,ReturnBug,Timer三者在一起是“三贱合体”,而前二者在一起就变成了“人贱合一”。而这两者的独门绝学是不会让Timer这个第三者所知的。因此在没有Timer的时候,他们就将数据绑定在触发器或单位上,但是谁也不知道触发器和单位是有一定的不足,所谓“人贱合一”只是用来打打小怪,吓唬吓唬BOSS用的。而想真正发挥他们二人的作用,还是要找到第三者——Timer发动“三贱合体”的。

第一节    人贱合一
    在本节之前,大家已经了解了什么是数据绑定。那么我们就先来看看,三剑客之二的GC与RB的人贱合一。
    这二者由于没有Timer的搅和,它们倒觉得二人世界真是很宁静,很惬意——就算同渡这二人世界的是两个男的或者是两个女的。因为他们是二人世界,所以我们也不知道他们在一起都做了一些什么,一切都是由人去想的。但是他们在一起所起的作用都是我们看得见的。为了熟悉他们有哪些能力,我们先来练练手吧。
首先我们准备好需要的函数:
+  Shingo Jass Highlighter 0.41 function  H2I  takes handle h returns integer
    return h
    return 0
endfunction
function  I2U takes integer i returns unit
                    return   i
                    return   null
endfunction
function I2G takes integer i returns group
return i
return null
endfunction
function  I2T takes integer i returns timer
return   i
return   null
endfunction
function  I2It takes integer i returns item
return i
return null
endfunction
function  I2Ef takes integer i returns effect
return i
return null
endfunction
function I2Li takes integer i returns lightning
return i
return null
endfunction
function game takes nothing returns gamecache  
    if bj_lastCreatedGameCache==null then  
        call FlushGameCache(InitGameCache("Alex.w3v"))  
        set bj_lastCreatedGameCache=InitGameCache("Alex.w3v")
    endif
    return bj_lastCreatedGameCache
endfunction
function RealHandleStore takes real i,handle project,string node returns nothing
    call StoreReal(game(),I2S(H2I(project)),node,i)
endfunction  
function IntegHandleStore takes integer i,handle project,string node returns nothing
    call StoreInteger(game(),I2S(H2I(project)),node,i)
endfunction  
function IntegHandleRead takes handle project,string node returns integer
    return GetStoredInteger(game(),I2S(H2I(project)),node)
endfunction  
function RealHandleRead takes handle project,string node returns real
    return GetStoredReal(game(),I2S(H2I(project)),node)
endfunction  
function FlushData takes handle project returns nothing
    call FlushStoredMission(game(),I2S(H2I(project)))
endfunction  
function FlushCache takes nothing returns nothing
    call FlushGameCache(game())
endfunction


eg.1:将英雄A的三围存入到一个触发器TrgHero类别的缓存中去;然后再读取出三围,分别赋值给AGI,STR,INT这三个变量。
分析:首先我们要明确的是,英雄三围是一个什么类型的数值。经过查询,我们得到,三围是属于整数型数据。因此我们可以用在第八章中最后所讲的函数了,也就是上面所准备好的函数了。
第一步:保存三围
+  Shingo Jass Highlighter 0.41 call IntegHandleStore(GetHeroAgi(A),TrgHero,”agi”)  //敏捷
call IntegHandleStore(GetHeroStr(A),TrgHero,”str”)   //力量
call IntegHandleStore(GetHeroInt(A),TrgHero,”int”)   //智力


第二步:读取并赋值
+  Shingo Jass Highlighter 0.41 set AGI = IntegHandleRead(TrgHero,”agi”)
set STR = IntegHandleRead(TrgHero,”str”)
set INT = IntegHandleRead(TrgHero,”int”)


第三步:清除缓存,由于在其他的动作中我们还需要这个缓存,故我们不用删除整个缓存,我们只需要清除对于我们来说已经没有用处的缓存类别中的数据即可。
+  Shingo Jass Highlighter 0.41 call FlushData(TrgHero) //如此一来,我们就把刚才在缓存中的TrgHero这个类别的所有数据全部清除了。



从上例中细心的你是否已经发现最后一步——清除缓存与以前所听说过的排泄很相近?的确,这也是排泄。要记住,凡是我们不需要再用了的handle,code类型数据都是需要进行排泄的,而保存在缓存中的数据,只要我们不需要再用了,也应该进行清除。以维持你地图的完美性与和谐性——这个世界是一个和谐的世界,千万别因为你一时的大意,而破坏了这样的一个美好的大环境。

eg.2:将单位K,保存进以单位M为类别的缓存中去,然后再读取出来赋值给单位类型变量L,命令其攻击M。
分析:同上例一样,单位K能转化为什么样类型的数据?从ReturnBug里面我们可以找到一个H2I函数,而单位其实也是一个handle类型数据,因此我们可以将单位K转化成一个整数。这样就好说了。  +  Shingo Jass Highlighter 0.41
第一步:保存单位
call IntegHandleStore(H2I(K), M,”””””K”)
第二步:读取并赋值,L为一个单位型变量,而我们读取的是一个整数,所以我们可以通过I2U进行转换。
set L = I2U(IntegHandleRead(M,”K”))
第三步:命令L攻击M
call IssueTargetOrder(L,””attack”,M)
第四步:清除缓存,排泄。
call FlushData(M)
set L = null


一般来说,“人贱合一”往往需要将数据绑定在一个全局变量身上,然后通过个全局变量来进行读取,从而达到在别处运用已存贮数据。我们进行数据的绑定,需要绑定在一个能够确定下来的handle值上。所以在前面我说,这招只能打打小怪,吓吓BOSS,也就是说这招很有局限。如果你找不到这样的一个随时可以确定下来的handle就无法进行数据绑定。
快速回复 返回顶部 返回列表