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

转载的一份jass教程(不知道作者是谁)

[复制链接]
发表于 2008-12-11 15:53:13 | 显示全部楼层 |阅读模式
前言
    关于学习JASS,很多人都在JASS大门外,不论怎样徘徊,怎样想学,看了许多教程,都觉得自己仍然在JASS大门外。我觉得这和许多JASS教程有关系,感觉那些教程并不是写给没有任何语言基础的人看的,你要么要有一定的语言基础,要么已经会JASS了,不然看那些教程是很难懂的,有一定语言基础的人不多,已经会JASS的人还学JASS干嘛?特此,专门写一篇专门针对没有语言基础的人的教程,必须承认,我不是什么高手,也许教程里存在一些错误,但是仍然相信这篇教程会对那些想学却又看不懂教程的人有所帮助。
第一章 理论基础
第一篇 触发器原理
    看到很多教程都是从变量,从函数写起的,不过我觉得,比那些更基础,更重要的反而是这些看起来和JASS不怎么相干的内容,触发器原理。废话不多说,我们首先强调第一个问题,那就是请分清楚3个名词:
    JASS ,T ,UI
JASS是指jass语言,是暴雪独有的语言,它不像其他的C,C++,应用很广,你只能在暴雪开发的程序里见到
T是Trigger的简称,中文意思是触发器,什么用的下面会讲
UI则是指用户界面
    很多人容易把UI和T搞混,认为T就是UI,经常说用T做的XXX,没用JASS,实际上,用JASS怎么可能不用到T呢?而用到UI又怎么可能没有用到 JASS呢?实际上,我们所写的T的内容,最终是要转化成JASS的,所谓UI无非就是把JASS界面化罢了,T的内容不论怎样写,本质都是JASS啊。
如果你终于分清楚T不是UI,那么恭喜你,你对JASS又进了一步。
    接下来我来具体讲讲触发器原理。
    首先,我们回顾我们用UI学习写T的时候,我们被告知,所谓T就是要有事件,条件,动作,于是,我们也许就天真地认为,计算机在触发这些事件的时候就会像我们想象的一样去处理,那么试问,计算机如何理解这些事件?实际上,计算机要想完成这一套过程,必须要借助一个东西,那就是T,触发器,有了触发器这种东西,我们写的东西才有意义。接下来我们做一件事,将一个空T转成JASS,我做一个示范,方法是 按键顺序Alt->E->X ,这里我把这个触发的名字起做new,我们得到下列代码
function Trig_new_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_new takes nothing returns nothing
    set gg_trg_new = CreateTrigger(  )
    call TriggerAddAction( gg_trg_new, function Trig_new_Actions )
endfunction
    我们重点看set gg_trg_new = CreateTrigger(),这句的意思是对gg_trg_new这个全局变量赋值,值为新建触发器,任何一个触发,你转化成JASS以后都能见到这么一句,你可以做一个试验,自己随便写一个触发,转成JASS,然后删掉这一句,你会发现,你的触发废掉了,可见这一句多么重要。实际上,讲这里是要告诉大家,不要认为你的触是通过写了一个函数出现的,触发的实质是创建了这个Trigger。具体运行情况就是,当你写了一个触发的时候,比如上面那个空T, 那么机器就会一开始的时候运行
function InitTrig_new takes nothing returns nothing
    set gg_trg_new = CreateTrigger(  )
    call TriggerAddAction( gg_trg_new, function Trig_new_Actions )
endfunction
这个函数,这个函数创建了一个触发器,并且给出发添加了动作(默认添加动作,如果你的触发还写了事件和条件则也会添加事件和条件),于是一个具有意义的T就产生了,触发器的产生意味着你写的触发是有意义的,这时候触发事件才会发生效用。这就是触发器运行原理。于是我来举一个例子,动态注册,我不写代码了,写了不懂的人还是不懂,所以用说的
    那么我们首先写一条函数(关于函数,请参考函数篇),函数的目的是创建一个触发器,但是这个函数不是通过转化UI转过来的,而是自己写的,由于触发器添加一个事件是对玩家添加的,比如说玩家1事件(即使是任意单位事件,你去观察它的函数,本质也是一一对每一个玩家添加事件),有的时候我们检测到游戏只有1 个玩家,那么却对所有玩家都添加事件纯属浪费(实际上还有别的可能的原因,这里只是举个例子),能不能玩家几存在就对玩家几添加事件呢?答案是肯定的,我们只需要在这个函数里带入一个整数和玩家相对应,然后希望对哪个玩家注册就对哪个玩家注册了(所谓注册是指添加事件,注册事件和添加事件只是说法不同而已,是一回事)。
    在这个过程中,不必关注动态注册的过程,关心一下触发器过程,在我们自己所写的函数,虽然是创建触发器了,但是,只有这个函数,我向你保证,自己写的这个函数的触发器并不会被创建,我们必须用UI写一个触发,由这个触发去运行自己写的创建触发器函数,说到这里也许有人明白了,UI所转化过来的东西,比如上面那个机器在一开始会运行这个函数
function InitTrig_new takes nothing returns nothing
    set gg_trg_new = CreateTrigger(  )
    call TriggerAddAction( gg_trg_new, function Trig_new_Actions )
endfunction
    然而并不是因为它有触发器才运行的,而是因为它在一条触发里才运行的(注意观察函数名,前缀InitTrig_则意味着这条函数将在初始时运行)。我们完全可以做这样一件事,写一条触发,事件为地图初始化,其他的触发器函数都是自己写的,通过这个地图初始化的触发去运行那些自己写的触发器函数,然后你所有自己写的触发器函数都将生效,感觉有点像链式反应,动态注册就是这样的原理。
    最后给大家讲一下一个完整的T函数
    首先,还是创建一个触发,触发名随便起,比如new,添加一个事件,比如玩家1按下Esc事件,添加一个条件比如1等于1,添加一个动作,比如无动作转化成jass有
function Trig_new_Conditions takes nothing returns boolean
    if ( not ( 0 == 0 ) ) then
        return false
    endif
    return true
endfunction

function Trig_new_Actions takes nothing returns nothing
    call DoNothing(  )
endfunction

//===========================================================================
function InitTrig_new takes nothing returns nothing
    set gg_trg_new = CreateTrigger(  )
    call TriggerRegisterPlayerEventEndCinematic( gg_trg_new, Player(0) )
    call TriggerAddCondition( gg_trg_new, Condition( function Trig_new_Conditions ) )
    call TriggerAddAction( gg_trg_new, function Trig_new_Actions )
endfunction
    还是首先观察创建触发器的函数,
    call TriggerRegisterPlayerEventEndCinematic( gg_trg_new, Player(0) )
    call TriggerAddCondition( gg_trg_new, Condition( function Trig_new_Conditions ) )
    call TriggerAddAction( gg_trg_new, function Trig_new_Actions )
    这3句分别是,添加事件,(你看英文翻译过来的确是注册事件),添加条件,添加动作
    可以看到,单单创建一个触发器是不行的,触发器创建了还必须给它“装”上事件,条件,动作,它才能有反应,不然还是个空T,而上面的2个函数分别是条件和动作函数
function Trig_new_Conditions takes nothing returns boolean
    if ( not ( 0 == 0 ) ) then
        return false
    endif
    return true
endfunction

function Trig_new_Actions takes nothing returns nothing
    call DoNothing(  )
endfunction
    运行的时候,当条件函数符合条件则返回真值,这时候动作函数将被调用,否则不调用
    举个老狼的动作写在条件函数里的例子有利于理解
    刚一听到动作写在条件里觉得很神奇是么,我一开始也这样认为,单是理解之后,对触发器原理的认识又更近了一步
    触发器在触发事件后,会先运行条件函数,而条件函数是一个函数,试问,我们作判断有必要还用一个函数这么麻烦么?直接if XXX then 就行了吧,所以,在条件里,作完判断以后直接还在这个函数了作动作就可以了,何必再跳到别的函数了做动作呢?于是上面的东西我们可以这样写
function Trig_new_Conditions takes nothing returns boolean
    if   0 == 0  then

    endif
    return false
endfunction

//===========================================================================
function InitTrig_new takes nothing returns nothing
    set gg_trg_new = CreateTrigger(  )
    call TriggerRegisterPlayerEventEndCinematic( gg_trg_new, Player(0) )
    call TriggerAddCondition( gg_trg_new, Condition( function Trig_new_Conditions ) )
endfunction
    动作没了!写在条件里了。
    曾经我天真的觉得既然动作写在条件里运行效率更高,为什么不干脆直接写在触发器函数里?这个想法真的很傻很天真,当我恍然大悟明白原因的时候,终于彻底理解触发器运行原理了。
    这就是动作写在条件里的原理,这么写有一定限制,也存在RP BUG,所以举这个例子完全是为了帮助理解触发器运行原理的。
    废话很多,希望大家对触发器原理有一定的认识和了解,这对学习JASS是很有帮助的。
第二篇 变量基础
    关于变量,相信大家已经灰常熟悉了,全局变量谁没用过(要是你没用过变量,那请去熟悉一下变量再来看下面的内容),甚至有的人虽然不会JASS,但是也懂得全局变量的局部化,很多人觉得全局变量就是前缀有udg_的区别,这没什么错,但是不本质,如果暴雪愿意,前缀完全可以改成OOXX_,本质区别在于以下2点
   1。作用域,全局在全代码段有效,局部仅在该函数内有效。
   2。全局永久静态占用内存位置(即使set null也没有意义),局部可以释放(其实应该自动释放的,但是jass写得很不好,有些需要手动释放)。
   有的人在看别人写得演示的时候看不明白为什么好多函数末尾加一句set XXX=null这就是没法自动释放的要手动释放了
   为什么要释放?不释放就形成拉 机(。。。la ji 居然被屏蔽。。。。),越玩越卡
   这就是变量一些基础。关于变量的更多具体内容可以参考其他教程,在此不多说了,毕竟变量不是什么难点。
第三篇 函数基础
   很多人都把这个放在教程的最开头认为这是基础,这不可否认,确实在UI里的最小单位就是函数,比如我们想要创建一个单位,需要调用函数,比如要做一个条件判断需要调用函数。函数不仅是基础,也是这个语言的关键,废话不多说,马上来介绍函数。
   我们对以前数学上的函数一定不陌生,所谓函数就是诸如y=f(x)的形式或者y=f(x1,x2...xN)的形式,这些分别称为一元函数和多元函数,在jass中,函数和数学上的差不多,也有小的区别,其重点在于都是仅有唯一的函数值,也就是不可能出现 g(y1,y2...yN)=f(x1,x2...xN)的形式,对于一个数学函数,比如y=2x如果带入x=1那么得到y=2,比如 y=2x_1+2x_2,如果带入x_1=1,x_2=3那么得到y=8,如果我们把后者写成jass函数的形式就是
function f takes real x_1,real x_2 returns real
return 2*x_1+2*x_2
endfunction
    如果在其他函数里调用这个函数,那么比如这样
call a=f(1,3)
    那么a就被赋值f(1,3),实际值是8
    对于一个函数,我们只能返回一个值
    比如
function f takes real a returns real,integer
    就错了
    以上是函数的一种作用,这和数学上的没有区别,函数还有另外一种作用,那就是调用函数来“干活”
    就像变量一样,如果我们在触发里大量计算180/3.14+5*123,那么不如用个变量udg_X=180/3.14+5*123来代替,同样的如果我们需要大量使用某几句相同的内容,比如
    设置单位X坐标,设置单位Y坐标
    如果分开来写就是
call SetUnitX(xx,xx)
call SetUnitY(xx,xx)
    如果用函数来写,就是
function SetUnitXY takes unit u,real X,real Y returns nothing
call SetUnitX(u,X)
call SetUnitY(u,Y)
endfunction
    然后在任何地方希望设置单位坐标的时候只需要一句
call SetUnitXY(xx,xx,xx)就可以了
    当然这只是举例子,像这么简单的东西就没有必要写成函数了,不过遇上大量的可以用函数来简化书写
    最后,函数的2种功能当然也能合起来用
    下面给出函数格式
function 函数名 takes 带入值(自变量)可以有1个或多个,变量前必须指定类型,也可以不带入任何值,用nothing表示 returns 返回值(函数值),可以不返回,用nothing表示
===函数内容===
如果要求返回类型则必须相应返回匹配类型用
return xxx 请注意返回类型是否匹配
endfunction
调用其他函数用call xxx比如上面的
function SetUnitXY takes unit u,real X,real Y returns nothing
call SetUnitX(u,X)
call SetUnitY(u,Y)
endfunction
    以上为函数基础,使用函数需要注意几点
1。不能带入数组
2。在调用一个函数以前必须先定义这个函数,比如
funtion a takes nothing returns nothing
call b()
endfunction
funtion b takes nothing returns nothing
endfunction
    这是错误的 因为在调用b函数时还没有被定义
3。jass区分字母大小写 缓存文件名和文件夹的字符串除外
    此外jass允许函数的递归调用,也就是自己调用自己,比如
function f takes integer i rethrns integer
return i + f(i-1)
endfunction
    不过在使用递归调用的时候请注意终止条件。比如上面这个将是死循环
    最后讲一下函数语法检查的一个有趣的规则
    由于函数如果要求返回值,那么在函数中必须返回值,然而在语法检查的时候,机器是从下往上检查比如
function H2I takes handle H returns integer
    return H
    return 0
endfunction
    检查机制在检查到return 0的时候发现和要求返回的类型一致就认为没有语法错误了,然而在实际运行函数的时候,却是从上往下运行的
而return将终止函数后面的内容不执行,这样在上面这个函数里实际上返回了一个不符合要求的类型 但是语法正确了 这就是著名的return BUG
第四篇 关于CJ BJ函数
    上面所讲述的是一些基础内容,知道这些并不代表你就已经会用jass写触发了,具体写触发的时候比如我希望创建一个单位,怎么写?就算知道以上的内容你还是写不出来,于是我们来介绍实现具体写函数是的关键内容,CJ BJ函数。
所谓CJ BJ函数 是暴雪已经写好的现成函数,我们可以直接调用的东东。一个函数,如果由我们自己来写,而不调用任何CJ函数,很多功能是无法实现的,可以说CJ函数是实现邪恶计划的关键所在,当我们还在使用UI的时候,我们其实就已经在调用函数了,不过那些大多数是BJ函数,而BJ函数和CJ函数是相关的,所谓BJ函数,就是在使用CJ函数实现各种功能。这么说可能对各位深爱UI的朋友有点难理解,那么换种说法就是很多人之前所说的一条T(笑)所谓一条T,一般就是指一条函数,而且一般是BJ函数,偶尔也可能是CJ函数,CJ函数一般是强化性函数,如果你用UI,你会看到这些函数前缀(忘了,也好像是后缀,不过不重要)有[R]的记号,这也就是UI比不加UI的一个不同点,说到这里,能理解什么是BJ CJ函数了吧?!那么好,下面讲关于CJ BJ函数的用法。
    首先必须明确一点,即使是懂jass的人并不见得什么样的BJ CJ函数都认得,甚至可能只认得5%或者以下,但是这并不影响jass的使用,那么有人会问,那么多BJ CJ函数都不知道,还怎么玩jass啊?事实上,在写函数的时候真正能够清晰记得的函数是很少的,要想随心所欲的调用这些函数有2种方法。
    第一种,由于对UI的充分了解,想用哪条函数,就用UI写出来,然后转成Jass看,一般这时候的函数是BJ函数,那么就要查询函数的全部内容了,看它究竟调用了哪些CJ函数,然后观察这个BJ函数是否很鸡肋,如果写得还凑合,那就直接调用,要是写的很糟糕,那么根据类似的原理,自己写一个出来。
    第二种,由于我们所处理的对象是有限的那么几个,比如单位,比如物品,而一般单位相关的函数都有unit这个关键字,物品则是item,于是我们可以通过输入关键字来查找相关函数。
    第一种适合初学者,难度不高,第二种适合对函数比较熟悉的人,难度稍高,但是效率更高。关于查找函数,工具很多,比如JASSCRAFT 比如JASSNewGen。
    没什么耐心的朋友最好别去把BJ CJ函数全看完了,看几行就行了,大概的脑子里产生个印象就行了,我向你保证,看完BJ CJ函数,你整个人都会斯巴达掉~
    本篇结束
第五篇 练习内容
    上面讲的内容基本上够你简单的使用jass了,那么接下来我来出2个题练习一下,希望想学jass的朋友一定要动手,这样才能真正学会,否则你将依然在jass大门外徘徊。
1。随便用jass写一个程序 比如写一个技能,学会用对玩家发送消息的形式Debug(消除BUG)
2。把你曾经做过的一个技能,或者别的什么,要求曾经用到点变量,现在要求不使用点变量,而使用坐标代替
相关函数有
SetUnitX SetUnitY GetUnitX GetUnitY等
这些函数怎样使用 请自行查询
好吧,你做完了这些事,那么意味着,你再去看别人写的教程已经不是一头雾水了
加上自己的实践,相信想要学习jass的朋友离完全掌握已经不远了
本章结束

评分

参与人数 1威望 +5 收起 理由
eff + 5

查看全部评分

 楼主| 发表于 2008-12-11 19:00:03 | 显示全部楼层
第二章 jass使用举例
一 函数的使用
1〉 递归调用
    跟我的专业有关,我很喜欢递归调用,语句简单而思想深刻,可以说,这就是一种美。递归调用好处很多,也有缺点,好处在于:
1。一般来说不需要任何变量
2。处理诸如调换参数位置的问题时很方便
3。引起迭代(这点是递归独一无二的)
缺点在于,比起循环体占用更多系统资源
对于函数的递归调用,一般用于数学计算,下面给出几个递归调用的实例:

function Nn takes integer a,integer b returns integer
    if b==0 then
        return 1
    endif
    return a*Nn(a,b-1)
endfunction
这个函数在做正整数的正整数次幂运算,比起原有的实数的实数次幂运算效率必然更高,应用也是比较广泛的

function ABC takes integer a,integer b,integer c returns integer
    if (a<=b and b<=c) then
        call BJDebugMsg(I2S(a)+" "+I2S(b)+" "+I2S(c))//BJ函数 作用是对所有玩家发送消息
    endif
    if b<a then
        return ABC(b,a,c)
    endif
    if c<b then
        return ABC(a,c,b)
    endif
    if c<a then
        return ABC(c,b,a)
    endif
    return 0
endfunction
这个函数则是将abc三个整数从小到大排序
毕竟在实际制作地图的时候 数学计算并不会如此复杂 用到的递归调用也是非常之少的,在此仅作为一种兴趣去看看就好了。
2〉代替重复的语句
下面举2个例子,不仅在函数中实现了代替重复语句的作用,而且应用坐标计算,可以彻底抛弃点变量

function LandMoveByFace takes unit u,real S returns nothing
    local real face = GetUnitFacing(u) //获取单位面向角
    local real X = GetUnitX(u) + S * CosBJ(face)//计算新X坐标
    local real Y = GetUnitY(u) + S * SinBJ(face)//计算新Y坐标
    call SetUnitX(u,X)
    call SetUnitY(u,Y)
endfunction
按单位面向方向移动单位

function UnitHelper takes unit WhichUnit,integer UnitType,integer SkillID,string ActID,real CreateX,real CreateY,real TargetX,real TargetY returns nothing
    local unit u=CreateUnit(GetOwningPlayer(WhichUnit),UnitType,CreateX,CreateY,0)
    call ShowUnit(u,false)
    call UnitApplyTimedLife(u,'BHwe',2)
    call UnitAddAbility(u,SkillID)
    call IssuePointOrder(u,ActID,TargetX,TargetY)
    set u=null
endfunction
又比如说向我们以前要创建一个辅助单位释放技能要写一堆东西,现在只需要1条函数就可以搞定
函数的实例不需要很多 它是基础中的基础 在很多别的地方还会再见到它的

二 动态注册
前面我花费了大量的口舌在讲解触发器原理,如果你看懂了,那么接下来的动态注册问题你会轻松很多
动态注册着力于解决2个问题
1。触发器运行效率问题
2。某些事件只能监视指定单位 对于任意单位 希望也可以以此事件为触发事件怎么办?

function CameraTimer takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer ID = GetInteger(t,"id")
    if udg_Real_CameraCircle_H[ID] != 0 then
        set udg_Real_CameraCircle_T[ID] = udg_Real_CameraCircle_T[ID] + udg_Real_CameraCircle_H[ID]
    endif
    if udg_Real_CameraAngle_H[ID] != 0 then
        set udg_Real_CameraAngle_T[ID] = udg_Real_CameraAngle_T[ID] + udg_Real_CameraAngle_H[ID]
        if udg_Real_CameraAngle_T[ID] < 300 then
            set udg_Real_CameraAngle_T[ID] = 300
        endif
        if udg_Real_CameraAngle_T[ID] > 345 then
            set udg_Real_CameraAngle_T[ID] = 345
        endif
    endif
    if GetLocalPlayer() == Player(ID - 1) then
        call SetCameraField(CAMERA_FIELD_ROTATION,udg_Real_CameraCircle_T[ID],0)
        call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,udg_Real_CameraAngle_T[ID],0)
    endif
    set t = null
endfunction
function CameraControl takes nothing returns boolean
    local trigger tri = GetTriggeringTrigger()
    local eventid eID = GetTriggerEventId()
    local integer ID = GetInteger(tri,"id")
    if(eID == ConvertPlayerEvent(261) or eID == ConvertPlayerEvent(264))then
        set udg_Real_CameraCircle_H[ID] = udg_Real_CameraCircle_H[ID] + .7
    endif
    if(eID == ConvertPlayerEvent(262) or eID == ConvertPlayerEvent(263))then
        set udg_Real_CameraCircle_H[ID] = udg_Real_CameraCircle_H[ID] - .7
    endif
    if(eID == ConvertPlayerEvent(265) or eID == ConvertPlayerEvent(268))then
        set udg_Real_CameraAngle_H[ID] = udg_Real_CameraAngle_H[ID] + .3
    endif
    if(eID == ConvertPlayerEvent(266) or eID == ConvertPlayerEvent(267))then
        set udg_Real_CameraAngle_H[ID] = udg_Real_CameraAngle_H[ID] - .3
    endif
    set tri = null
    set eID = null
    return false
endfunction
function CameraTrigger takes integer ID returns nothing
    local trigger tri = CreateTrigger()
    local player p = Player(ID - 1)
    local timer t = CreateTimer()
    call SaveInteger(t,"id",ID)
    call SaveInteger(tri,"id",ID)
    call TimerStart(t,.01,true,function CameraTimer)
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(261)) //left   Press
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(262))        //Release
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(263)) //right  Press
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(264))        //Release
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(265)) //down   Press
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(266))        //Release
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(267)) //up     Press
    call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(268))        //Release
    call TriggerAddCondition(tri,Condition(function CameraControl))
    set t = null
    set tri = null
endfunction
方向键控制镜头的函数 用到了动态注册
一般来说 方向键操作使用动态注册比较多
新手的话重点观察最后一个函数CameraTrigger
所谓动态注册 是指对T的事件注册 关于前面的函数可以暂时无视

三 缓存代替全局变量的动态创建
有人说缓存的使用乃是jass的精华所在,我觉得与其说这是jass的精华,不如说这是前辈们给我们留下的一笔财富,由于缓存的存在,我们曾经讨厌的全局变量冲突问题终于很好的解决了(虽然在效率上不是很完美),废话不多说,下面来具体介绍。
所有用UI的人一定在作图的时候碰到这样的问题,一个技能需要用到全局变量,而又有时间间隔,那么这个技能所使用的全局变量如果不是数组那就只能给某个单位独自享用了,或者即使用了数组,恐怕也不过是一个玩家只能同时静态拥有有限的那么几个单位可以享用这个技能,否则,将无法解决变量冲突问题。关于后者,其实在实践中已经较好的解决了变量冲突问题,因为实际运用中需要一个玩家动态拥有一些单位可以享用上述技能是很少见的,但是在理论上并没有解决问题,我们希望真正在理论上解决此问题,让我们的好创意不受任何阻碍,能让我们YY无极限,接下来便为大家隆重介绍缓存的使用。这里是一大块内容,所以请大家有耐心的看下去。
1〉缓存是什么鬼?
缓存全称游戏缓存,E文名GameCache,作用是储存数据用途,可以储存整数,实数,布尔,字符串4种类型(还可以储存单位,不过不必理会),所储存数据的生存期和全局变量相同,简单说就是只要你不删除,所储存的数据永远在那不会变。那么它和全局的区别在于?
全局数组是一维数组,而且数组上限8000+,一个数组也就只能储存8000+数据,缓存看作变量的话则是2维数组,而且很有意思的是,缓存这个特殊的数组的数组角下标不是整数,而是字符串,那么这个特别的数组可以说所能储存的数据是无限的(其实有上限,不过我向你保证,你绝对用不到),不过对缓存储存数据的方式并不像给变量赋值那么方便,是通过使用CJ函数来实现储存数据和读取数据的
native  StoreBoolean    takes gamecache cache, string missionKey, string key, boolean value returns nothing
native  StoreInteger    takes gamecache cache, string missionKey, string key, integer value returns nothing
native  StoreReal       takes gamecache cache, string missionKey, string key, real value returns nothing
native  StoreString     takes gamecache cache, string missionKey, string key, string value returns boolean
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
一定有人看到这里就开始害怕了,不知道上面的是什么东东,这些都是CJ函数,你想看习惯,那就把所有的native换成function当成普通函数去看就行了
来解释一下这几条函数,Store表示储存,后面跟的是指储存的类型,GetStored表示获取已储存过的内容,后面跟的是所储存的类型。拿第一条以及和它相对应的第五条来举例:
call StoreBoolean(udg_Cache,"First","Second",true)
set a=GetStoredBoolean(udg_Cache,"First","Second")
这里第一句表示对udg_Cache这个变量所指代的缓存创建一个名为First的文件夹,名为Second的文件,Second这个文件所储存的数据是true
第二句表示设置a变量值为udg_Cache这个变量所指代的缓存的一个名为First的文件夹,名为Second的文件所储存的数据
很简单吧
这里要注意几点
1。缓存必须要在使用它之前被创建,创建缓存的函数请自行查询
2。缓存文件名和文件夹名不区分大小写
3。缓存在没有存入数据时候默认值分别是false,0,0.00,""

2〉传说中的return BUG?
在说return BUG以前,来回顾一下变量,我们见过的变量类型千千万,但实际上基本类型只有5种,布尔,整数,实数,字符串和句柄
前4种在全局变量中我们都见过了,最后一种使用UI的人一般不怎么熟悉,而这最后一种却又是非常重要的一种,它类似于内存的一个地址,很多变量的基变量都是它。
啥是基变量?(我觉得这里准确说是基类,然后以此为基的类则是继承与派生,学过C++的会很快理解这一段,没学过的无视吧)举个例子,比如说单位类型是我们常见的变量类型,如果是你,会通过什么方式记录单位类型呢?起名字呗~很好,这是很好的方法,但只是针对人类而言,人类对有意义的东西处理速度较快,但是机器却反之,机器处理一串数字要比一个名字(字符串)快得多,所以实际上采取整数型作为单位类型的基变量,一个整数对应一个单位类型(且是一一对应)。所以我们看这样一个常用的函数
native          CreateUnit              takes player id, integer unitid, real x, real y, real face returns unit
带入值的第2个unitid大概是单位类型的意思,但是仔细看参数类型,没错,是整数型,但是,我们在调用这个函数的时候,第二个带入值仍然可以是单位类型这个变量类型,不过如果要求带入单位类型,却不能够带入整数型,原因么很简单,整数型变量的取值范围比单位类型的大,如果要求带入单位类型却带入整数,而这个整数却不在单位类型取值范围内则会出错,扩展到一般的基变量和派生类别也有相同的结论。那么,现在好理解什么是基变量了吧?
了解基变量以后,来介绍一个非常重要的变量类型,句柄型,handle,它是众多变量类型的基变量,比如单位,比如物品,比如单位组,曾经有人列过表,表里说的很详细,哪些变量类型是基于handle的,我就不列了,你可以参考其它教程,比如GA出品everguo写的渣字图标的教程附录,那里面很详细,不过一般来说你不需要死记,判断一个变量类型是否以handle为基变量,如果这个变量类型在一张图中可以创建多个不同的具体对象,它一般就是以handle为基变量的,比如步兵可以在地图中创建多个,每个步兵都是具体对象,互相区分,而步兵类型不论你创建多少个步兵,步兵这种类型只能有一个,那么步兵这种类型对应的变量类型就不是以handle为基变量的变量类型。
下面介绍return BUG
所谓return BUG就是jass的一个漏洞,而这个漏洞却帮助我们完成了伟大的事业。来看标准的return BUG漏洞下的函数
function H2I takes handle H returns integer
return H
return 0
endfunction
前面已经说过检查机制的漏洞,这个只是原理,你就算不理解也没有关系(很多教程老爱强调原理,实际上对于新手原理根本不是重点),那不是重点,重点在于,上面这个函数在干嘛?我们从它的运行过程去理解它,带入了一个handle型变量,前面说过,由于这是基变量,那么我们可以带入任何以此为基变量的变量类型,比如我们熟悉的单位,这个单位对应一个handle值,于是函数把这个handle值返回了,然而要求返回类型确是整数型,这个时候计算机会怎样呢?不让你猜了,让你猜你也猜不出来,告诉你答案,计算机这个时候会把handle值转化成一段整数,这个整数有的人(比如学过C的人)可能会理解为变量地址,那么我告诉你,这个是不是地址我不清楚,也不重要,关键在于,转化后的整数集合与原有的handle所构成集合是同构的!通俗来说就是一一对应的。我们理解这一点要比知道return BUG原理重要得多,因为你就算理解return BUG原理对你应用它本身也没有什么帮助,而因为这里的一一对应性,我们可以理解为,转化后的整数就是handle的一个编号。并且肯定地说,H2I转化过来的值再转化回去不会发生偏差,也就是说H2I如果将一个单位转化成一段整数,那么拿这段整数转化回去仍然是之前的那个单位。不过以下代码
function I2H takes integer I returns handle
return I
return null
endfunction
虽然没有任何语法错误,却没有意义,因为handle作为众多变量类型的基变量,本身并没有处理价值,也就是说我们对于handle,它仅仅是只鸡(基)而已,要想有意义,那你想转成什么就要求返回什么吧,比如单位
function I2U takes integer I returns unit
return I
return null
endfunction

好了,讲了这么多东西,现在最精彩的地方才刚刚开始!
3〉实现数据绑定
其实数据绑定我们早就见过了,比如设置单位自定义值,这其实就是数据绑定于一个单位上,但是,这里限制颇多,一个单位只能设置一个自定义值,而且只能是整数!现在我们用缓存+return BUG(实际上利用的是数据转化)来实现数据绑定,以下是我写每张图的开头必有的内容,它就是数据绑定的基础
//------------------------------------------------------------------------------
//转化基础
//------------------------------------------------------------------------------
//return BUG
//------------------------------------------------------------------------------
function H2I takes handle H returns integer
    return H
    return 0
endfunction
function I2Item takes integer I returns item
    return I
    return null
endfunction
function I2U takes integer I returns unit
    return I
    return null
endfunction
function I2Tri takes integer I returns trigger
    return I
    return null
endfunction
function I2TC takes integer I returns triggercondition
    return I
    return null
endfunction
function I2TA takes integer I returns triggeraction
    return I
    return null
endfunction
function I2Tm takes integer I returns timer
    return I
    return null
endfunction
function I2Snd takes integer I returns sound
    return I
    return null
endfunction
//------------------------------------------------------------------------------
//简化缓存使用
//------------------------------------------------------------------------------
//基础缓存绑定数据到handle
//------------------------------------------------------------------------------
function SaveBoolean takes handle WantToSave,string Ex,string SaveName,boolean SaveData returns nothing
    local string S = I2S(H2I(WantToSave)) + Ex
    call StoreBoolean(udg_Cache,S,SaveName,SaveData)
endfunction
function GetBoolean takes handle WantToLoad,string Ex,string LoadName returns boolean
    local string S = I2S(H2I(WantToLoad)) + Ex
    return GetStoredBoolean(udg_Cache,S,LoadName)
endfunction
function SaveInteger takes handle WantToSave,string SaveName,integer SaveData returns nothing
    call StoreInteger(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetInteger takes handle WantToLoad,string LoadName returns integer
    return GetStoredInteger(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
function SaveReal takes handle WantToSave,string SaveName,real SaveData returns nothing
    call StoreReal(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetReal takes handle WantToLoad,string LoadName returns real
    return GetStoredReal(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
我为了方便举例,再把多余的东西删掉
//------------------------------------------------------------------------------
//转化基础
//------------------------------------------------------------------------------
//return BUG
//------------------------------------------------------------------------------
function H2I takes handle H returns integer
    return H
    return 0
endfunction
function I2U takes integer I returns unit
    return I
    return null
endfunction
//------------------------------------------------------------------------------
//简化缓存使用
//------------------------------------------------------------------------------
//基础缓存绑定数据到handle
//------------------------------------------------------------------------------
function SaveInteger takes handle WantToSave,string SaveName,integer SaveData returns nothing
    call StoreInteger(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetInteger takes handle WantToLoad,string LoadName returns integer
    return GetStoredInteger(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
function SaveReal takes handle WantToSave,string SaveName,real SaveData returns nothing
    call StoreReal(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetReal takes handle WantToLoad,string LoadName returns real
    return GetStoredReal(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
第2部分,你可以随便找一条函数来看,这就是数据绑定的实现,比如我要邦定一个整数100到单位XX作为这个单位的飞行高度(比如起个名字"flyheight"),只需要
call SaveInteger(XX,"flyheight",100)
OK了
想要获取这个单位的飞行高度只需
return GetInteger(XX,"flyheight")
读者请仔细观察SaveInteger   GetInteger   SaveReal     GetReal并自行体会这里面的用法,这就是数据绑定啦~
一般来说 数据绑定对象常用的有
单位unit 物品item 计时器timer 触发器trigger
当然并不限于这个,你可以发挥自己的无限YY能力,开发更强大的功能
上面说常常绑定于timer计时器上,而之前说的时间间隔导致全局变量冲突问题,实际上解决这个问题就是靠数据往timer上绑定来完成的,由于这些内容涉及到timer计时器,以及与其相配套的函数TimerStart,那么就放到下一节内容里讲吧~

四 触发器运行效率竟是如此之低
之前介绍过触发器的运行原理,我们知道一个触发器需要被注册事件,然后触发器开始监视对象,当条件满足时则触发器开始运行,然后进入条件判断,然后。。。总之有一大堆过程要做,比起函数要复杂的多,那么可想而知触发器的运行效率必然好不到哪去。曾经时候,我们做个跳跃的技能,想必不是用到计时器倒计时就是用了每XX秒事件,这些都是通过触发器来实现的,所以很多人说时间间隔过小多人游戏就容易卡~于是我们拿另外一个东西代替,直接运行函数而不是触发器,效率肯定会高许多:
函数TimerStart
native TimerStart           takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
举个最简单的例子
function timer takes nothing returns nothing
call DisplayTextToPlayer(Player(0),0,0,"!!!")
endfunction
function one takes nothing returns nothing
call TimerStart(CreateTimer(),1,true,function timer)
endfunction
如果function one被运行,则玩家一每1秒钟会被发送一条消息,内容是  !!!
这就使TimerStart的基本用法于是,我们再把前面的缓存数据绑定结合起来,跳跃技能的实现,下面给出跳跃函数
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 Distance takes real X,real Y,real x,real y returns real
    return SquareRoot((X-x)*(X-x)+(Y-y)*(Y-y))
endfunction
function SaveInteger takes handle WantToSave,string SaveName,integer Data returns nothing
    call StoreInteger(udg_GC,I2S(H2I(WantToSave)),SaveName,Data)
endfunction
function GetInteger takes handle WantToLoad,string LoadName returns integer
    return GetStoredInteger(udg_GC,I2S(H2I(WantToLoad)),LoadName)
endfunction
function SaveReal takes handle WantToSave,string SaveName,real Data returns nothing
    call StoreReal(udg_GC,I2S(H2I(WantToSave)),SaveName,Data)
endfunction
function GetReal takes handle WantToLoad,string LoadName returns real
    return GetStoredReal(udg_GC,I2S(H2I(WantToLoad)),LoadName)
endfunction
function MoveByAngle takes unit u,real S,real angle returns nothing
    call SetUnitX(u,GetUnitX(u)+S*CosBJ(angle))
    call SetUnitY(u,GetUnitY(u)+S*SinBJ(angle))
endfunction
function JumpTimer takes nothing returns nothing
    local timer t=GetExpiredTimer()
    local unit u=I2U(GetInteger(t,"Unit"))
    local integer Help=GetInteger(t,"Help")
    local real r=GetReal(t,"distance")
    local real angle=GetReal(t,"angle")
    call MoveByAngle(u,r,angle)
    call SetUnitFlyHeight(u,GetUnitFlyHeight(u)+Help/10,99999)
    if (Help==-100) then
        call PauseTimer(t)
        call DestroyTimer(t)
    else
        set Help=Help-1
        call SaveInteger(t,"Help",Help)
    endif
    set t=null
endfunction
function Jump takes unit u,location TargetPoint returns nothing
    local timer t=CreateTimer()
    local real distance=Distance(GetUnitX(u),GetUnitY(u),GetLocationX(TargetPoint),GetLocationY(TargetPoint))
    call SaveInteger(t,"Help",100)
    call SaveInteger(t,"Unit",H2I(u))
    call SaveReal(t,"distance",distance/200)
    call SaveReal(t,"angle",Atan2(GetLocationY(TargetPoint)-GetUnitY(u),GetLocationX(TargetPoint)-GetUnitX(u))*bj_RADTODEG)
    call TimerStart(t,0.01,true,function JumpTimer)
    set t=null
endfunction
好了,到此止,如果你全部掌握了,那么你已经可以较好地利用jass干活了,要想深入掌握jass则需要大量的练习和实践,希望各位热爱WE的朋友们能够真正享受WE带给我们的乐趣

五 扩展版 C2I简介
如果你理解H2I了,那么C2I的原理是一样的,实际上,如果全局变量中有code这个变量,我宁愿使用全局变量,可惜的是,没有!于是当我们希望全局传递code变量就变得很麻烦,于是用C2I将code存在缓存中,进行全局传递。具体内容可以参考everguo的帖子
http://bbs.tbswe.com/thread-40406-1-1.html

以上,基本上把前人的研究成果都挖出来了,希望对想学jass的朋友有所帮助
回复

使用道具 举报

发表于 2009-3-12 23:05:12 | 显示全部楼层
先顶再看~~~
回复

使用道具 举报

发表于 2009-4-30 18:05:03 | 显示全部楼层
[s:194]在心魔网看到过,搞得头晕死了。。
回复

使用道具 举报

发表于 2009-4-30 19:51:01 | 显示全部楼层
支持.
回复

使用道具 举报

发表于 2009-10-26 23:58:06 | 显示全部楼层
顶起来,慢慢看。
回复

使用道具 举报

发表于 2009-10-27 12:00:44 | 显示全部楼层
还不错
回复

使用道具 举报

发表于 2010-4-17 15:56:00 | 显示全部楼层
这是讲的最好的一篇了,顶起
回复

使用道具 举报

发表于 2010-11-19 11:02:51 | 显示全部楼层
对缓存比较有兴趣  就是不太懂  貌似 存储 和调用  是可以了  但是怎么删除呢    比如 一个函数 利用缓存  存储的数据  还没用完  第2次调用 又出现了 那不是又冲突了?
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-11-22 00:55 , Processed in 0.189970 second(s), 20 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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