达真堪布:《魔兽争霸III》Jass游戏脚本语言速成教程

来源:百度文库 编辑:九乡新闻网 时间:2024/05/05 06:37:01

《魔兽争霸III》Jass游戏脚本语言速成教程

2007-02-12 09:48第一章 Jass语言简介

1.1 Jass语言的用处
Jass(正确地说是Jass2)是魔兽3的脚本语言, 用于控制地图的进程和行为, 是魔兽游戏和地图的基础. 正常的地图编辑中摆放的单位(Unit), 设置的触发(Trigger)等最终都会被翻译成Jass语言存在地图文件里在游戏时被调用.

1.2 为什么要学习Jass语言
a)少量功能用触发(Trigger)不能完成, 必须用Jass来实现, 比如一些内存泄漏, 一些特殊功能
b)在实现一些功能时使用Jass可以比触发(Trigger)写得更简洁高效快速

1.3 一定要学习Jass吗?
答案是否, 只要学习触发(Trigger)就能完成做地图的绝大多数功能, 只需记忆少量Jass特别的语句辅助即可(主要用于防止内存泄漏)

1.4 Jass和其它语言的比较
Jass的语法比较接近BASIC, 但引入了一些C的优秀的结构, 不过语法比这两者都要简单得多, 不需要任何语言基础就可以轻松学习

1.5 本教程的定义/代码/例子表示方法:红色部分


第二章 预备知识

2.1 Jass语言的源代码
Jass语言的扩展类型定义, 基本函数和常量取值都是直接调用游戏的函数, 他们被存放在war3x.mpq和war3patch.mpq内的Scripts\common.j和Scripts\common.ai中
Jass还有一些扩展函数, 放在war3x.mpq和war3patch.mpq内的Scripts\blizzard.j和Scripts\common.ai中
common.ai包含了用于设计ai的大量内部函数和扩展函数, 但对于我们只用Jass进行普通脚本编写的人来说可以忽略这个文件内的代码
其中war3patch.mpq里的文件是打了升级补丁后的最新版本, 可以使用mpq工具提取, 如果不懂也没有关系, 我们提供了提取好的1.15的Jass源代码文件压缩包.

2.2 Jass语言中的注释(comment)
任何写在//后面的都是注释内容, 这也是Jass唯一的注释语法, 后面的例子会多处用到这个注释符号, 这个符号和后面的注释只是用于解释一些东西, 大家在看完一些例子后自己编写Jass代码时完全可以去掉

2.3 Jass里的数据类型(data type)
基本数据类型有:
integer 整数型, 相当于C语言里的int, 取值范围为 -2147483648到2147483647
可以有4种形式的方法来写整数型的值:
1)全部为数字, 第一个数字不为0, 这是10进制表示方法, 如: 23
2)全部为数字, 第一个数字为0, 这是8进制表示方法, 如: 023(相当于19)
3)0x(x可以是大写)开头, 后面全部为数字(16进制的数字包含A,B,C,D,E,F, 分别表示10,11,12,13,14,15, 可以为小写), 这是16进制表示方法, 如: 0x23(相当于35)
4)用单引号括起4个字母, 表示单位/技能编号对应的整数, 如'Hpea'
real 实数型, 相当于C语言里的float, 取值范围为 1.5 x 10^(-45) .. 3.4 x 10^38
boolean 布尔型, 只有真假两个值, 用于表示一个判断式是否正确, 分别用true和false来标示
string 字符串, 用双引号括起的若干个字母, 空字符串表示为""
字符串中遇到"可以用\"来表示这是一个字符串中的引号
如: "abc\"def\"", 就表示字符串abc"def"
handle 句柄型, 相当于C中的指针类型, 它指向内存的一个地址, 用于表示一个较大的结构, 是很多数据类型的基类型
code 代码型, 用于表示一个函数地址, 通常用于传值时传递一个函数, 其值的形式为function <函数名>
如: function codeexample
注意: 这个值表示的函数必须在前面定义过, 关于函数, 见3.3

除了基本数据类型, Jass还提供了大量扩展数据类型, 都是以handle为基类型, 我们先来看定义扩展数据类型的语法:


type <数据类型> extends <基类型>


我们在自己的脚本中除非有非常特殊的需要, 否则基本不会用到这个语法, 介绍这个语法是为了大家能更好地读common.j的源代码
common.j中扩展的类型如widget, player, trigger, ability, boolexpr等都是基于handle, 除了unit, destructable和item是基于widget, buff是基于ability, conditionfunc, filterfunc是基于boolexpr和几个非常罕用的类型, 通过类型名的英语含义可以清楚地了解这个类型表示的数据的类别

2.4 Jass里的表达式(expression)
表达式可以是直观可见的值, 也可以是函数, 计算式等, 如:


数值型/实数型: 1, 3, 5565.33, ........
字符串: "Xasfsfs", "Greedwind", ........
布尔型: true, false
函数: GetTriggeringUnit()
GetUnitLoc(GetTriggeringUnit())
计算式: (a + b) * c + d



计算式中可以存在的操作符有:

数学计算:
+, -, *, / 加, 减, 乘, 除(注意整数除法的结果仍然为整数, 是舍去小数点的结果)
比较符号(返回的是boolean布尔值):
>, <, >=, <=, ==, != 分别是大于,小于, 大于等于, 小于等于, 等于(注意不是=), 不等于
布尔值运算:
and, or, not 条件与, 或, 非
字符串运算:
+ 字符串相加, 如"abc" + "def"的结果"abcdef"

优先级大家不用记忆很多, 只要记住先*,/后+,-, 先数学运算再比较最后布尔值运算, 括号可以改变运算优先级到最高这3点就可以了


第三章 Jass是一种语法非常简单的语言

3.1 Jass语法概览
Jass语法一共只有以下5种: 变量控制(定义/赋值), 函数控制(定义/返回), 调用函数, 判断和循环. 每行写一个语句, 换行就是语句分隔符号, 可以说是非常的简单, 很快可以理解上手.

3.2 全局变量(Global variable)定义
Jass中的变量分为两类, 全局变量(Global variable)和本地变量(Local variable), 如果你只使用地图编辑器的触发(Trigger)中的自定义脚本(Custom script)进行Jass编写, 请跳过对全局变量语法的描述, 直接看全局变量在地图编辑器中的命名形式.
全局变量必须定义在一个Jass脚本文件的开始, 语法为:

globals
[constant] <变量类型> [array] <变量名> [= <初始值>]
...
endglobals

其中变量数据类型见2.3, [constant]为可选定义段, 写上表示这个变量被视为常量, 不允许改动变量的内容, 此时必须加上[= <初始值>]这个部分以对常量进行初始化, [array]为可选定义段, 不写表示这是一个普通变量, 加上则表示是这是个数组, [= <初始值>]也是可选定义段, 但只有在不是数组的时候可以使用, 定义了变量被初始化时的取值, 可以为一个表达式. 举例:

globals
int udg_i = 1 //整数型变量udg_i, 初始化为1
real udg_d = 1.0 + 2.5 //实数型变量udg_i, 初始化为3.5
constant string udg_s = "abc" //字符串变量udg_s, 初始化为"abc", 并且定义为常量, 不可进行修改
boolean udg_b //布尔型变量udg_b
int array udg_ia //整数型数组变量udg_ia
endglobals

我们在这段代码里定义了5个全局变量, 其中最后一个是数组.
全局变量可以在地图编辑器的触发(Trigger)部分直接进行定义, 地图编辑器内部转换为Jass后自动会在变量名前面加上udg_, 并把变量名中间的空格转换为下划线. 比如你定义了一个Unit类型的变量my hero, 那么当你要使用Jass访问这个全局变量时, 你必须使用变量名udg_my_hero
关于本地变量, 请看3.5

3.3 Jass的基本组成部分是函数(function)
Jass不是面向对象的(OOP), 是一种非常传统的结构化的语言(不懂的人可以忽略这一句). 除了全局变量的定义, Jass完全由函数组成.

3.4 函数的语法
Jass中定义一个函数的语法为:

function <函数名> takes <参数类型> <参数名>[, <参数类型> <参数名>[, ...]] returns <返回类型>
.... //函数主体
endfunction

其中 参数 可以为多个, 用逗号隔开, 用于传递参数给函数, 传递过去的参数一律被视为本地变量(参见3.5), <返回类型>可以写nothing, 表示不返回任何值.
然后介绍函数基本语法return, 它必须写在函数里, 格式为:

return [表达式]

当函数返回类型为nothing时, 后面不需要表达式, 否则必须写表达式, 它可以出现在函数的任何地方, 可以出现不止一次, 但是一旦碰到这一行, 函数立即返回值并跳出该函数的运行. 注意, return的值的类型必须和函数的返回类型相同或为返回类型的子类型(现在的Jass有解析bug, 只判断最后一个return返回的类型, 而且官方明确表示永远不会修正这个bug, 但是出于安全考虑, 最好保证所有return的值的类型都符合)



举例:

function example takes int i, real d returns integer
return i * 2 //返回i * 2
endfunction

在这里我们建立了一个函数example, 它有两个参数, 整数型的i和实数型的d, 返回一个整数型值, 这里要注意的是returns最后一定要写s, 要和基本语法return区分开来. 在这个例子里, 我们函数中只用到了参数i, 而参数的d没有用到过, 这是不提倡的做法, 如果一个参数没有被使用, 推荐在函数定义中去掉这个参数, 即改写为:

function example takes int i returns integer
return i * 2 //返回i * 2
endfunction

common.j/common.ai中内部函数的定义语法为:

native function <函数名> takes <参数类型> <参数名>[, <参数类型> <参数名>[, ...]] returns <返回类型>

这表示这个函数直接使用了魔兽游戏内部的函数, 所以没有过程也没有endfunction, 所以大家不能通过看代码来分析这些函数的作用, 但是大家在读的时候注意一下函数名和参数名的英语含义基本就能明白这些内部函数的作用

3.5 本地变量(Local variable)的定义
本地变量(Local variable)的定义必须写在function的开头, 所有非变量定义语句之前, 语法结构为:

local <变量类型> [array] <变量名> [= <初始值>]

开头必须用local, 后面的含义和全局变量定义相同, 例子见3.6, 注意函数参数也可以视作是本地变量, 本地变量只能在本函数内使用, 而全局变量则可以在任何函数里使用

3.6 变量(variable)的赋值和使用
变量赋值语句的语法为:

set <变量名> = <表达式>

这个语句把<表达式>计算结果赋值给<变量名>表示的变量, 结合3.4, 3.5, 3.6举例:

function plus takes integer a, integer b returns integer //一个加法函数
local integer c
set c = a + b
return c
endfunction

当然, 为了举例我们才这么写, 其实我们可以简写为:

function plus takes integer a, integer b returns integer //一个加法函数
local integer c = a + b
return c
endfunction

再进一步化简:

function plus takes integer a, integer b returns integer //一个加法函数
return a + b
endfunction

这样就能把本地变量去掉, 节省代码的长度和游戏运行时用的资源, 只不过在函数比较复杂的时候, 通常就无法简化掉本地变量的使用了
然后讲一下数组的使用, 数组变量使用中的写法为:

<变量名>[<索引>]

<索引>是一个数字, 表示数组的元素位置, 最小为0, Jass中的数组不需要定义大小就可以随便使用小于common.j中定义的JASS_MAX_ARRAY_SIZE(8192)的任何索引, 我们将plus函数再做变化来举例:

function plus takes integer a, integer b returns integer //一个加法函数
local integer array c
set c[0] = a
set c[1] = b
set c[2] = a + b
return c[2]
endfunction

这里我们用c[0], c[1]来储存a, b的值, 再把它们加起来赋值给c[2]


3.7 函数(function)的调用
函数调用的语法为:

call <函数名>(<变量列表>)

调用的函数一定要在前面定义过, 括号内为变量列表, 变量的数量和类型要和函数定义相符合, 用call调用的函数会忽略返回值, 通常用于需要函数的运行过程但不需要判断或使用其返回值的场合, 如我们调用上面的plus函数应该这样写:

call plus(1, 2)

对于有返回值的函数, 我们可以把它们写在表达式中, 这样就可以取得其返回值, 仍然以plus函数举例:

set i = plus(1, 2) * 2 //将(1 + 2) * 2 = 6赋值给本地变量i(注意前面要定义本地变量i)

3.8 判断(if)语句
判断语句的基本语法为:

if <表达式> then
... //代码段, 可以为多行
endif

<表达式>结果必须为布尔型, 因此通常用比较符号和布尔型运算进行复合运算, 当表达式结果为真时, 运行中间的代码段.
我们还可以判断语句在其中加入否则(else)和否则如果(elseif)的语句:

if <表达式> then
... //代码段, 可以为多行
elseif <表达式> then
... //代码段, 可以为多行
else
... //代码段, 可以为多行
endif

可以用多个elseif, else必须写在最后, 表示前面的条件都不符合时执行else后面的那段代码
例一:

//判断两个数的大小, 相等返回0, ab返回1
function compare takes integer a, integer b returns integer
if a > b then //如果a>b
return 1
elseif a < b then //否则如果areturn -1
else //否则(a不大于也不小于b, 即a == b)
return 0
endif
endfunction

例二, blizzard.j中的RMaxBJ函数(返回两个实数中大的一个):

function RMaxBJ takes real a, real b returns real
if (a < b) then
return b
else
return a
endif
endfunction

其实该函数可以化简为不使用else语句:

function RMaxBJ2 takes real a, real b returns real
if (a < b) then
return b //如果aendif
return a //如果a不小于b, 则返回a
endfunction

这个例子同时也很好地诠释了return的作用, 大家可以体会一下

3.9 循环(loop)语句
循环语句的语法为:

loop
... //代码段, 可以为多行
exitwhen <表达式>
... //代码段, 可以为多行
endloop

<表达式>表示跳出循环的条件, 结果必须为布尔型值, 当它为真时, 直接跳出循环运行循环下面的语句, 可以存在多条exitwhen语句, 表示多个跳出条件
例一:

function addfromone takes integer n returns integer //计算从1加到n
local integer i = 1 //循环中表示当前加到的数的变量
local integer s = 0 //表示和的变量
loop
exitwhen i > n //当i>n时跳出循环, 计算结束
set s = s + i //加上当前数
set i = i + 1 //把数加1
endloop
return s //返回计算结果
endfunction


例二(loop和if嵌套使用):

function addevenfrommton takes integer m, integer n returns integer //计算从m到n的所有偶数的和
local integer i = m //循环中表示当前加到的数的变量
local integer s = 0 //表示和的变量
loop
exitwhen i > n //当i>n时跳出循环, 计算结束
if ModuloInteger(i, 2) == 0 then //ModuloInteger是blizzard.j中的求余数函数, 如果余数为0即i为偶数, 将s加上当前数
set s = s + i
endif
set i = i + 1 //把数加1
endloop
return s //返回计算结果
endfunction

请注意loop和if嵌套使用时不要交叉, 比如:

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

这样写是不对的, loop在必须在endif前给出endloop才能形成正确的嵌套结构分享到搜狐微博