找回密码
 点一下
查看: 4405|回复: 19

【jass教程】史上最精简的教程,包你10分钟学会!

[复制链接]
发表于 2010-9-26 15:52:14 | 显示全部楼层 |阅读模式
BY落叶无痕,我只不过是转贴,我也是看完之后才会Jass的
───────────────────────────────────────
声明:有一定T基础的人完全可以在10分钟之内基本掌握该jass教程,夯实基础后要继续深入
        学习和研究请去高级讨论区查阅有关资料。摒弃传统教学的特点,直击重点、深入浅出,
        即使是毫无程序基础的人也能在很短的时间内迅速掌握。本教程不打算与其他语言挂钩,
        不论原因为何,基于让人们迅速掌握和理解该语言的初衷,因此采用最有效最通俗的手
        法去阐述该语言。关于jass是什么以及为什么要学习等内容,就不再赘述。
──────────────────────────────────

一、初识jass

1、概览(5秒)
关于jass,只要知道2个内容即可:变量函数
╔═══╦═══════════════════════
变量  存储数据,重点熟悉和掌握全局和局域变量   
╠═══╬═══════════════════════
函数  执行功能,了解函数的结构、生成和运行等   
╚═══╩═══════════════════════

2、详解(2分20秒)
(1)、变量和函数声明格式如下:
───────────────────────────
a、全局变量的声明:
globals
    变量类型 变量名 ( = 初始值 ) //括号表示不赋值亦可,下同
    变量类型 array 数组名 //数组不可直接赋值,需在函数内逐个赋值
endglobals

b、函数和局域变量的声明:
function 函数名 takes 参数列表 returns 返回值类型
    local 变量类型 变量名 ( = 初始值 )
    ......
    执行语句
    ......
    return 返回值
endfunction
───────────────────────────
(2)、全局与局域变量的区别:

全局任何函数皆可用,局域只作用于所声明的函数。不同函数的局域变量命名可相同,最好不要与全局
参数名相同。全局占用内存大,执行速率快,全局则之。触发中,属性变量诸如触发单位、循环整数A、
最后创建的单位、新建的单位组等均是全局变量。同样,变量编辑器(ctrl+B)中的变量亦是全局变量。

(3)、函数起步,掌握以下重点即可:

①、函数名唯一;
②、函数可以无参数和返回值,是为空函数;
③、参数列表为参数类型1 参数名1,参数类型2 参数名2,...,参数类型N 参数名N。逗号分隔即可;
④、返回值必唯一;
⑤、局域变量声明必在所有执行语句之前;
⑥、return必须写在函数末端,即endfunction上一行;
⑦、无返回值函数则无需写return;
⑧、注意returns和return及其后携带数据的区别,前者后接变量类型,后者接具体的数据;
⑨、数组不能做参数和返回值。

例:
───────────────────────────
globals
    integer int = 3 //声明一个整数型全局变量int并赋值为3

    real col //声明一个实数型全局变量col未赋值
    string array str //声明一个字符串数组str

    boolean array b = false //错误!数组不能直接赋值!
endglobals

local integer index = 19 //错误!局域变量声明怎能脱离函数?

function A takes nothing returns nothing
    local integer a = 1 //声明一个整数型局域变量a并赋值为1,下同
    local integer b = 2
    local integer c = a + b
    call BJDebugMsg(I2S(c)) //显示c的值,该句(带call)即为执行语句
endfunction

function B takes real r, integer e returns nothing
//接受一个实数型参数r和一个整数型参数e
    call BJDebugMsg(R2S(r)+I2S(e)) //显示r的值
endfunction

function C takes nothing returns real
//返回一个实数型变量
    return 2 * 3 * 15 //正确,必须拥有返回值
endfunction


function D takes nothing returns real, integer
//该函数错误!一个函数只能返回一个数据类型!
    return ...
endfunction

function E takes real r returns real
//该函数错误!有返回值类型,但函数结尾却无具体返回值!
endfunction

function F takes real r returns real
//正确,有返回值类型也有具体返回值
    return r * 33
endfunction

function G takes nothing returns nothing
    call BJDebugMsg(I2S(h))
    //错误!局部变量声明必须在所有执行语句之前!
    local integer h = 5

endfunction

function H takes integer array num returns nothing
    //错误!数组不能作为函数的参数!

endfunction
───────────────────────────

二、jass进阶(1分30秒)
(1)、函数定义
很多初学jass的朋友一直没有搞清楚该问题,很简单,函数就是能独立完成一件工作的机构。
如下例函数showtext,他能完成什么工作呢?或者说他有什么样的职能?注意,凡是在函数
之间的部分(function~endfunction)就是这个函数的职能或作用。那么,该函数的职能就是
打印一行信息“我在学jass”。
───────────────────────────
function showtext takes nothing returns nothing
call BJDebugMsg("我正在学jass") //显示“我正在学jass”

endfunction
───────────────────────────

(2)、生成和运行
函数是如何运行(调用)的?很简单,call 函数名(参数列表)即可,这里的参数列表的要求前面已经
说过。需要注意的是,call后面接的是一定函数,此类函数叫被调用函数。被调用函数一定要在调用
函数的前面,且传递状态下的参数列表要和被调用函数的参数列表的类型、次序和数量相一致。
───────────────────────────
function A takes integer i returns nothing
    call BJDebugMsg(I2S(i)) //显示31

endfunction

function B takes nothing returns nothing
call A(31) //正确,被调用函数A在函数B的前面。函数B将整数31传递给函数A
    call C(0.02) //错误!被调用函数C在函数B的后面!

endfunction

function C takes real rreturns nothing
call BJDebugMsg(R2S(r))

endfunction
───────────────────────────

可不可以让一个函数调用其后面的函数呢,可以的。使用ExecuteFunc("函数名")的检索功能,但要注意2点:
①、函数检索距离不宜过大,即2个函数之间的字节码有一定限制;
②、检索状态下,被调用的函数不能拥有参数,但可以有返回值;
③、在遵循②前提下,被检索的函数在检索函数的前后均可。
───────────────────────────
function A takes nothing returns nothing
endfunction

function B takes nothing returns nothing
call ExecuteFunc("A") //正确,被检索函数无参数,下同
    call ExecuteFunc("C")
    call ExecuteFunc("D") //错误!被检索函数D携带参数!

endfunction

function C takes nothingreturns integer
return 256

endfunction

function D takes integer  i returns nothing
    call BJDebugMsg(I2S(i))
endfunction
───────────────────────────

三、语法(1分30秒)
(1)、循环
──────────────────
loop
    exitwhen 条件 //满足条件退出循环
   执行语句
endloop
──────────────────

(2)、条件控制
──────────────────────
a、单层控制:
if 条件 then //如果满足条件1则做...

执行语句
endif

b、双层控制:
if 条件1 then //如果满足条件1则做...

执行语句
else 否则做...
   执行语句
endif

c、多层控制:
if 条件1 then //如果满足条件1则做...

执行语句
elseif 条件2 then //如果满足条件2则做...
   执行语句
elseif 条件3 then //如果满足条件3则做...
   执行语句
elseif 条件4 then //如果满足条件4则做...
    执行语句
...... //如果满足条件N则做...
endif
──────────────────────

(3)、逻辑关系符:
仅3个,and(且)、or(或)、not(非)。

(4)、比较和操作符:
仅9个,[](数组)、[]=(数组赋值)、=(赋值)、
==(等于)、!=(不等于)、>(大于)、<(小于)、
>=(大于或等于)、<=(小于或等于)

(5)、实例。若混合使用,注意嵌套先结束里层再结束外层:
───────────────────────────
function A takes integer&#160;&#160;i, integer&#160;&#160;jreturns nothing
if (i==3 and j==5 ) then //如果满足i=3且j=5则做显示他们的和
&#160; &#160;&#160; &#160; call BJDebugMsg(I2S(i+j))
else
call BJDebugMsg("条件不符!")//否则,不做显示“条件不符”的警告!
endif
endfunction

function B takes nothing returns nothing
local integer i = 1
&#160; &#160;local integer array index&#160; &#160;
&#160; &#160; loop
&#160; &#160;&#160; &#160;&#160; &#160;exitwhen i > 7 //当i>7时退出循环
&#160; &#160;&#160; &#160; set index = 25 * i
&#160; &#160;&#160; &#160; set i = i + 1
&#160; &#160; endloop
endfunction

function C takes nothingreturns integer
&#160; &#160; local integer i = 1
&#160; &#160; local integer array index&#160; &#160;
&#160; &#160; loop
&#160; &#160;&#160; &#160;&#160;&#160;exitwhen i > 7
&#160; &#160;&#160; &#160;&#160;&#160;set index = 25 * i
&#160; &#160;&#160; &#160; if (i==2 or i==6 ) then
&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160; call BJDebugMsg(I2S(i))
&#160; &#160;&#160; &#160;&#160;&#160;endif //endif嵌套于循环内,因此应该先结束里层
&#160; &#160;&#160; &#160;&#160;&#160;set i = i + 1

&#160; &#160; endloop //再结束外层
endfunction
───────────────────────────

四、函数间的数据传递(4分)
(1)、利用全局数组传递
这涉及内容较多,暂不说明。

(2)、1.20的Gamechche(游戏缓存)+Return Bug(类型强制转换)
我们知道,要实现一个技能多人运行,可以用全局数组。当然可以利用局域变量,因为局域变量
是互不干扰的、稳定的,且并行运行的。当然,我们可以通过传递参数传递局域变量的值,然而,
对一些不允许携带参数的函数,局域变量的数据无法在函数间传递,怎么办?这个时候需要利用
到缓存。像这样不允许参数的函数有哪些呢?归纳一下:如下几种
1、jass计时器调用的函数;
2、队列事件(单位组、玩家组、可破坏物组等)的过滤(条件)函数;
3、触发的条件函数。

缓存很好理解,就是一个仓库,这个仓库可以想象成很大的房子,里面有很多柜子,而每一个柜子
里面又有很多抽屉,然而,每一个抽屉只能装指定的5种类型的东西,且每一种东西只能装载一个。
就是柜体原理,用不着多么抽象专业的术语,我想上面这个阐述已经让你理解了什么是缓存。

那么,在实际的缓存中,房子可以看做一个缓存,当然,既然是很大的房子,一个地图一个缓存足矣。
柜子可以看做缓存的目录,抽屉可以看做缓存的标签,由于缓存的目录和标签是采用字符串的形式,因
此,我们不妨存储一个打火机1到房子X的A号柜子K号抽屉里:房子X → A号柜子 → K号抽屉 → 打
火机1同样的,我们必须在存储后指定的路径下才能找到这个打火机1,基于抽屉存放东西的唯一性,“房
子X → A号柜子 → K号抽屉”即指代所存放的打火机1。由于抽屉一次一种东西只能放一样,如果再放
一个打火机2进去的话,就会覆盖掉之前的打火机1,此时,“房子X → A号柜子 → K号抽屉”指向
机2。当然,如果在其他抽屉比如L号抽屉方上打火机2,丝毫不影响打火机1的存放,因为是2个毫不相干
的路径嘛。

所以,对于缓存,一个道理,一个标签只能存5种类型的数据:整数、实数、布尔值、字符串和单位。每种数据
只能存一个,否则后进来的数据会覆盖之前的属于这个种类的数据。下面,看看缓存是如何实现的。注意,缓存
必须在使用前初始化,方法为set 缓存变量名 = InitGameCache("缓存名"),缓存名随便取吧。

取:call Store数据种类(缓存, 目录名, 标签名, 该类数据的某个值)
存:GetStored数据种类(缓存, 目录名, 标签名)
清空标签:call FlushStored数据种类(缓存, 目录名, 标签名)
清空目录:call FlushStoredMission(缓存, 目录名)
清空整个缓存:call FlushGameCache(缓存)
检测缓存数据是否存在:HaveStored数据种类(缓存, 目录名, 标签名)

假定缓存名为udg_GC(udg_是什么?有T基础都多少知道):
call StoreInteger(udg_GC, "A", "b", 15) //将整数15存到了缓存GC的A目录下的b标签下
call StoreReal(udg_GC, "A", "b", 0.2) //将实数0.2存到了缓存GC的A目录下的b标签下
call StoreIBoolran(udg_GC, "A", "b", false) //将布尔值false存到了缓存GC的A目录下的b标签下
call StoreString(udg_GC, "A", "b", "TigerCN") //将字符串TigerCN存到了缓存GC的A目录下的b标签下
由于同一个路径下每种数据可以存一个,所以不会冲突,但若是此时在这个数据下再存一个整数37,即
call StoreInteger(udg_GC, "A", "b", 37)
那么GetStoredInteger(udg_GC, "A", "b")所指向的就是后存进来的37了,但这不影响该路径下其他类型的数据。
除非它们也想这样改变。当然,为了保证整数15不被覆盖,我们可以将37存到标签c下,即:
call StoreInteger(udg_GC, "A", "c", 37)
利用循环+缓存,我们可以存取局域数组变量。

这时,有的同学会问:只能存取5种?太少了吧!我要存取一个特效怎么办?不用担心,return bug出场了!我们可
以用他将其他种种句柄类的数据转换成整数,然后利用StoreInteger来存储。return bug原理很简单,利用编译器
自下而上的检测,当碰到第一个return时,被虚假的结果所蒙蔽就自动跳过检测。其形式如下:
───────────────────────────
function H2I takes handle hreturns integer
&#160; &#160; return h //真实返回的值是h,即句柄所指向的内存地址
&#160; &#160;return 0 //编译器先检测这里,返回值是0,正确,跳过检测
endfunction

function I2E takes integer ireturns effect
&#160; &#160; return i //从句柄所指向的内存地址中读取出句柄
&#160; &#160; return null //编译器先检测这里,返回值是null,正确,跳过检测
endfunction

───────────────────────────
所以我们要存句柄类的变量诸如单位、特效、点、单位组、物品等均可以利用此形式,自己按照这个格式套就是了。
这样我们就来存储一个触发单位:
───────────────────────────
function H2I takes handle hreturns integer
&#160; &#160; return h
&#160; &#160;return 0
endfunction

function I2U takes integer ireturns unit
&#160; &#160; return i
&#160; &#160; return null
endfunction


function run takes nothing returns nothing
&#160; &#160; local unit u = I2U(GetStoredInteger(udg_GC, "Unit", "Caster"))
//从缓存对应的路径下读取出存储的单位的地址,通过I2U转换成单位,再赋值给变量u
&#160; &#160; call RemoveUnit(udg_GC, "Unit", "Caster", H2I(u))
&#160; &#160;//删除单位u
&#160; &#160;call FlushStoredMisiion(udg_GC, "Unit")
&#160; &#160;//清空Unit目录。注意,删除整个缓存会删除其他使用缓存的数据
&#160; &#160;set u = null
endfunction


function test takes nothing returns integer
&#160; &#160; local unit u = GetTriggerUnit() //声明一个单位型局域变量u并赋值为触发单位
&#160; &#160;local timer t = CreateTimer() //声明一个局域计时器t并赋值为新建的计时器
&#160; &#160;call StoreInteger(udg_GC, "Unit", "Caster", H2I(u))
&#160; &#160;//将u转换成整数并存储到Unit目录的Caster标签下
&#160; &#160;call TimerStart(t, 5, false, function run)
&#160; &#160;//开启计时器t,一次性,持续5秒,作用与函数run
&#160; &#160;set t = null
&#160; &#160;//清空句柄变量

endfunction
───────────────────────────

(3)、1.24的Hashtablee(哈希表)
很简单,只不过是把缓存的目录和标签字符串记录的形式改成了整数,也修正了RB,同时可直接存储一些
句柄类的数据。

取:call Save数据种类(哈希表, 目录名, 标签名, 该类数据的某个值)
存:Load数据种类(哈希表, 目录名, 标签名)
清空目录:call FlushChildHashtable(哈希表, 目录名)
清空整个缓存:call FlushParentHashtable(哈希表)
检测缓存数据是否存在:HaveSaved数据种类(哈希表, 目录名, 标签名)

五、触发器和函数(35秒)
一般触发器无非是由三个函数组成:事件函数(初始化函数)、条件函数、动作函数,所以无需多疑,jass要完
成一个技能或系统也需要经历这3个步骤,当然,往往可以扩展出很多自定义的函数。总体思路不变。打雷了,
就写到这吧!10分钟让你基本掌握jass。更多内容请到高级讨论区去挖掘!如果有人问我最后创建的单位、杀死
单位、创建特效用jass怎么写,我只能说,最直接最偷懒的方法就是用T写再转换成J。

评分

参与人数 1威望 +3 收起 理由
疯人¢衰人 + 3 ……

查看全部评分

发表于 2010-9-26 19:07:18 | 显示全部楼层
不能说是特别好的教程,j的语法从来都不是困难的,难度在于参数传递以及timer延时,更复杂的是应用。即如何用j实现自己的目的。怎么在功能有限的j下实现无限的功能。
这个教程本身还是可以的,比我写的易于理解多了,但是理解不等于会同。教程更应该在使用上下功夫。
LZ不是作者,多说也没用,总之感谢转载
回复

使用道具 举报

发表于 2010-9-26 19:12:11 | 显示全部楼层
没记错的话 这个是百度编辑器吧那个置顶吧~~~ 回帖都很有爱
回复

使用道具 举报

 楼主| 发表于 2010-9-26 21:57:03 | 显示全部楼层
谢谢2楼加分

3楼那个不是置顶,是精品,而且百度的也不过是转贴,原贴是Wow8的
回复

使用道具 举报

 楼主| 发表于 2010-9-26 22:04:54 | 显示全部楼层
现在才发现2楼是斑竹
回复

使用道具 举报

发表于 2010-9-27 06:57:20 | 显示全部楼层
叶落无痕……才想起来作者我认得……
回复

使用道具 举报

 楼主| 发表于 2010-9-27 11:23:57 | 显示全部楼层
:-S
回复

使用道具 举报

发表于 2010-9-27 22:53:59 | 显示全部楼层
好东西当然要支持.
全局占用内存大,执行速率快,全局

这句话没问题吗?
回复

使用道具 举报

发表于 2010-9-27 23:17:53 | 显示全部楼层
可能一个是全局一个是局部吧...
话说我根本不清楚二者在开销和效率上有啥区别~
起码使用上几乎各司其职,泾渭分明了,那点儿区别早被使用习惯打趴在地老~
回复

使用道具 举报

 楼主| 发表于 2010-9-28 01:42:57 | 显示全部楼层
很多字打错了,我读的时候也发现到,尤其是好像 Boolan GameChche Hashtablee
回复

使用道具 举报

 楼主| 发表于 2010-9-28 01:44:22 | 显示全部楼层
全局和局域都有它的用途,局域有时在避免Bug方面用途很大,全局在运行速度的优势很高,但是占用内存不小
回复

使用道具 举报

发表于 2010-9-28 11:47:56 | 显示全部楼层
全局和局域变量在速度和大小上基本没差别。实际上大小的差别也可以无视。速度上变量最快,hashtable次之,Gc再次。功能上gc有总数上限以及String泄漏。hashtable只兼容1.24,变量有数组上限及参数传递的麻烦问题
推荐用hashtable,比较好的东西
回复

使用道具 举报

 楼主| 发表于 2010-9-28 22:11:13 | 显示全部楼层
1.24的都省略了GC和RB吧,都有HT了

据说HT能直接存取多种句柄,只是象天气效果这种就要数组
回复

使用道具 举报

发表于 2010-9-28 22:27:13 | 显示全部楼层
米有绝对自由类型转换的jass不够可爱
回复

使用道具 举报

发表于 2010-9-29 02:49:07 | 显示全部楼层
应该加个写个timer+ht/gc的应用 比如一个简单位移的例子 这个感觉是最常用的。
还有filter和group的Jass写法什么其实刚开始也搞不太懂挺麻烦的。。。不过慢慢写多了就好了。
回复

使用道具 举报

 楼主| 发表于 2010-9-29 11:23:32 | 显示全部楼层
是指ht的例子?
回复

使用道具 举报

发表于 2010-10-6 16:49:11 | 显示全部楼层

回 楼主(回帖是种好美德) 的帖子

好  还不错
回复

使用道具 举报

 楼主| 发表于 2010-10-7 02:11:05 | 显示全部楼层
谢支持
回复

使用道具 举报

发表于 2010-11-12 19:52:19 | 显示全部楼层
看了脑壳晕··
回复

使用道具 举报

 楼主| 发表于 2010-11-15 18:14:17 | 显示全部楼层
不会吧,可能你还没有T基础,已经写的很简易明了了
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-5-3 19:19 , Processed in 0.202711 second(s), 22 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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