找回密码
 点一下
查看: 6047|回复: 14

[勿进]填坑中

  [复制链接]
发表于 2008-1-8 23:33:16 | 显示全部楼层 |阅读模式
我是everguo,hackwaly把这号借我了;JASS培训班教程电子书出炉在即,我现在发帖补充些内容,以网页形式保存,请大家不要回复此帖。

感谢朋友帮忙,电子书以网页形式来保留JASS代码高亮,以下是效果预览

111.jpg

好了,1楼开始为正式内容
 楼主| 发表于 2008-1-8 23:34:32 | 显示全部楼层
欢迎大家加入到JASS学习的行列,我是Everguo(QQ:376856420   e-mail:[email protected]),很荣幸能为这部电子书写序;让一个曾对JASS敬而远之的人来负责撰写JASS教程,命运总是这般作弄世人。不过讽刺的是,正是因为在学习JASS的过程中遇到太多挫折,因此我写的教程比较能体恤新手(笑~);JASS不是什么困难的东东,因为像我这样的笨蛋都能学会~

    那么,首先简单介绍下吧:除了JASS培训班的教程,本电子书还收录了Greedwind和eGust等前辈编写的部分教程,zyl910和Red_wolf等高手的经典帖子,JASS培训班习题、试卷和优秀学员完成的作业,常见JASS问题与解答收录帖,以及大量JASS演示等资源。此外,犹豫了很久,最终我还是决定把[月协出品]的三章JASS教程也收录进来;虽然这教程内容有些YD,但据看过的朋友反映,它非常好懂,所以一并收录进来。

本电子书内容分为三部分:
一、基础教程。(为什么要学JASS,初学者如何上手,以及由Greedwind和eGust编写的部分教程。)
二、JASS培训班教程。(电子书的主体部分,包含从初级到高级的教程。)
三、其他资源。(经典帖子,月协出品的YD_JASS教程,大量演示,JASS培训班习题试卷和学员提交的作业。)


学习流程:
一、首先,去这里(连接1)看下自己的JASS水平属于哪个阶段,每个阶段都有针对性的学习方法。
二、确认自己所处阶段后,按给出的学习方法学习;学习到一定阶段后,可以找试题自测(连接2),自测分数达标后可进入下一阶段学习。
三、在学习完每章教程后,最后能主动完成作业;有兴趣可看下其他学员完成的作业(连接3)。
四、当你能做出教程指定的演示(连接4),那么,你可以出师了。
五、无论是在学习期间,还是学完教程内容以后,最好能多去论坛(www.ourga.com)逛逛,多在QQ群(7847868)里发言,同其它人交流JASS,这对能力提高帮助很大。
回复

使用道具 举报

 楼主| 发表于 2008-1-9 00:41:26 | 显示全部楼层
JASS常见问题之Q来A去:

========================================================JASS培训班专用分割线=========================================================

Q:什么是JASS?

A:JASS(正确地说是JASS 2)是魔兽3的程式语言,用於控制游戏和地图的进行,也是魔兽游戏和地图的基础。 地图编辑器中摆放的单位(Unit),区域(Region) ,触发(Trigger)……等,最终都会被翻译成JASS语言存在地图档里,在游戏时被使用。JASS在语法结构上比较接近Basic,同时也引用了许多C的东西。如果读者有接触过这二种程式语言,相信一定能很快上手!

========================================================JASS培训班专用分割线=========================================================

Q:为什么要学习JASS?

A: 某些功能只靠GUI Trigger无法完成, 必须用JASS来实现。例如对指定玩家播放音效,或者替单位加上永不消失的被动物品技能等。
      JASS可以定义区域变数及自订的函数,增加设计的便利性,也提供更简单可行的演算法。GUI虽然能完成几乎所有的功能,但是对於记忆体释放的能力太差,容易增加电脑不必要的负担。用JASS可以写出比GUI效率更高的程式码,对执行速度有不小的帮助。

========================================================JASS培训班专用分割线=========================================================

Q:一定要学JASS吗?

A:当然不一定。一般来说,单纯使用GUI Trigger,就可以达到大多数的功能。但是笔者还是建议对Trigger有相当了解的人学些基本的JASS写法,可以省下不少力气,且能让你的地图更不lag!!

========================================================JASS培训班专用分割线=========================================================

Q:如何使用JASS?

A:触发编辑器中的 Edit => Convert To Custom Text 将触发转成文字型态。在触发编辑器下面选 Actions => Custom Script 可以插入单列JASS叙述。此外,如果要定义所有触发都能呼叫的函数
    JASS语言的基本函数和常数都是直接调用游戏的函数,他们被存放在war3patch.mpq内的Scripts\\common.j中,另外还有一些扩充函数放在war3patch.mpq内的Scripts\\blizzard.j中。war3patch.mpq内的Scripts\\common.ai则包含了用於设计AI的 内部函数和扩展函数,虽然AI也是用JASS码编成,但本文不探讨关於AI的设计,有兴趣者请自行研究。
    地图中的触发以及物件的摆设情形等,都会被编译成JASS并储存在war3map.j档案中。读者可以到WE中的 File => Export Script 将它汇出。
    JASS语言以列为基本单位。每一列的文字必须有完整的意义,不能把一列的内容分成二列写;也不可把二列的叙述写成同一列。
    JASS语言是区分大小写的,该大写就要大写;该小写就要小写。
    写在//後面,直到该列结束的文字都是注解内容,这也是JASS唯一的注释语法。後面的例子会多处用到这个注释符号,这个符号和後面的注释只是用於解释程式码的功能,并不会被执行到 。
    在JASS中,空格的使用限制很宽松,除了某些必要的地方一定要有至少一个的空格以外,其它的地方都是可空可不空。此外,要空几格都无所谓,电脑不会因为你空了很多格就说有错。因此,使用者应多多利用空格作缩排,以使程式更易读。
    和数学一样,()内的程式码优先被执行。不过请注意,JASS中只有小括号()有用,中括号[]和大括号{}不可作为改变执行顺序之用。

========================================================JASS培训班专用分割线=========================================================

Q:我英语不好,也没学过编程,能学JASS吗?

A:当然。JASS是门程序语言,只要你熟悉常用的程序指令即可;此外,JASS的语法非常简单,如果你能熟练运用T,那么掌握JASS编程不难。

========================================================JASS培训班专用分割线=========================================================

Q:我写的JASS在保存时经常出错,怎么解决啊!

A: JASS常见错误一般有以下三种
   语法错误:少写一个字母,少空空白,或者把大写写成小写等,都是写JASS常犯的错误。一般来说,如果语法有问题,在存档时电脑会显示编译错误的讯息,并指出是哪一列有问题,依它的指示修正即可。不过当电脑指出某列有误时,也可能是前面的几列出了问题(范围大概约1~5列),所以如果你怎么检查都看不出某列到底错在哪里的时候,检查前面的程式码。此外,某些错误会导致存档时WE当掉,导致先前的辛苦付诸流水,所以请随时存档并尽量小心。
    执行错误:一般会发生这种问题是指定的变数没有资料,当电脑找不到资料时,由於无法继续执行,因此会无条件跳出目前的函数,如果该函数是要传回值的函数,它将不会传回值(也是无资料),因而可能导致呼叫它的函数也跳出。此外如除数为0也会造成 类似的结果。
    无穷回圈:一般这种事都是人为疏失(忘了写exitwhen,或是触发的动作引发同一个触发,而造成无限循环等),发生机会不大。不过一旦发生可是会让War3当掉的喔。

  你也可以借助JassCraft的语法检测功能来查出错误
6.jpg

========================================================JASS培训班专用分割线=========================================================

Q:如何将T转成J查看JASS代码?

A:选中触发器,在编辑中选择转化为自定义文本
1.jpg

========================================================JASS培训班专用分割线=========================================================

Q:如何将JASS代码添加到自定义脚本框

A:鼠标左键单击左上的地图名称,便会出现自定义脚本框

2.jpg

========================================================JASS培训班专用分割线=========================================================

Q:call set bj_wantDestroyGroup = true是什么意思,为何要在选取单位组前加这个。

A:因为我们在T里使用的选取单位组,它的JASS代码是这样

[codes=jass]function ForGroupBJ takes group whichGroup, code callback returns nothing
    // If the user wants the group destroyed, remember that fact and clear               //如果用户希望这个单位组被销毁,记得这事并清零标志
   // the flag, in case it is used again in the callback.                 // (即将bj_wantDestroyGroup 重新设置为false), 以便用户重新使用
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false
    call ForGroup(whichGroup, callback)
    // If the user wants the group destroyed, do so now.            //如果用户希望单位组被销毁,那么这样做            
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
endfunction[/codes]
可见,bj_wantDestroyGroup这全局真值型变量是个标志,当它为真时,系统会自动销毁刚才选定的单位组。 因此,假如是一次性使用的单位组,我们可以在前面加上call set bj_wantDestroyGroup = true,以便在使用完后自动清除。

========================================================JASS培训班专用分割线=========================================================

Q:数组尺寸是什么意思,好象设置与不设置没区别。
A:  很多朋友不清楚数组尺寸是做什么的,好象设置为0后数组也可正常使用;那么,不知大家注意到没有,在定义局部变量数组时,根本没有数组尺寸这东西——只有在WE里定义全局变量数组才有。一般来说,通常变量数组设置为0即可,惟独group(单位组)、force(玩家组)和timer(计时器)这三种类型的变量,作为数组使用时数组尺寸必须设置正确。

比如,我们定义一个名为timer的全局计时器变量数组,数组尺寸设置为5,那么,地图代码里会多出下面的代码
[codes=jass]globals
    // User-defined
    timer array             udg_timer
endglobals

function InitGlobals takes nothing returns nothing
    local integer i = 0
    set i = 0
    loop
        exitwhen (i > 5)
        set udg_timer = CreateTimer()
        set i = i + 1
    endloop   
endfunction[/codes]

我们看到,“exitwhen (i > 5)”,也既是说,我们把计时器变量timer的数组尺寸设置为5,那么会生成5个计时器,并且timer[0]、timer[1]…timer[4]均为有效变量,timer[5]就没意义了。

group和force也有类似性质,比如我们定义个名为Units的全局单位组数组,尺寸为10;定义个名为Players的全局玩家组数组,尺寸为12。地图代码如下
[codes=jass]
set i = 0
loop
        exitwhen (i > 10)
        set udg_Units = CreateGroup()
        set i = i + 1
endloop

set i = 0
loop
        exitwhen (i > 12)
        set udg_Players = CreateForce()
        set i = i + 1
endloop
[/codes]

大家以后在设置这三种类型变量数组时小心些,数组尺寸一定要设置正确。


========================================================JASS培训班专用分割线=========================================================

Q:如何查看地图JASS代码?
A:[月协出品]的YD-JASS教程中讲过查看地图代码的一个小技巧,不过还是把图文版本放出来,方便大家掌握查看地图代码的方法:
1.在任意一个T中,加入“自定义代码:call a()”,然后保存地图;
3.jpg

2.由于地图里没有“a”这个函数,因此保存地图时会出错(预料中的一个函数名);
4.jpg

3.先不要点确定,拉动滚动条,你便可以看到地图的代码,最好把这些代码复制下来,放到JASS工具中看。
5.jpg
回复

使用道具 举报

 楼主| 发表于 2008-1-9 01:36:03 | 显示全部楼层
JASS中的已知BUG        

        war3本身就有BUG(每个版本都说修正上个版本XXBUG),何况是JASS了;所以你在使用JASS时,要掌握JASS中常见的BUG,然后写代码时尽量避免,否则到你的地图会出现灵异现象时再来看这篇文章就悔之晚矣~


        这篇介绍“JASS中的已知BUG”出自wc3c,目前有三个译版——FlyingSown和onlyxuyang联合翻译的,illusion翻译的,以及狡猾的兔子翻译的;我综合这三个版本, 给出下面的译文:

========================================================JASS培训班专用分割线========================================================   

这篇文章打算对我们目前所知的JASS的BUG做出一个列表。在这里将会列出在暴雪的解析器以及在本地函数(natives)出现的BUG,但是出现在Blizzard.j和用户自定义函数中的BUG就不会列出来了。WC3中的BUG也不会列出来。

注意:这份列表仅针对于war3(冰封王座1.20)中的jass。

这篇文章的主要意图是帮助大家,同时避免论坛被相同的问题塞满。它包括了对BUG的大体描述以及对这些BUG进行了详细描述的文章的链接。如果有工具可以解决这些问题,这些工具的链接也会同时给出来(在使用前请先阅读该工具的ReadME文件)。
如果你发现有的BUG没有列出来,请把它回复在下面或者给我发短消息让我来添加它。

编辑器Bug:

1.漏写endif和endloop而导致程序崩溃
  对于没有结尾的loop或者if,WE有可能在存盘时崩溃。
出现该情况时(或者WE因为别的原因崩溃),你可以这样来挽救:在地图的目录下找到一个名为<地图名>的临时目录,在里面找到脚本,然后你可以用触发编辑器中的“文件->导入触发选项”从其他地图导入触发。注意:这样做容易导致一些重新申明全局变量的问题。

  我还是建议使用JassCraft。
JassCraft 说明及下载地址:http://www.wc3campaigns.net/showthread.php?t=80105

2. 无效整数型值导致存盘时崩溃
  无效的整数值,比如原始代码,有可能在WE存盘时导致崩溃。'&Atilde;&#8224;&Atilde;&#732;po'就是一个会导致崩溃的例子,'&Atilde;&#8224;'和'&Atilde;&#732;'也同样是不能接受的。

3.在文件名中使用单斜线(\\)在保存时将导致程序崩溃
  无论什么时候,在字符串中使用\\\\而不是\\,单\\是用于控制的。程序在使用字符串时会把其中一个\\删除。

4.函数被指定需要返回某值,却没有返回
  如果你的函数应该有返回值但是你没有返回一个值,保存的时候程序将崩溃。

5.错误定位不准
  WE有时不能检测一些bugs,也因此其分析器可能会把bug的位置报告低了一行。对这个问题没有解决方案,它只是告诉你编写错误,以及错误的位置,对WE来说,这不是一个bug,你必须检查错误报告的上一行。这个bug通常在你写错endfunction时发生。

6.字符串长度限制
  单个字符串的字符数超过860个会造成崩溃。要解决这个问题,使用字符串相连吧。

7.Return Bug
        这是一个非常有用的BUG。WE只检测最后一个return语言返回的值的类型,所以你可以转换;举个例子来说,可以十分方便地把handle型转换成integer型。下面就是这个传奇般的函数:
[codes=jass]function H2I takes handle h returns integer
        return h
        return 0
endfunction[/codes]
这个bug更像一个恩赐而不是一个bug,因为它允许你将固定的类型存储在其他的变量中,给予你很多自由。

关于return bug的更多文章:http://jass.sourceforge.net/doc/retbug.shtml
教程 使用局域句柄变量函数:http://www.wc3jass.com/viewtopic.php?t=2006
修复一个可能在使用返回值bug时出现的问题:http://www.wc3jass.com/viewtopic.php?t=2091
(编者按:关于使用Return Bug时可能出现的问题,会在后文补上。)

8.相同名字的变量彼此覆盖   
  这个bug非常烦人, 例如,你可以让参数和一个局部变量有同一个名字。最后声明的那个同名变量,也就是局部变量,将会保留它的引用(即有效),如果你不知道这点的话,这将会让你的脚本变得混乱
    但是,同时这个bug可以用来让界面多实例化(仅仅在一定程度上)http://www.wc3jass.com/viewtopic.php?t=2356
(编者按:这便是最新出炉的Union Bug,后文有介绍。)

9.在特定的操作系统上自定义脚本有一些限制(最开头那里写公用函数的地方,即自定义代码框)
        当使用Windows 98 和ME或者其他版本时,系统时就会出现此障碍,解决的办法是在编译其他的触发器前先创造一个触发器,所以你可以在这个触发器里面写你的自定义代码。有一个工具可以实现它,这样就允许了在使用混乱王座的时候也有地方写大量的触发器。
http://www.wc3jass.com/files.php?mode=view&id=17

10.有SP2补丁的XP和没有补丁的XP
        是的,这是真实的,而且可能是最无聊的bug了。
    很多东西在这两种系统上显得不一样。我们没有很好的解释。更糟糕的是有时两种系统上的游戏还会导致不同步。

11. 单独的"%"在保存地图的时候将被从自定义代码中除去    这只会发生在自定义脚本框。解决方案是,使用"%%"来代替,这样移除的时候只有第一个"%"会被除去。

本地函数相关Bug

1.IsUnitType 布尔表达式bug
        当你在布尔表达式中使用IsUnitType(whichUnit, whichUnitType) 而不比较真或假时,会出现bug。
详请见http://www.wc3jass.com/viewtopic.php?t=2370
(编者按:这个BUG在后文会有强调。)

2.游戏缓存限制
      游戏缓存最多只能有256个。当你有256个缓存存在campaigns.w3v文件中时,你的地图将变得很奇怪。
详情见http://www.wc3jass.com/viewtopic.php?t=2334

3.已销毁的(Destroyed)定时器仍会过期(Expired)
        当你在倒计时函数的另一个函数里将此计时器销毁后(destroyed) ,它仍然不会停止倒计时。解决方法很简单,在移除它之前先将其暂停(PauseTimer)。
详情见http://www.wc3jass.com/viewtopic.php?t=2373

4.SetUnitX 和 SetUnitY 导致自动关闭
      使用这两个本地函数时,如果给的坐标超过了地图范围 (即bj_mapInitialPlayableArea 的初始值之外), 就会造成游戏崩溃。解决的办法也很简单,就是在调用此函数前自己检查一下坐标是否超出了范围。
(编者按:最好用全局变量来保存地图边界,然后跟需要移动的坐标比较。)

5.在地图范围外(playable map area)创建单位导致自动关闭
        在可用地图区域之外创建单位 (即bj_mapInitialPlayableArea 的初始值之外))会造成崩溃。解决方法是在调用这个函数之前检查坐标所在范围。

6.RestoreUnit 忽略类型保护
    本地函数RestoreUnit 似乎会对返回的单位忽视类型保护, 因此使用不正确的话会造成游戏崩溃。
详情见http://www.wc3jass.com/viewtopic.php?t=2518
(狡猾的兔子:如不同的两个对象同时附加到同一个对象上时,如果使用了相同的文本标签,那么即使两个附加对象的类型完全不同,这个本地函数也不会区分,从而导致崩溃。)

7.GroupEnumUnitsInRectCounted 和 GroupEnumUnitsInRangeCounted
      这两个本地函数被用在大量单位上时会有非常不稳定的结果。

8.ExecuteFunc 的限制
      ExecuteFunc在搜索需要的脚本上时有一个愚蠢的错误, 当调用ExecuteFunc 的函数和被执行的函数之间的距离超过一定行数后,就会超时,然后什么都不做。

========================================================JASS培训班专用分割线========================================================  

编者按:以上是wc3c的“JASS中的已知BUG”,现补充几条新发现的BUG。

1.在ForForce中调用ForForce导致问题(由狡猾的兔子提供)
比如:
[codes=jass]function X3 takes nothing returns nothing
    call BJDebugMsg("2")
endfunction

function X2 takes nothing returns nothing
    call BJDebugMsg("1")
    call ForForce(udg_f, function X3)
    call BJDebugMsg("3")
endfunction

function X1 takes nothing returns nothing
    call ForceAddPlayer(udg_f, Player(0))
    call ForceAddPlayer(udg_f, Player(1))
    call ForForce(udg_f, function X2)
endfunction[/codes]
正确应该显示 1, 2, 2, 3, 1, 2, 2, 3, 但结果却是 1, 2, 2, 3, 2.

2.GetExpiredTimer()崩溃(由狡猾的兔子提供)
        有时当没有到期计时器的时候,调用到期计时器会导致讨厌的崩溃。
        如果“一个单位死了”事件调用一个触发“开火”,然后触发调用了 GetExpiredTimer(),如果死了的单位属于计算机用户,此时会发生崩溃。相反如果属于玩家,就不会发生崩溃。


3.handle负值引起弹出BUG(由hackwaly提供)
      不要在handle变量里存放负值,即使这个参数没有任何用途,仍将有几率导致error出现。
  一般这种情况是人为的,比如把一个负数由integer转换为handle(将-1直接i2h)。
  如果你遇到遇到弹出问题,又找不到原因,可能就是这个问题。

========================================================JASS培训班专用分割线========================================================  

编者按:针对前面的BUG,讲解几个地方。

1.Union Bug
[codes=jass]
globals
        handle H
        integer I
endglobals

function Trig_test_Actions takes nothing returns nothing
      local integer I
      local handle H
      set H = GetTriggerUnit()
      call BJDebugMsg(I2S(I))
endfunction 
[/codes]
我们会得到什么结果呢,触发动作显示在屏幕上的是什么呢?0?不,是触发单位的Handle值(地址:http://hilton.vs.oiccam.com/showthread.php?t=98230

  当全局变量和局部变量同名时,该全局变量被覆盖,然而jass的变量覆盖存在着bug,既每执行一次对被覆盖的变量的赋值之后,当前变量被赋予的值也同时会被无视类型地赋给本函数内所有其余的被覆盖变量。也就是说,在这个函数里,GetTriggerUnit() 不但被赋给了H,也被赋给了I。

  我们称这个bug为Union Bug,是用于替代以前的Return Bug实现强制类型转换的好方案。
                               
                 (原作者:[Wc3c]Troll-Brain  翻译及解释:[GA]Renee)


[注:]其实这BUGzyl910也发现了,在他的JASS学习笔记中可以找到——当时他建议不要采用“local integer udg_i”的写法,说这样会造成BUG,还给出了演示;但他没进一步研究如何用这BUG做些来转换类型,以致与一个伟大的发现失之交臂。 

  Union Bug跟Return Bug一样,是个很有用的BUG,更显现出替代Return Bug的趋势;不过目前关于它的用法还没研究成型,这里也不多多介绍,有兴趣可去论坛看下(http://www.ourga.com/bbs/read.php?tid=8723)。

2.Return Bug可能存在的问题——使用return bug时把变量设置成null”-bug(由狡猾的兔子提供)

        在极少数情况下,设置一个变量(多数是计时器),而这个变量又被你连同return bug一起使用,会产生问题。它看起来像是重设了某对象的handle id。
     解决此问题有很多方案:
     不要把变量设置成null。这是最容易的方案,但会造成(轻微)内存泄漏。
     另建一个函数用于输入这个计时器(或者造成该问题的其他类型)。此时你可以把null值传递给那个函数,也可以在这个函数中将变量作为参数使用,而不需要把它设置成null。比如:
[codes=jass]function X2 takes timer t returns nothing
    call DoSomethingWith(t)
    call DoSomethingElseWith(t)
endfunction

function X takes nothing returns nothing
    call X2(GetExpiredTimer())
endfunction
//...
call TimerStart(yourTimer, duration, periodic, function X)
//...[/codes]
        最后,你可以使用全局变量来临时储存这个对象。
     除非你的脚本里有这个问题,否则我建议你不需要太关心这个bug。

3.IsUnitType
        通常在我们写触发器条件时候,我们通常把
[codes=jass]function Trig_test_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'AUan' ) ) then
        return false
    endif
    return true
endfunction[/codes]
写成
[codes=jass]function Trig_test_Conditions takes nothing returns boolean
    return  GetSpellAbilityId() == 'AUan'
endfunction[/codes]
一般情况下,这样写没什么问题,可当对象是IsUnitType时,就得小心了;以下两种写法,都会出错
[codes=jass]function Trig_Lame_Condition takes nothing returns boolean
    return (IsUnitType(GetTriggerUnit() , UNIT_TYPE_TOWNHALL) == true)
endfunction

function Trig_Lame_Condition takes nothing returns boolean
    return IsUnitType(GetTriggerUnit() , UNIT_TYPE_TOWNHALL)
endfunction[/codes]

详情请见:http://www.wc3jass.com/viewtopic.php?t=2370
回复

使用道具 举报

 楼主| 发表于 2008-1-9 21:42:55 | 显示全部楼层
其他资源:

1.WE变量命名规范
http://www.ourga.com/bbs/read.php?tid=7687&fpage=4

2.TriggerRegisterFilterUnitEvent
http://www.ourga.com/bbs/read.php?tid=7686&fpage=4

3.JASS学习笔记
http://www.ourga.com/bbs/read.php?tid=7690&fpage=3

4.Jass心得
http://www.ourga.com/bbs/read.php?tid=7684&fpage=3

5.WORLD EDITOR和JASS变量类型对照表
http://www.ourga.com/bbs/read.php?tid=7681&fpage=3

6.GameCache应用教程
http://www.ourga.com/bbs/read.php?tid=3791&keyword=

7.常见JASS问题之Q来A去
http://www.ourga.com/bbs/read.php?tid=8513

8.作业
第一期:http://www.ourga.com/bbs/read.php?tid=7818
学员提交作业: acer01-第一期JASS培训班试题 .w3x (24 KB, 下载次数: 59)    JASS培训班第一期作业byV·K.w3x (32 KB, 下载次数: 44)       JJJ8-JASS培训班第一期作业.w3x (21 KB, 下载次数: 43)

第二期:http://www.ourga.com/bbs/read.php?tid=7912
学员提交作业: blandheart-作业提交—英雄复活.w3x (23 KB, 下载次数: 54)        英雄复活系统byV·K.w3x (24 KB, 下载次数: 63)        acer01-英雄复活系统.w3x (22 KB, 下载次数: 38)

第三期:http://www.ourga.com/bbs/read.php?tid=8428
学员提交作业: acer01-3C选英雄.w3x (25 KB, 下载次数: 53)    

9.IdToString&StringToId
http://www.ourga.com/bbs/read.php?tid=858&fpage=2
演示: 聊天输入创建物品.w3x (19 KB, 下载次数: 45)
  
回复

使用道具 举报

 楼主| 发表于 2008-1-10 03:11:19 | 显示全部楼层
[月协出品] YD的JASS教程

[简介]月协的JASS教程,一共三卷,内容丰富,语言通俗易懂,举例生动形象妙趣横生,更介绍了一些非常实用的小技巧;虽然YD,却深受某些朋友喜爱;本打算把这些教程净化后发布,但一来太花时间,二来会破坏教程原有的味道,故作保留,请大家以正确的眼光对待。

[]这些JASS教程,均出自月协会长之手,他以天马行空的手法给我们展现了一个奇妙荒诞的JASS世界,让我们在大笑过后不但消除了对JASS的恐惧更增进了对JASS的了解。在第一卷讲变量时,他以三名妓女来分别比喻全局变量、局部变量和全局变量数组,并在篇末讲解了一种通过“阉割全局变量”,让技能可以多人使用的方法;在第二卷讲函数时,以嫖客和妓女为例讲解函数的调用,情节丝丝入扣,顺带介绍了一种快速查看地图代码的小技巧;在第三卷讲GameCache时,以木子美的性爱日记为参照讲解GameCache的使用,更把GameCache跟Return Bug和Timer的关系比作淫乱的3P...


                               一、变量

1.什么是变量
    变量是存放的是实质的东西,并且因存放的东西不同而分类不同:有单位、区域、触发器等变量——这样说吧,就像容器,放水的叫杯,盛饭的叫碗,装屎的叫马桶。因分类不同,所以你不能让一个变量储存与它类型不同的东西,譬如你不能用单位变量来保存地图上一个区域,就像你不能用马桶给客人盛饭。

2.不同变量类型的转换

     虽说变量不能储存与它类型不同的东西,但变量的类型,其实是可以改变的。我们在T里,看到“将一个整数转换成实数”,“将一个整数转换成字符窜”等等;WE里提供了三种变量类型之间的自由转换——整数,实数,字符窜;就像我们可以用杯子装饭,拿碗装水一样。

     不知不觉便说到我们的周星星同学了,因为有人看到他居然拿马桶吃饭;这是怎么回事?原来有个Return Bug,利用系统漏洞可以实现任意两个变量类型之间的转换。由于周星星是个特工,他的皮鞋,其实是吹风机,他的电话,还是吹风机,也就是说,由于职业需要,他把他的东西做了改装,以麻痹敌人。所以,他拿的马桶也不是真的马桶,那其实是个饭盒,外观像马桶而已。所以当周星星拿着马桶去厕所,别人以为他是去便便,其实他是去吃饭。

     关于Return Bug,将在后面着重单独列出来讲。


3.全局变量与局部变量
 
     变量是容器,这里我们拿杯子来说明全局变量与局部变量的区别。全局变量就像一个玻璃杯,不可能两个人同时使用,使用完下次可继续使用;局部变量则像一叠一次性纸杯,同时可以满足很多任何水的需要,并且使用完纸杯就丢掉,即便你有Sars也不怕传染给别人。

     一般而言,由于程序在执行时,速度很快,因此多个T使用同一个全局变量,出错的概率很小;但,一旦T里有等待,情况就不妙了。

    就像用杯子喝水,如果你是一口气把水喝完,然后把杯子放回原处给下一个人用,没什么问题;但如果你喝一小口,歇半天,再喝下一口,就出问题了——如果这时,另外有人也要用这杯子,那么,你跟他将轮流喝杯子里的水——很有断臂山的感觉。

    好了,我们把目光回到JASS上。

1>全局变量

      全局变量我们并不陌生,顾名思意,它是在不同的函数体里都能使用的变量。

     那么,哪些变量是全局变量呢。

     首先我们在T里自定义的变量全是全局变量;将T转为J后,你会发现,所有自定义变量前都有udg_的前缀。比如,我们定义一个类型为整数的变量i,那么,当保存地图时,我们可以看到下面这条J:
[codes=jass]globals
        // User-defined
        integer  udg_i= 0
endglobals[/codes]
        可见,在J里,全局变量是在globals下定义的。

        以bi_为前缀的变量也是全局变量,比如bj_lastCreatedUnit,bj_forLoopAIndex,等,或许这些变量你看了有些陌生,但是之前在T里我们曾大量使用过。

        在介绍bj_类全局变量前,我们先看两个例子:

        我们常说,不能在循环里加等待,为何?因为bj_forLoopAIndex便是循环里使用的循环整数A,它是一个全局变量;如果在循环里加等待,那么当别的T也用循环整数A来执行循环时,前面的提过断臂山就将上演。


        还有一个经典的错误,我们在创建一个单位后,等待XX秒,然后命令上次创建的单位去XX,便有可能会出错,因为此时最后创建的单位可能不是原来的单位了。或许你已经猜到了,我们在用T时,系统每生成一个单位,便用bj_lastCreatedUni来保存它,因此使用等待之后,那它可能保存别的单位去了。

        其实,bj_类全局变量,不是别的,正是WE自带的全局变量;如果说udg_类变量是二奶,那人家bj_类变量就是原配。

2>局部变量

          局部变量是在一个函数体里定义的变量,并且只能在该函数体内使用。它定义的方式如下:

local <类型> <变量名>

        比如,我们定义一个单位变量:


local unit FRJJ

        于是,我们就定义了个名为“芙蓉姐姐”的单位变量。

      “定义它做什?”


      “待会儿我要用她教训看帖不回帖的人。”

        “YD男,你这样讲解太费事了,把局部变量比作老婆,把全局变量比作军妓,不就讲明白了么!”

      “非也,老婆都还有红杏出墙的可能,但局部变量它忠贞不二绝不会勾一搭二勾三搭四。”

        要注意下,部分类型的局部变量在使用后必须清零,所以在用FRJJ教训完看贴不回帖的人后,要记得

        set FRJJ = null

        除了unit类变量,类似需要清空的还有rect, location, timer, trigger, group, triggercondition, triggeraction,我只记得这几个,漏了的话请大人补充。


3>全局变量+数组

        丽春院的生意火了,这可把已经升级到老鸨的韦春花乐得合不拢嘴;但让她发愁的是,最近人手严重不够用。举个例子,她去给田大爷唱十八摸,都唱到第十七摸了,结果西门大官人又点她去唱;给西门庆唱了第一摸后,在田伯光面前把第十八摸唱成了第二摸,这可把她郁闷到了。
        这该死的全局变量!
        这时,一个小鬼头跑进来,两边脸都有三条纹,跟小花猫似的;他问:
        “店里招人吗,不用给我工钱,给碗拉面就是了。”
        “我们不招伙计。”
        “我可以去接客。”
        “哼,来这的人都来找姑娘的,没人会找正太……”
        韦春花话音未落,眼前的小正太不见了,出现一个裸体的美女,两边脸都也有三条纹。
        “你看,这样行吗?”
        哇噻,这小鬼居然会使传说中的色诱术,相信大家都猜到他是谁了吧!
        这时西门庆跟田伯光打起架了,都想要这小美女陪,但又不肯玩3P,这可急煞了韦春花。此时,只见小美女双手结印,大喝一声“影分身之术”;瞬间三个美女出现在众人面前。
        三个美女,一个陪西门庆,一个陪田伯光,另一个接过拉面埋头大吃。
        韦春花纳闷,难道这小子像孙悟空一样,拔毛一吹能变出小猴子;她好奇地到两位客人的房前偷听,只闻得那一声声“呀咩嗲”连绵不绝,没有任何可疑之处。
        事后,当西门庆跟田伯光满足地走出来,韦春花进屋一看,哪有小美女的影子……
        局部变量就是好啊!不愧是东嬴来的,真厉害。韦春花感叹道。

        这时,又进来一个女人,十分妖艳。
        “你是谁?”
        “难道你看不出,我是个女人吗?”   
        “你来这做什么?”
        “看你缺人手,帮你接客咯!”
        “不用了,我有人选了。”这小鬼只要一碗拉面就能打发,而且会这么厉害的法术,韦春花当然不会再招人。
        “这小Y头片子有我漂亮么?”
        “人家会法术。”
        “不就是分身术么,我也会!”说完,一阵烟雾过后,几十个美女出现在韦春花面前。
        这时,门口的客人再人忍不住,一窝蜂冲进来,抢了美女就去开房。
        “好,你被录用了,你叫什么名字。”
        “我叫春三十娘。”

        春三十娘的分身比那小鬼头的更好,完事后会从房里走出来;不像那小鬼头的分身,太邪乎了。
        托两个伙计的福,这几天丽春院生意一直很好。不过,被春三十娘的分身服侍过的客人,有些染上了花柳;而小鬼头的分身,即便是服侍了有花柳的客人,也没事,因为服侍完后分身就不见了,连根毛都没有。渐渐的,客人不敢点春三十娘服务了。
        这时,有人指出,春三十娘的分身术是骗人的把戏,这人不是别人,正是我们智慧的化身——阿凡提。
        原来阿凡提每次被春三十娘的分身服侍,都会在她们身上做记号;终于有天,他发现服侍自己的分身上有以前做过的记号。
        铁证如山,春三十娘只好承认,她有二十九个孪生姐妹——她们分别是春大娘,春二娘,……,春二十九娘。

        那么,故事讲完了~


          如果韦春花是全局变量,旋涡鸣人是局部变量,那春三十娘跟她的姐妹们就是全局变量加数组——她们长得一模一样,能同时应付多人需求。

        全局变量加数组还能克服全局变量的一些弊端,比如,我们要在循环里加等待,那我们就不要使用循环整数A,而是定义一个整数变量数组Loop[],可以把循环数设为Loop[触发单位的自定义值],或Loop[触发玩家的索引],这样一样出错的概率就大大降低。

        因此,使用不了局部变量,用全局变量加数组也是个不错的选择~~

        需要指出的是,关于数组的尺寸,一般类型变量的数组,尺寸设置为0就可以了,使用时数组会自动增加其长度;而单位组跟玩家组类型的数组,则必须设置好尺寸,比如PiaokeZhong[]的尺寸设置为3,那PiaokeZhong[4]就是无效的;PiaokeZhong[3]也无效,因为数组的有效索引是从0开始的,所以真正有效的三个单位组是PiaokeZhong[0]、PiaokeZhong[1]、PiaokeZhong[2]。

4.王道

        即便采用全局变量加数组,也不能完美地模拟局部变量的功能,正如前面所说,春三十娘的分身还是会传播花柳;可是,我们无法在T里使用局部变量,那么,怎么完美地解决了。

        当你被问到谁是你心目中武功最高的人,你可能会想到他(她)——东方不败。为什么他(她)最厉害呢,因为他(她)既不是男人也不是女人,但同时又具备了两者的特性,阴阳调和,是天地间最可怕的生物。所以,人妖是王道啊,比如我国现在采取的一国两制。

        那么,如何将局部变量写进T里呢,呵呵,我们有Custom Script(自定义代码),也就是传说中的《葵花宝典》。

        举例如下:

[trigger]
piaoji
    事件
        单位 - 单位进入 丽春院
    环境
        ((触发单位) 是一个嫖客) 等于 True (真)
    动作
        自定义:local unit piaoke = GetTriggerUnit()
        自定义:local real x = GetUnitX(piaoke)
        自定义:local real y = GetUnitY(piaoke)
        自定义:local unit WeiChunhua = CreateUnit(Player(0),'JV00',x,y,0)
        游戏 - 对 (所有玩家) 显示文本: 楼上楼下的~
        游戏 - 对 (所有玩家) 显示文本: 姑娘们出来~
        游戏 - 对 (所有玩家) 显示文本: 接客了!
        自定义:call IssueTargetOrderBJ( piaoke, "XX", WeiChunhua )
        等待 1800.00 游戏秒
        自定义:set piaoke = null
        自定义:call RemoveUnit(WeiChunhua)
        自定义:set WeiChunhua = null[/trigger]

        如何,我们用个局部变量piaoke来保存触发单位,然后生成的单位WeiChunhua也用局部变量保存,如此一来,即便丽春院生意再红火,也不会出现人手不够的情形,也不会有人染上花柳。

        这里,需要注意下,含有local的自定义代码必须放在其它代码前面,否则会出错。

        那么,如何把一个含有全局变量的T,变成上面的“东方不败”呢,呵呵,很简单。

        我们先用T把这个触发写好:
[trigger]
piaoji

    事件
        单位 - 单位进入 丽春院
    环境
        ((触发单位) 是一个嫖客) 等于 True (真)
    动作
        设置 piaoke = (触发单位)
        设置 point = (单位 piaoke 的位置)
        单位 - 创造 1 个妓女为了玩家 1(红色) 在 point 面对 0.00 度
        设置 WeiChunhua = (最后创建的单位)
        游戏 - 对 (所有玩家) 显示文本: 楼上楼下的
        游戏 - 对 (所有玩家) 显示文本: 姑娘们出来
        游戏 - 对 (所有玩家) 显示文本: 接客了!
        单位 - 命令 piaoke  XX WeiChunhua
        等待 1800.00 游戏秒
        单位 - 把 WeiChunhua 从游戏中不留痕迹的删除
        点 - 删除 point[/trigger]

        好了,我们开始人类改造手术吧(奸笑~)

        首先,我们把这个T转成J,我们可以看到,在动作函数里


[codes=jass]function Trig_piaoj_Actions takes nothing returns nothing
      set udg_piaoke = GetTriggerUnit()
      set udg_point = GetUnitLoc(udg_piaoke)
      call CreateNUnitsAtLoc( 1, 'JV00', Player(0), udg_point, 0.00 )
      set udg_WeiChunhua = GetLastCreatedUnit()
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1330" )
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1331" )
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1332" )
      call IssueTargetOrderBJ(udg_piaoke, "XX", udg_WeiChunhua )
      call PolledWait( 1800.00 )
      call RemoveUnit( udg_WeiChunhua )
      call RemoveLocation( udg_point )
endfunction[/codes]

          现在,我们可以戴上手套,拿出剪刀了(继续奸笑)。

        我们的目的,是用局部变量替代全局变量;找了下,这段代码里有三个全局变量,udg_piaoke,udg_point,udg_WeiChunhua;为什么我们能很快找出全局变量呢,因为他们多长了个东西——“udg_”,好了,是时候阉割全局变量了让他们变人妖了~

剪掉udg_后,代码变成
[codes=jass]
function Trig_piaoj_Actions takes nothing returns nothing
      set piaoke = GetTriggerUnit()
      set point = GetUnitLoc(piaoke)
      call CreateNUnitsAtLoc( 1, 'JV00', Player(0), point, 0.00 )
      set WeiChunhua = GetLastCreatedUnit()
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1330" )
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1331" )
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1332" )
      call IssueTargetOrderBJ(piaoke, "XX", WeiChunhua )
      call PolledWait( 1800.00 )
      call RemoveUnit(WeiChunhua )
      call RemoveLocation(point )
endfunction[/codes]

        那么,手术结束了吗?当然没,因为局部变量是要先定义的,而且在使用完后,单位啊,点啊之类的变量要清空。

[codes=jass]function Trig_piaoj_Actions takes nothing returns nothing
      local unit piaoke            
      local unit WeiChunhua
      local location point
      set piaoke = GetTriggerUnit()
      set point = GetUnitLoc(piaoke)
      call CreateNUnitsAtLoc( 1, "JV00", Player(0), point, 0.00 )
      set WeiChunhua = GetLastCreatedUnit()
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1330" )
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1331" )
      call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_1332" )
      call IssueTargetOrderBJ(piaoke, "XX", WeiChunhua )
      call PolledWait( 1800.00 )
      call RemoveUnit(WeiChunhua )
      call RemoveLocation(point )
      set piaoke = null
      set WeiChunhua = null
      set point = null
endfunction[/codes]

        手术成功,我们先把这段代码保存下来。

        最后,我们把这些代码,一行行用自定义代码加到T里,就成了这样子~
[trigger]
piaoji
    事件
        单位 - 单位进入丽春院
    环境
        ((触发单位) 是一个嫖客) 等于 True (真)
    动作
        自定义:local unit piaoke
        自定义:local unit WeiChunhua
        自定义:local location point
        自定义:set piaoke = GetTriggerUnit()
        自定义:set point = GetUnitLoc(piaoke)
        自定义:call CreateNUnitsAtLoc( 1, 'hfoo', Player(0), point, 0.00 )
        自定义:set WeiChunhua = GetLastCreatedUnit()
        游戏 - 对 (所有玩家) 显示文本: 楼上楼下的
        游戏 - 对 (所有玩家) 显示文本: 姑娘们出来
        游戏 - 对 (所有玩家) 显示文本: 接客了!
        自定义:call IssueTargetOrderBJ(piaoke, "XX", WeiChunhua )
        等待 1800.00 游戏秒
        自定义:call RemoveUnit(WeiChunhua)
        自定义:call RemovLocation(point)
        自定义:set piaoke = null
        自定义:set WeiChunhua = null
        自定义:set point = null[/trigger]

    于是一个伟大的人妖诞生咯~

        可以看出,对于含有全局变量的一行T,我们实行阉割,然后装进自定义代码中,但对于其他的T,比如上面的“游戏 - 对 (所有玩家) 显示文本: 接客了!”、“等待 1800.00 游戏秒”,则可以原封不动保存下来。
        我一直认为做图,没必要学习JASS,T就已经绰绰有余;只需要了解点排泄方面的知识,以及会用两句Custom Script,就足够了。按刚才阉割全局变量的方法,我们可以轻松把一个技能变成可以多人同时使用。

        综述,使用Custom Script是解决全局变量与局部变量矛盾最好的方法。
回复

使用道具 举报

 楼主| 发表于 2008-1-10 19:12:59 | 显示全部楼层
                                                                                                  二、函数

序言

    大家看了第一章应该有些收获吧,在我看来,能在T里娴熟地使用局部变量,说明你已经算得上JASS菜鸟了,幸福吧!如果仅仅是为了做图的需求,那么,你已经没必要往下看了,有时候东西学多了,反而会迷失最初的目标。不过呢,有时候研究WE本身就是种乐趣,你说是么——认可这点的话,请继续看下去。这次要讲的,是JASS学习中相当重要的一个环节,学会了本章内容,你就可以随心所欲往自己图里添加自定义函数,达到JASS入门级别。


1.认识函数

  简单地说,函数就是构成地图这座JASS大厦的砖瓦;当我们把图里的JASS提取出来,我们会发现它是由N个函数堆切成的。变量,命令行,都仅仅是组成函数的元素。

  那么,我们平时在T里使用的触发器,也是函数么——当然,我们看下面这个名为“YDJASS”的T:


[trigger] YDJASS
    事件
        单位 - 网友 网友查看帖子结束
    环境
        (物品(最后被看的帖子)的类型) 等于 YD男 – JASS教程
    动作
        游戏 - 对 (所有网友) 显示文本: Everguo is handsome![/trigger]

  恩,我已经听到大家的赞美了,谢谢~(脸红ing)

我们把它转成J,可以得到

[codes=jass]function Trig_YDJASS_Conditions takes nothing returns boolean
  if ( not ( GetLearnedSkillBJ() == 'YDJASS' ) ) then
      return false
     else
      return true
  endif
endfunction

function Trig_YDJASS_Actions takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_010" )
endfunction

//===========================================================================
function InitTrig_YDJASS takes nothing returns nothing
    set gg_trg_YDJASS = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_YDJASS, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddCondition( gg_trg_YDJASS, Condition( function Trig_YDJASS_Conditions ) )
    call TriggerAddAction( gg_trg_YDJASS, function Trig_YDJASS_Actions )
endfunction[/codes]
  我们可以看到,这段代码包含三个函数:第一个Trig_YDJASS_Conditions,是由环境“(查看的帖子) 等于 YD男 – JASS教程”转来的;第二个Trig_YDJASS_Actions,当然是动作了;最后一个InitTrig_YDJASS比较有意思,它先是生成一个名为gg_trg_YDJASS的触发器,然后给该触发器添上事件,条件和动作——我们看到之前的Trig_YDJASS_Conditions跟Trig_YDJASS_Actions分别作为条件和动作被添加到gg_trg_YDJASS中。

  大家现在相信JASS大厦都是由函数一砖一瓦堆砌的吧!下面我们来进一步认识下这座JASS大厦。好了,互动环节开始,现在我教大家一个很猥琐的查看地图代码的方法。

  大家请打开WE,新建张空白地图,然后随意新建个T(有兴趣的话还可以再自定义个全局变量,或随意放置一个单位在地图上),随意加上事件条件动作。然后保存这张地图。

  (准备好了么,开始YD了~)

  现在我们就像个裁缝,我们做好衣服后,把衣服送去更衣间,里面的美女把衣服穿上,走出来——然后,我们看到的是穿好衣服的美女;至于这衣服下面的美女是什么样,这衣服又包裹了怎样的秘密,我们很想知道,嘿嘿~(鼻血ing)

  (作战开始了~)

  我们在衣服里,偷偷地缝枚小钉子进去,然后把衣服送进更衣室;时间一秒一秒过去,我们等着看好戏。突然,“啊”,美女衣衫不整地冲出来,花容失色,指着衣服道:

  “这衣服上怎么有根刺?”

  “哪里?我看看……”

  于是我们假装看衣服,但目光却往美女裸露的胴体上瞄……

  (YD结束)

  好了,我们如何往T里加入“钉子”查看代码呢~~

  在刚才新建的T里,我们插入一行自定义代码“call a()”,整个T如下:

[trigger] YDJASS
    事件
        单位 - 网友 网友查看帖子结束
    环境
        (物品(最后被看的帖子)的类型) 等于 YD男 – JASS教程
    动作
        游戏 - 对 (所有网友) 显示文本: Everguo is handsome!
        自定义:call a() [/trigger]  

  “钉子”已经插好,我们保存地图,然后等待美女跑出来,一秒,2秒……
  这时屏幕出现个提示框“发生错误,YDJASS被关闭”,错误原因的提示是“预料中的一个函数名”——因为我们根本没定义“a”这个函数。

  我们选择确定后,一个“美女”衣衫不整地站在我们面前……

  “嘿嘿嘿嘿~”(奸笑)

  我们可以开始检查身体了~

  查看代码,我们看到最多的是“//”和“=”,在J中,“//”后面的东西会被程序无视掉,因此可以在“//”后写点说明之类;而这里“//”后一窜“=”或“*”,作用就是分割线。

  这段代码最开头是地图信息,显示地图名字、保存日期和作者;我们无视掉,继续往下看,会看到个“Global Variables”,这表示你下面将看到的是全局变量。

[codes=jass]
globals
// Generated
trigger gg_trg_YDJASS = null
endglobals[/codes]

  啊哈,快看,我们看到了熟悉的gg_trg_YDJASS,原来它也是个全局变量。正如一般的自定义全局变量有“udg_”的前缀,类型为触发器的全局变量,有“gg_trg_”的前缀;当我们新建名为“YDJASS”的触发器时,会自动生成一个“gg_trg_YDJASS”的全局变量,然后执行“set gg_trg_YDJASS = CreateTrigger()”,即生成一个触发器,用全局变量gg_trg_YDJASS保存。

  这时,如果你起先自定义过全局变量,那么你也可在这找到它,比如:

[codes=jass]
/ /User-defined
  unit udg_piaoke = null
  unit udg_WeiChunhua = null[/codes]

  啊,这不是我在第一章讲变量时定义的两个单位型全局变量么~

  之前如果有朋友在地图上放置了单位,那么就要注意了,我们会看到个“Unit Creation”;譬如,我们随便在地图里放置一个红色玩家的女巫(为什么放女巫呢,因为她的打扮很像应召女郎),那么我们会发现
[codes=jass]
function CreateUnitsForPlayer0 takes nothing returns nothing
    local player p = Player(0)
    local unit u
    local integer unitID
    local trigger t
    local real life
    set u = CreateUnit( p, 'hsor', 185.3, -1169.2, 39.211 )
endfunction[/codes]
在一连窜的定义后,它执行了“set u = CreateUnit( p, 'hsor', 185.3, -1169.2, 39.211 )”,这行代码的作用是生成一个类型为'hsor'(女巫),在坐标(85.3, -1169.2),面向39.211,给玩家p(红色玩家一)。

  在J里,最小的整数是0,所以Player(0)表示红色玩家一;如果我们再放个蓝色玩家二的女巫,那么,我们会看到个名为“CreateUnitsForPlayer1”的函数,除了“local player p = Player(1)”,其他与先前的“CreateUnitsForPlayer0”别无二致。

继续往下,是个

[codes=jass]
function CreatePlayerUnits takes nothing returns nothing
    call CreateUnitsForPlayer0( )
    call CreateUnitsForPlayer1( )
endfunction[/codes]

  啊哈,我们似乎看到金字塔结构了,这两个函数原来是在CreatePlayerUnits里被调用,而CreatePlayerUnits一定会被后面更上层的函数的调用。

  继续检查身体继续检查身体~~

[codes=jass]
function Trig_YDJASS_Conditions takes nothing returns boolean
    if ( not ( IsUnitAliveBJ(GetTriggerUnit()) == true ) ) then
       return false
    endif
    return true
endfunction

function Trig_YDJASS_Actions takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_008" )
endfunction

//===========================================================================
function InitTrig_YDJASS takes nothing returns nothing
    set gg_trg_YDJASS = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_YDJASS, EVENT_PLAYER_UNIT_SPELL_FINISH )
    call TriggerAddCondition( gg_trg_YDJASS, Condition( function Trig_YDJASS_Conditions ) )
    call TriggerAddAction( gg_trg_YDJASS, function Trig_YDJASS_Actions )
endfunction[/codes]

   这不是先前那个T的代码么,原来它在这儿~
  现在我们知道这个位置是存放触发器代码的了。那么我们再看后面,有这行代码:

[codes=jass]
function InitCustomTriggers takes nothing returns nothing
    call InitTrig_YDJASS( )
endfunction[/codes]
  由于我们只建了一个名为“YDJASS”的T,所以在InitCustomTriggers函数下,只有一个call InitTrig_YDJASS();翻翻前面,InitTrig_YDJASS()这函数功能是生成触发器并给它添上事件条件和动作。

  再往下,是“Players”类,是设置玩家的颜色种族控制权等,跳过~

  就在差不多快结束的时候,我们看到“Main Initialization”,原来我们到金字塔的顶尖了。
[codes=jass]
function main takes nothing returns nothing
    call SetCameraBounds( -3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), -3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), -3328.0 + GetCameraMargin(CAMERA_MARGIN_LEFT), 3072.0 - GetCameraMargin(CAMERA_MARGIN_TOP), 3328.0 - GetCameraMargin(CAMERA_MARGIN_RIGHT), -3584.0 + GetCameraMargin(CAMERA_MARGIN_BOTTOM) )
    call SetDayNightModels( "Environment\\\\DNC\\\\DNCLordaeron\\\\DNCLordaeronTerrain\\\\DNCLordaeronTerrain.mdl", "Environment\\\\DNC\\\\DNCLordaeron\\\\DNCLordaeronUnit\\\\DNCLordaeronUnit.mdl" )
    call NewSoundEnvironment( "Default" )
    call SetAmbientDaySound( "LordaeronSummerDay" )
    call SetAmbientNightSound( "LordaeronSummerNight" )
    call SetMapMusic( "Music", true, 0 )
    call CreateAllUnits( )
    call InitBlizzard( )
    call InitGlobals( )
    call InitCustomTriggers( )
endfunction[/codes]

  我们发现,先前的CreateAllUnits()、InitGlobals()、InitCustomTriggers()都在这被调用。站在着座JASS大厦的顶层,你有什么感想呢~

  “YD男,我想的是,像金刚一样站在全世界最高的楼顶为心爱的女人打飞机~”

  “Y的,你应该说‘这些代码没想象的复杂,YD男你太棒了我爱死你了~’”

  现在我们回顾以前听过的一些做图方面的经验之谈,当时心中打满了问号:

  为什么我们要把触发事件“地图初始化”改成“当流逝的时间是0.00秒”

  为什么要尽可能减少地图初始放置的单位和减少T的数量


  现在我们结合本篇教程分析:

  当我们在Loading时,大概在读到70%-80%时,会卡一下,这时程序执行到了Main Initialization这一步。(一个典型的例子,对一个播放音乐的T,如果事件是“地图初始化”,那么这时我们已经可以听到音乐了;而事件是“当流逝的时间是0.00秒”,却要等到Load完成进入游戏之后才能听到音乐。)这时,T越多,地图里放置的单位太多,对应的函数就越冗长,势必会影响Loading速度,因此建议能合并的触发器一定要合并,地图里初始放置的单位改成在游戏里生成,不要使用“地图初始化”做触发事件,等。

  最后,要说明的是,花这么多时间来介绍函数,是为了增进大家对函数的好感,为下面讲解“如何YD地使用函数”营造良好的氛围。末了,之前大家或许听过,在JASS高手面前,任何加密手段都是无效的,因为如何加密都隐藏不了地图的JASS代码,而他们能从容地修改代码达到改图的目的——你,以前是不是觉得那些用JASS改图的人高深莫测呢;现在看来,他们也不过是坨SHIT,对吧!

  我不希望有人看了这教程去研究改图,当我正要展开笔墨对那些函数做进一步详尽的说明时,突然意识到,这可能会误导个别人走上改图之路,因此讲解点到即止,很多函数一笔带过。我很鄙视改图的人,我要在他们屁股上烫字,左屁股蛋烫“猪不”,右屁股蛋烫“狗如”,看他们还改图不。

  “YD男,你屁股上怎么有‘猪狗不如’四个字?”

  “厄……这个,其实《春雪》就是拿ORC改的。”

  “这不算改图吧?”

  “可是,为了测试AI,我特意做了面盗版的ORC,一输入‘woyongzhenghaixiayongguodeweishengjindangzhentou’,就有十万金钱……”

  “……”

  “还好我没说改图要切JJ谢罪……”


2.函数的结构
function
<函数名> takes
<参数类型1> <参数名1>,

<参数类型2> <参数名2>,
……, <参数类型N> <参数名N>, returns nothing(或unit,boolean,integer……)

……//函数主体

//return

endfunction


  这里传递的参数,可以没有(用nothing表示),也可以有几个;返回值可以没有(即不返回任何值,仍用nothing表示),也可以是任何一种类型,但只能返回一个。

1>参数的传递

  在前面介绍函数的时候,我们看到,除了触发器条件的函数会return boolean外,其余函数全是takes nothing returns nothing;其实这类不传递任何参数的函数最SB,为什么这么说呢,我们看看先前的那个T,那个会显示“Everguo is handsome!”的T,其实,我们可以写个函数:
[codes=jass]
function EverguoIsHandsome takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), "Everguo is handsome" )
endfunction[/codes]
  于是,只要call EverguoIsHandsome(),就会出现"Everguo is handsome"在屏幕上。

  如果你抗议,你说,人家泄停封哥哥才帅呢,那好吧,我们写个XieTingfengIsHandsome:

[codes=jass]
function XieTingfengIsHandsome takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), "XieTingfeng is handsome" )
endfunction[/codes]
  于是又有人抗议了,说我是刘德华的粉丝我叫杨丽鹃刘德华才是最帅的……

  如果,每一个帅哥,都要写个帅哥函数,那N个帅哥就要写N个函数,是不是很麻烦呢!

  于是,我们写个帅哥函数:

[codes=jass]
function ShuaigeIsHandsome takes unit shuaige returns nothing
    call DisplayTextToForce( GetPlayersAll(), ( GetUnitName(shuaige) + "is handsome!" ) )
endfunction[/codes]

然后,我们调用
  call ShuaigeIsHandsome(everguo)
  call ShuaigeIsHandsome(XieTingfeng)
  call ShuaigeIsHandsome(LiuDehua)
然后屏幕上会出现  
  everguo is handsome
  XieTingfeng is handsome
  LiuDehua is handsome

看到了吧,这就是参数传递的好处;现在大家知道我为什么说JASS大厦里的函数很SB了么,比如在“Unit Creation”里
[codes=jass]
function CreateUnitsForPlayer0 takes nothing returns nothing
    local player p = Player(0)
    local unit u
    local integer unitID
    local trigger t
    local real life
    set u = CreateUnit( p, 'hsor', 185.3, -1169.2, 39.211 )
endfunction

function CreateUnitsForPlayer1 takes nothing returns nothing
    local player p = Player(1)
    local unit u
    local integer unitID
    local trigger t
    local real life
    set u = CreateUnit( p, 'hsor', 185.3, -1169.2, 39.211 )
endfunction

function CreatePlayerUnits takes nothing returns nothing
    call CreateUnitsForPlayer0( )
    call CreateUnitsForPlayer1( )
endfunction[/codes]
完全可以这样写:
回复

使用道具 举报

 楼主| 发表于 2008-1-11 03:02:58 | 显示全部楼层
[codes=jass]
function CreateUnitsForPlayer takes player p returns nothing
    local unit u
    local integer unitID
    local trigger t
    local real life
    set u = CreateUnit( p, 'hsor', 185.3, -1169.2, 39.211 )
endfunction

function CreatePlayerUnits takes nothing returns nothing
    call CreateUnitsForPlayer(Player(0) )
    call CreateUnitsForPlayer(Player(1) )
endfunction[/codes]
为了进一步说明参数传递的好处,我们看下面的例子:

  话说自从韦春花招了两个伙计后,丽春院生意非常红火,来往的客人络绎不绝;不过麻烦事又来了……

  丽春院的接待处在一楼,客房包厢在二楼;客人在一楼选择好姑娘和服务类型后,付了银子,拿了交费凭证后上二楼享受服务。

  西门大官人来了,点春三十娘,包夜;由于他是开绸缎庄的,所以小二A拿出块圆圆的红布给他。


  需要说明下,布代表西门庆,也只有西门庆本人才能凭布享受到服务——因此即便田伯光偷了抢了这块布,也是享受不到服务的;红色代表春三十娘,黄色代表鸣人,蓝色代表韦春花;而园形代表包夜,三角形代表喝花酒,方形代表听十八摸。

  当他上楼后,把信物交给小二B;小二B一看是西门庆本人,没错;再看布是红色的,说明他要找的是春十三娘;最后布的形状是圆的,代表他选择的服务是过夜。于是小二B就领着西门庆找春三十娘去了。

  田大爷找鸣人喝花酒,由于他的职业是采花贼,所以小二A给了他朵黄色的三角形花瓣。

  这样,咋看是没什么问题,可是……

  假设有D名客人,S名妓女,B种服务,那么,小二A一共要准备DxSxB种信物,太麻烦了……

  正当韦春花一筹莫展,这时一张俏皮的笑脸出现——我们YD与智慧的化身——韦小宝。

  听完娘亲的苦衷,韦小宝哈哈大笑道:

  “这有何难,直接给他们一张嫖妓通行证不就得了!”

  韦小宝的办法是,只用一种竹简,当西门庆点春三十娘包夜,就在那简上写下:

 “西门庆,春三十娘,包夜。”

  随着嫖妓通行证的推出,丽春院生意更好了。

  现在我们回头看丽春院的例子,之前他们采用的方法,是针对不同的客人,不同的妓女,不同的服务,写takes nothing的函数;正如前面所说,D个客人,S个妓女,B种服务,有DxSxB种组合,需要DxSxB个函数,这方法够SB吧——那座JASS大厦里采用的就是这方法。

  韦小宝的方法是,写个名为“PiaoJiTongXingZheng”的函数,传递三个参数(unit 客人,unit 妓女,string 服务),比如

[codes=jass]call PiaoJiTongXingZheng(Xi_Men_qing, Chun_San_shi_niang, "guoye")
call PiaoJiTongXingZheng(Tian_Bo_Guang, XuanWoMingRen, "huajiu")[/codes]

  如此一来,只要一个函数,就可实现之前DxSxB个函数才能实现的功能,是不是很酷!(DSB就是大SB的意思,大家看出来了吧!)

  可见,参数传递是多么的有用,而takes nothing是多么SB!
  可惜,参数传递有它的局限性,比如我们可爱的TimerStart(native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing),它的形式是TimerStart(计时器, 实数, 真值型, 进程)

例如

[codes=jass]call TimerStart(timer, 1 , true, function kill)  //每隔一秒执行一次kill函数[/codes]
这里的kill,只能是takes nothing型的SB函数,不能传递任何参量。

        有人说,如果TimerStart能这么用
[codes=jass]call TimerStart(timer, 1 , true, function kill(everguo))[/codes]
那么,使用缓存的人一定会少很多;的确,正因为这个缺陷,TimerStart才不得不跟Game Cache和Retuen Bug玩3P。

  (Game Cache、Retuen Bug和TimerStart,将作为重点在后面的章节详细讲解。)

  最后要说明的是,参数传递后的变量,是局部变量,大家放心使用吧,没花柳的~

附上以前写的一个苟合函数:


[codes=jass]function GouHe takes unit GouNan,unit GouNv,string gouhe returns nothing
    call IssueTargetOrder(GouNan, "gouhe", GouNv )
endfunction[/codes]

如果我们

    call GouHe(gounan,gounv,"kiss")  那么狗男会去亲狗女

如果
    call GouHe(gounan,gounv,"touch")  那么狗男会去摸狗女

但如果

    call GouHe(gounan,gounv,"XX")   就十八禁了~

    
2>返回值

  我们在前面看到,把触发器中的环境函数,有个returns boolean,干啥用的,举个YD的例子你就知道了。

鉴定性别函数

  GA至今有很多性别不明的人(说人妖会得罪人,说伪娘吧),而笔者曾被人妖骗走200RMB,教训啊!因此我们如何坚定网上的MM是不是人妖呢,小悟空说:“摸摸看啊~”

  好吧,我们就写个,能鉴定性别的函数~
[codes=jass]function RYIdentify takes unit MM returns boolean
      local unit it = MM
      local real x = GetUnitX(it)
      local real y = GetUnitY(it)
      local unit XiaoWukong = CreateUnit(Player(0),'zhengtai',x,y,0)      
      local boolean RY
      call IssueTargetOrder( XiaoWukong, "touch", it )
      
      if UnitHasBuffBJ(it, 'JJ') == true  then
            set RY = true        //如果有小JJ, 说明是RY,送他把剪刀自宫
        endif
            set RY = false      //如果木有小JJ, 说明是MM,可以泡~
      call RemoveUnit(XiaoWukong)      
      set it = null
      set XiaoWukong  = null
      return  RY    //返回值
endfunction[/codes]

在小悟空纯洁的手面前,任何RY都无处遁循~

  当有MM搭讪的时候,这“性别鉴定函数”就派上用场了,我们先定义一个真值型(boolean 布尔)全局变量RY,一个单位型全局变量MM,T的写法如下:
[trigger]LiaotTian
    事件
        玩家 - QQ信息–好友(美女头像) 当输入以下内容的聊天信息 HI~ 你好,可以聊聊吗?, 匹配方式为 完全匹配
    环境
    动作
        设置 MM = (触发好友)
        自定义:  set udg_RY = RYIdentify(udg_MM)
        如果 (所有条件是成立的) ,那么做 (动作) 否则[如果不成立]做 (动作)
            如果 - 条件
                RY 等于 True (真)
            那么(条件成立) - 动作
                单位 - 命令 MM 自宫  
            否则(条件不成立) - 动作
                游戏 - QQ信息 - 向好友发送语音(触发好友) 从  最后看帖不回的人 名字色狼: 播放 奸笑声音 并显示 好,来裸聊吧~.  编辑持续时间 : 添加 0.00 秒和 等待[/trigger]



大家一定很惊讶——“set udg_RY = RYIdentify(udg_MM)”,函数还能这样用啊,是的,因为RYIdentify函数运行后,会返回一个类型为真值的参数,我们这里把这个参数用一个真值型全局变量RY保存。
  当然,如同局部变量一样,在T里函数也只能写在自定义代码里。

那么,如果我们


              call RYIdentifyudg_MM


  只运行函数,不用变量保存它返回值,会出错么——不会,用call调用的函数会忽略返回值;这样说吧,当执行完函数,也就是在小悟空摸完MM后,我们用变量RY保存返回值,相当于问小悟空这MM是不是假的;如果我们只call函数,那么,相当于只叫小悟空摸MM而不去问他结果一样——这当然是可以的。

  好了,我们将YD进行到底吧~

  拿刚才的苟合函数举例,现在我们给它添上返回值,call GouHe(gounan,gounv,"XX")

returns unit //返回一个宝宝,当然,得至少十个月才可抱到它后~
returns item //返回一个用过的套套
returns rect //返回床,当然也可能是地板或草地
returns destructable //返回枕头或床头一些小玩意
returns sound //返回“嘿休”
returns race //返回禽兽
returns image //返回照片,真变态,还玩自拍
returns integer //返回苟合次数,如果他是传说中的“一夜十次郎”,这里会返回10
returns real //返回苟合时间,并且返回值如果小于某个值,那么,他需要服点药物或去看下医生

  以上只是开个玩笑,请无视……

  我想说明的是,所谓的返回参数,它的意思是,当函数体运行结束后,我们把运行得出的某个结果,按参数类型返回,就像那个人妖识别函数返回真值一样。

  有人或许会问
[codes=jass]function H2I takes handle h returns integer
    return h
    return 0
endfunction[/codes]
  这函数,怎么有两个return呢——这便是所谓的return bug了;关于它会在以后的章节里详细讲解,现在唯一可以说的是,一般函数里只有一个return,就像正常人只穿一条内裤;但return bug却有两个return,正如某些变态穿两条内裤一样~

  一般来说,自定义函数都是保存在自定义代码里的,而且先后顺序很重要。我们看到JASS大厦,最底层的函数,放最前面;处于大厦顶端的main函数,反倒在最后,为什么呢——因为程序执行是由上往下,被调用的函数,必须处于调用函数的前面。比如前面我们讲的在函数里插入“钉子”(断点)——call a(),由于在“Trig_YDJASS_Actions”前没有“a”这个函数,因此程序会判断出错;如果我们要补上函数“a”,那么必须把它放在“Trig_YDJASS_Actions”的前面——因此我们可以把它放自定义代码里。

  当我们把H2I函数放入自定义代码里,用先前加“钉子”的方法查看地图里JASS代码,我们会看到
[codes=jass]//***************************************************************************
//*
//* Custom Script Code
//*
//***************************************************************************
function H2I takes handle h returns integer
return h
return 0
endfunction

//***************************************************************************
//*
//* Triggers
//*
//***************************************************************************[/codes]


  也就是说,自定义代码(Custom Script Code)是放在触发器(Triggers)前面的,因此我们在T里调用自定义代码里的函数。

  不过,倘若将自定义函数全放入自定义代码,那么,一旦代码多了后,看起来很乱,也不方便日后查找修改,因此把自定义函数保存在触发器里,是个不错的选择。

  比如,我们像保存帅哥函数,如何保存在触发器里呢,我介绍下流程:

1>新建个T,名字叫“shuaige”,然后把它转成J,我们可以看到

[codes=jass]function Trig_shuaige_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_shuaige takes nothing returns nothing
    set gg_trg_shuaige = CreateTrigger( )
    call TriggerAddAction( gg_trg_shuaige, function Trig_shuaige_Actions )
endfunction[/codes]
可以说这完全是个空T,没有事件环境动作。

2>去掉多余的语句,最后只留下
[codes=jass]//===========================================================================
function InitTrig_shuaige takes nothing returns nothing
endfunction [/codes]


  也就是把Trig_shuaige_Actions这函数去掉,把生成触发器和给触发器添加动作的语句也去掉。

3>把需要保存的函数复制进去,如下
[codes=jass]//帅哥函数
function ShuaigeIsHandsome takes unit shuaige returns nothing
    call DisplayTextToForce( GetPlayersAll(), ( GetUnitName(shuaige) + "is handsome!" ) )
endfunction
//===========================================================================
function InitTrig_shuaige takes nothing returns nothing
    set gg_trg_shuaige = CreateTrigger( )
endfunction[/codes]


  OK,现在可以保存了~

  如此一来,我们很容易实现函数分门别类的保存,不必把所有函数全放在一堆;不过像H2I这样使用频率比较高的函数,最好还是放自定义代码保存比较好。

  如果你要使用这种用触发器保存函数的方法,需要注意一些地方。

  “YD男,你的演示我复制过去怎么用不了,说缺少XX函数,可我是全部复制过去的啊!”

  这样的问题我被问到过很多次,由于正确的解决方法有点复杂,因此我只好叫他们把我保存在触发器里的函数剪切下来,复制到自定义代码里。

  为何明明有那个函数,系统还会提示找不到那函数呢?还是之前提过的——调用函数时被调用函数与调用函数放置顺序的问题。


  当你把别人含有自定义函数的演示复制过来时,保存有被调用函数的触发器A,跟含有调用函数的触发器B,由于位置问题,系统在保存时可能会先保存触发器B,这时它当然会提示找不到调用的函数。

  解决方案如下:

1>在复制别人的T时,先复制保存触发器A,复制完毕保存地图;然后再复制触发器B。如此一来,就不会出错了。

2>当系统提示出错,找不到函数后,把触发器B禁用,保存地图;然后新建个触发器C,把触发器B的事件环境动作全复制或剪切,然后粘贴到触发器C里。删除触发器B,保存地图,系统不会提示出错,保存成功~


3>使用ExecuteFunc(native ExecuteFunc takes string funcName returns nothing),它的功能是搜索所有函数,使用方法为ExecuteFunc(”函数名”)。
  ExecuteFunc的优点是可以搜索调用在后面的函数,独立创建进程,使用wait不影响调用它的函数,能续承TriggerUnit之类的东东,但是……
它只能执行takes nothing(不传递任何参数)的SB函数,因此,要使用它,我们必须用到全局变量来传递数据。使用方法如下

[codes=jass]function NewShuaigeIsHandsome takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), ( GetUnitName( udg_boy) + "is handsome!" ) )
endfunction[/codes]

[trigger]YDJASS
    事件
        单位 - 网友 查看帖子结束
    环境
        (学习英雄技能) 等于 YD男 – JASS教程
    动作
        设置 hero = (触发帖子) 的发帖人
          自定义:  call ExecuteFunc(NewShuaigeIsHandsome)[/trigger]

4.函数的使用

  如果你真看明白了前面的内容,那我现在讲函数的使用无异画蛇添足;不过罗嗦点也没坏处,既然都看到这里,也在乎多看点。

  当我们保存好“shuaige”函数后,我们就可以用它在回帖的时候拍别人马屁;新建个单位型全局变量LZ,T的写法如下:
[trigger]YDTiezi
      事件
        单位 - 网友 查看帖子结束
    环境
    动作
        设置 LZ = (触发帖子) 的发帖人
        自定义:  call shuaige(udg_LZ)[/trigger]


或许有人觉得我这新建个全局变量没有必要,因为直接

  自定义:call shuaige(GetOwningLouzhu(GetTriggerTiezi()))


就可以了;但我觉得,对于初学者而言,还是不要写GetOwningLouzhu(GetTriggerTiezi())那么长的代码比较好。

  由上可见,在T里调用函数,跟在T里使用局部变量一样,也是使用“自定义”——说白了“自定义”就是在T里写JASS。调用形式无非call 函数名(参数),这里的参数可以是变量,也可以实质的东西,比如

  自定义:call shuaige(gg_unit_YDN_0001)

在地图上放置的单位,前面都有gg_unit_的前缀,它的形式是gg_unit_单位类型(代码)_编号;比如这里的YDN_0001,就是我——“YD一号”。
  好了,最后该教训下看帖子不回的人了,之前在第一章定义的芙蓉姐姐变量还没用呢,苟合函数再度登场,HOHO~
[trigger]punish
    事件
        单位 - 网友查看帖子结束
    环境
        ((触发单位) 是 回过帖的) 不等于 True (真)
    动作
        自定义:  local unit FRJJ
        设置 gounan = 最后不回帖的人
        设置 MM = (触发单位)
        自定义:  gounv = 芙蓉姐姐0001()
        自定义:  call GouHe(udg_gounan,FRJJ,”XX”)
        自定义:  setl FRJJ = null[/trigger]
郁闷,忘了之前定义的FRJJ是局部变量,因此只有这样写,不过正好可以不上关于参数传没讲透彻的地方——当我们把参数传递给函数后,这时即便我们把set FRJJ =gg_unit_linzhiying0001,那么此时看贴不回的人XX的,还是芙蓉姐姐;或许你要说,这是因为FRJJ是局部变量——那么,我们设置全局变量gounan =李亚鹏0001(情报),但这时,XX芙蓉姐姐的,还是看贴不回的人,因为,正如前面所说,参数传递后的变量,是局部变量,
[codes=jass]function GouHe takes unit GouNan,unit GouNv,string gouhe returns nothing
    call IssueTargetOrder(GouNan, "gouhe", GouNv )
endfunction [/codes]

在call GouHe(udg_gounan,FRJJ,”XX”)后,传递参数时,实际执行了下面的操作
[codes=jass]local unit GouNan = udg_gounan
local unit GouNv = FRJJ
local string gouhe = "XX"[/codes]

意思是说,在这里局部变量GouNan 、GouNan和gouhe保存了外部传递过来的三个参数,然后运行函数,此时,即便udg_gounan跟FRJJ两个变量保存的值改变,GouNan 、GouNan里的值是不会变的,因此,没什么能阻扰看贴不回的人跟芙蓉姐姐XX,直至死亡把他们分开~

  还有一点,如果local unit GouNan,那么函数执行完后,必须加上set GouNan = null,但传递过来的参数不必要set null,只有变量才需要清空。

  那么,这一章也讲完了,大家有兴趣的话,找未加密的《龙之舞者》看下,里面加入了大量的自定义函数。
回复

使用道具 举报

 楼主| 发表于 2008-1-11 17:18:23 | 显示全部楼层
三 、Game Cache



      不知不觉进入到最关键的环节了,谢谢大家支持。
  为什么这部教程没有关于JASS语法的介绍呢,因为其它教程已经讲解得足够详细,没必要赘述。本教程只是启发性,所以最好结合其他教程学习。
  J的大部分功能,用T都可实现;即便用T遇到瓶颈,也只需要加几句自定义代码进去就能解决。但是,J真正的精华所在,是T难以望其项背的。Game Cache、Return Bug和Timer可谓是JASS三剑客,只有当你能熟练地运用它们,你才能跨入JASS高手行列,才能享受JASS真正的高效快捷;那时你写的J,与由T转化而来的J,会有本质的区别。如果你想真正学会JASS,那么本章的内容不容错过。
  首先分别讲述下Game Cache、Return Bug和Timer的原理和使用方法,照例我们先YD把,把Game Cache想象成一位美丽却有些滥交的美女,把Return Bug想象成一位略带点邪气的帅哥,把Timer想象成一位正派却又怀才不遇的英俊小生——他们三人的恩恩怨怨,情感瓜葛(琼瑶大婶的拿手好戏),将在后面详细阐述。
  本想把Game Cache、Return Bug和Timer放在一章里讲的,因为他们三个实在是谁也离不开谁,是个有机的整体;不过今天发现,Game Cache这块的内容比想象的多,待到把Return Bug和Timer也讲了,那这章未免也太长了;因此,把Game Cache单独列出来重点讲。

1.什么是Game Cache

  有人把Game Cache比作存放东西的仓库,有一些贴切;但我喜欢把它比作一个笔记本,比如木子美的性爱日记~
之所这么比喻的原因,是因为Game Cache的作用像帐本或者日记本,是将一些参数记录后保存。
  既然Game Cache也是保存数据,那么,它与变量的不同在哪里;我们看下面的例子:


1>全局变量

  我们把它想象成小黑板,在上面写东西,大家都能看到(比如你在黑板上写道“猪肉怎么涨价了”);但小黑板容量有限,一次只能写一句话——当你想在上面写新的内容,那么,必须先把原来的内容擦掉(比如第二天你发现黑板上的话变成了“因为楼猪是母猪中的战斗猪”)。不过呢,小黑板最大的好处是可反复使用;因此如果条件允许的话,每个人都配个小黑板,那感情挺好,大家各写各的,互不影响。
  全局变量的好处自然是各个函数体都能调用,但缺点是一旦用来保存新的东东,之前保存的数据就会丢失;在使用数组后,这种情况有所缓解,却仍解决不了根本问题。(每个人都有自己的小黑板后,的确不用担心别人会擦掉你写的东西;但缺点是,你仍旧一次只能在黑板上写一句话。)

2>局部变量

  我们把它想象成便条本,大家需要时就撕下一张,各写各的,互不干涉;而且,你还可以一次撕下几张,在每张上面写上不同的话——这是小黑板做不到的。但是,谁都不知道别人写的什么,而且便条纸难以保存,一般用完就丢了。

  当几个函数体调用同一个函数,或,一个函数体连续调用同一个函数时,我们丝毫不担心会发生群P或双飞,也不担心会传播花柳——因为我们使用的是局部变量。但遗憾的是,局部变量也没有全局变量的优点,正如便条纸即不利于保存又不方便交流一样。

  (王道,是什么……在第一章讲变量时,我说阉割全局变量是王道,那不过是忽悠不愿深入学习Jass的人罢了,真正精通J的人才不会那样干呢;铺垫就做到这,接下来该介绍我们的主角了。)

  (既要像小黑板一样,能让大家都看到,又要像便条纸一样,满足多人使用;能做到这点的,我们很自然就想到了——签名簿、日记本、帐本、笔记本……等等。我们还是拿日记本来举例吧)


3>Game Cache

  我们把它想象成公用的日记本,大家都可使用——不同人在不同页面上写下自己的东西,在每页不同的行写下不同的话,写完后内容也不会丢失;而且,你可以查看或删改别人的内容,譬如把他们yy的“林志玲”改成“芙蓉姐姐”~
  Game Cache同时具备了全局变量与局部变量的优点,但访问速度比起变量来要慢得多(用日记本记东西明显不如用小黑板或便条纸方便),使用不当还会生成数量很大的string对象(参看Red_Wolf的GameCache应用教程,也有人提出用别的方法来替代Game Cache),但总的说来, Game Cache是很强大的,尤其跟她两个炮友Return Bug和Timer苟合后,简直是无敌的,甚至可以发挥出1+1+1>10的功能来。

  Game Cache又跟变量不同,它能储存的数据类型只有五种:unit(单位)、integer(整数)、real(实数)、string(字符窜)、boolean(真值或布尔);功能很拙,是吧,因为BLZ最初设计时就没想过让它来替代变量,而仅仅是让它在战役过面时在两张地图之间传递数据:因为变量只能本图定义本图使用,不可能被别的图调用;但只要此刻游戏运行时缓存里有XX.w3v这个东东,你就可以调用XX.w3v里的数据——因此你在上张图里创建游戏缓存,把数据写入,那么在下一张图就可从这缓存里读出数据。SaveGameCache(存储游戏缓存)和ReloadGameCachesFromDisk(从硬盘读取所有存储数据)这两个函数只用于战役,这里就不讲了。 
 
  Game Cache记录integer(整数)、real(实数)、string(字符窜)、boolean(真值或布尔)四种类型的数据比较好理解,跟用变量保存数据大同小异;但记录unit(单位)则就不一样,这点将在后面结合例子讲解。

2.记录

  记录数据的形式是Store_Type,函数如下:
[codes=jass]
    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    //存储真值(布尔)
[/codes]

  那么,如何记录呢,我们来看下面的例子。由于剧情需要,我们这章的主角就不再是韦春花,而是木子美。

  “多谢YD男大人,我老婆子活了大半辈子,还是第一次当主角,大恩大德老身没齿难忘;下次大人来丽春院,给你打八折……”

  “……滚!!!”


  (开始YD了~)

  木子美是个前卫的女人,这年头,女人越贱越容易出名;跟芙蓉姐姐和张钰不同,木子美没资本,既没曲线(她是两条平行线),又没脸蛋(遭遇不了潜规则);但是,她文笔很好,所以最终还是出名了——她把姘头一些资料记录到日记本上,写了本性爱日记。

让我们一起来看看木子美如何写性爱日记吧!

  首先,木子美要写日记,得先有日记本吧——所以我们得先创建个游戏缓存,用个全局变量GC来保存。

[trigger]SexNote
    事件
        地图初始化
    环境
    动作
        游戏缓存 - 创建游戏缓存 SexNote.w3v
        设置 GC = (最后创建的游戏缓存) [/trigger]

好了,现在我们就有个名为“SexNote”的缓存,哦不,日记本了。

  现在美美要开始记录了。第一个被记录的,是李亚鹏 ——年龄34(乱编的),身高1米78,未婚,等等。

记录如下:

[codes=jass]
call StoreUnit(udg_GC,"LiYaPeng", "DNA",gg_unit_LiYaPeng_0001)
call StoreString(udg_GC,"LiYaPeng", "profession", “player”)
call StoreInteger(udg_GC,"LiYaPeng", "age",34)
call StoreReal(udg_GC,"LiYaPeng", "stature",1.78)
call StoreBoolean(udg_GC,"LiYaPeng", "marriage", false)[/codes]

其中udg_GC便是日记本了,准确说是名为“SexNote”的日记本,因为木子美的日记本不止一个(她也许也写了一些比较纯洁的日记),所以你必须说清楚我们鸭棚格格到底在哪个日记本上(要说明是在性爱日记上)。木子美翻开第一页,在页眉上写下LiYaPeng,然后在下面写:

    DNA  LiYaPeng        //夹在日记本里,头发或指甲,含有人体DNA的东西
    Profession player      //职业  演员
    age  34           //年龄  34
    stature 1.78         //身高  1米78
    marriage false       //婚姻  否

如此以来,鸭棚格格的资料就保存下来了;记录,就这么简单。
  需要特别指出的是,StoreUnit与用单位变量保存单位完全不同;当游戏缓存记录单位时,其实记录的是单位类型、携带的物品(有物品栏的单位)和英雄等级和经验值(英雄单位),单位的HP和MP则没有记录;比如我们把跟春三十娘做了几次后快要精尽人亡又染上了花柳的西门庆call StoreUnit,那么,RestoreUnit后的西门庆,又是条生龙活虎的汉子。因此,我们可把游戏缓存看作一个人体克隆机,StoreUnit其实是储存单位的DNA,然后RestoreUnit是根据该DNA克隆出来的克隆人;至于本体在StoreUnit时肾亏了,或是染上了花柳,RestoreUnit出来的克隆人却很健康——因为肾亏或花柳不会影响DNA结构。

  所以这里记录的“DNA LiYaPeng”,其实是鸭棚格格的DNA;当以后木子美想念鸭棚格格了,就可以拿他DNA克隆出鸭棚格格2号来。听起来很玄,但科技发达了,克隆技术问世是迟早的事——这也是我至今保存有以前喜欢的女孩的头发的原因:)


3.查看与使用

  木子美写日记,可不仅仅是自娱自乐,她是要拿给我们看的。如何查看与使用呢,我们先看HaveStored_Type系列函数(好吧,我承认讲HaveStored_Type系列函数是在凑字数^-^ 这类函数根本用不着)。

1>HaveStored_Type系列函数

函数如下  
[codes=jass]HaveStoredInteger(native HaveStoredInteger takes gamecache cache, string missionKey, string key returns boolean)
HaveStoredReal(native HaveStoredReal takes gamecache cache, string missionKey, string key returns boolean)
HaveStoredBoolean(native HaveStoredBoolean takes gamecache cache, string missionKey, string key returns boolean)
HaveStoredString(native HaveStoredString takes gamecache cache, string missionKey, string key returns boolean)
HaveStoredUnit(native HaveStoredUnit takes gamecache cache, string missionKey, string key returns boolean)
HaveStoredValue(function HaveStoredValue takes string key, integer valueType, string missionKey, gamecache cache returns boolean)[/codes]

不用我依次讲解了吧~ HaveStored_Type用来判断是否有存有Type类数据的记录,比如我们用HaveStoredBoolean(udg_GC,"LiYaPeng","AIDS")来判断性爱日记上是否有鸭棚格格有无爱滋病的记录,会返回一个false,因为之前木子美没有记录过。

  那么我们讲下HaveStoredValue(继续凑字)吧,它定义如下
[codes=jass]function HaveStoredValue takes string key, integer valueType, string missionKey, gamecache cache returns boolean
    if (valueType == bj_GAMECACHE_BOOLEAN) then
        return HaveStoredBoolean(cache, missionKey, key)
    elseif (valueType == bj_GAMECACHE_INTEGER) then
        return HaveStoredInteger(cache, missionKey, key)
    elseif (valueType == bj_GAMECACHE_REAL) then
        return HaveStoredReal(cache, missionKey, key)
    elseif (valueType == bj_GAMECACHE_UNIT) then
        return HaveStoredUnit(cache, missionKey, key)
    elseif (valueType == bj_GAMECACHE_STRING) then
        return HaveStoredString(cache, missionKey, key)
    else
        // Unrecognized value type - ignore the request.
        return false
    endif
endfunction[/codes]
其中几个bj_变量的值分别是
[codes=jass]    constant integer bj_GAMECACHE_BOOLEAN = 0
    constant integer bj_GAMECACHE_INTEGER = 1
    constant integer bj_GAMECACHE_REAL = 2
    constant integer bj_GAMECACHE_UNIT = 3
    constant integer bj_GAMECACHE_STRING = 4[/codes]

  因此我们把HaveStoredBoolean(udg_GC,"LiYaPeng","AIDS")换成HaveStoredValue表示就是HaveStoredValue("AIDS",0,"LiYaPeng", udg_GC)

好了,凑字结束;为虾米说讲解HaveStored_Type是在凑字呢,因为我们在使用时根本用不着HaveStored_Type:当我们读取GameCache里数据时,如果没有这项记录,那我们将得到默认值(0或null);只有在很特殊的情况下,才需要用到这函数。因此像这样的冷门函数,我们完全可以无视。



2>使用

  终于,讲到如何使用GameCache了;一般说来,我们是先定义个变量,用它来保存从GameCache读出的数据。除unit外,integer、real等类型数据都是使用GetStored_Type类函数从GameCache中读取数据。

以下是几个GetStored_Type类函数:
[codes=jass]GetStoredBoolean(native GetStoredBoolean takes gamecache cache, string missionKey, string key returns boolean)
GetStoredInteger(native GetStoredInteger takes gamecache cache, string missionKey, string key returns integer)
GetStoredReal(native GetStoredReal takes gamecache cache, string missionKey, string key returns real)
GetStoredString(native GetStoredString takes gamecache cache, string missionKey, string key returns string)
RestoreUnit(native RestoreUnit takes gamecache cache, string missionKey, string key, player forWhichPlayer, real x, real y, real facing returns unit) [/codes]

我们可以这样
[codes=jass]    local boolean a = GetStoredBoolean(udg_GC,"LiYaPeng", "marriage")
    local integer b = GetStoredInteger(udg_GC,"LiYaPeng", "age")
    local real c = GetStoredReal(udg_GC,"LiYaPeng", "stature")
    local string d = GetStoredString(udg_GC,"LiYaPeng", "profession")
    local unit e = RestoreUnit(udg_GC,"LiYaPeng", "DNA",Player(0),0,0,0) [/codes]

喏,现在我们就从GameCache里读出了数据,仅仅需要注意变量的类型要与GetStored_Type的类型相同即可;在这里又要再强调次读取unit类记录,其实RestoreUnit(恢复单位)是生成一个单位,跟CreatUnit差不多;区别在于CreatUnit是根据指定的单位类型生成单位,而RestoreUnit是根据之前存储的单位信息生成单位——可以这样比喻,CreatUnit类似女娲造人,RestoreUnit则像受孕怀胎。好吧,我承认这样比喻很猥琐,仅仅是为了增加教程的YD含量;其实RestoreUnit就是个克隆机,输入之前保存的DNA,克隆出相应的个体。

  正如CreatUnit有CreateNUnitsAtLoc(生成单位面向角度)和CreateNUnitsAtLocFacingLocBJ (生成单位面向点)两个派生函数,RestoreUnit也有RestoreUnitLocFacingAngleBJ(恢复单位面向角度)和RestoreUnitLocFacingPointBJ(恢复单位面向点)。

  好吧,我承认讲解unit类数据的存储和读取也是为了凑字,因为后面我们几乎用不到。不过呢,面对一个绝色佳丽,我们不应该一来就推倒她发泄兽欲;倘若先跟她聊聊天,培养下感情,岂不更好。所以呢,本教程跟其它教程不同,没有一来就讲GameCache的使用,而是讲了相关一些东西,让大家对GameCache这薄命红颜了解得更多。

4.修改

  如果我们想修改缓存里的数据,该怎么做呢?

  比如,鸭棚格格和王菲姐姐结婚了,也就是要把在“婚姻”那栏里把“未婚”改成“已婚”;那么,我们可以这样  

[codes=jass]    call StoreBoolean(udg_GC,"LiYaPeng", "marriage", ture) [/codes]

  也就是说,在原来的位置,重新输入数据,把原来的数据覆盖即可。

5.清除

  如何清除缓存呢,当清除GmaeCache中某个类的某项,也就是日记本中某页上某一行的数据,我们可以使用FlushStored_Type,如下: [codes=jass]
FlushStoredInteger(native FlushStoredBoolean takes gamecache cache, string missionKey, string key returns nothing)
FlushStoredBoolean(native FlushStoredInteger takes gamecache cache, string missionKey, string key returns nothing)
FlushStoredReal(native FlushStoredInteger takes gamecache cache, string missionKey, string key returns nothing)
FlushStoredString(native FlushStoredInteger takes gamecache cache, string missionKey, string key returns nothing)
FlushStoredUnit(native FlushStoredInteger takes gamecache cache, string missionKey, string key returns nothing) [/codes]

具体怎么使用呢,继续YD~

  鸭棚格格打电话来了,说阿美啊你怎么可以记下我年龄呢,这是秘密啊让我女粉丝知道会伤心的……

  于是木子美就用橡皮擦将年龄那行擦掉

[codes=jass]    call FlushStoredInteger(udg_GC,"LiYaPeng", "age" ) [/codes]

  ~0~啊噢,我们不知道鸭棚格格的年龄了 ~0~

  如果我们要清除GmaeCache中某类的所有数据,也就是日记本中某页的的数据,我们可用FlushStoredMission(native FlushStoredMission takes gamecache cache, string missionKey returns nothing),例子如下:

  才刚放下电话不久,这时,鸭棚格格的弟弟鸭肠又打来电话,说我哥都结婚的人了大姐你就行行好不要写他了行不……

  于是木子美只好把这页撕了

[codes=jass]    call FlushStoredMission(udg_GC, "LiYaPeng") [/codes]

  如此一来,关于鸭棚格格的资料就全米了~0~

  那么要清空并删除整个GameCache呢,我们用FlushGameCache(native FlushGameCache takes gamecache cache returns nothing) ,好吧,最后一个例子了……

  木子美年纪不小了,她决心从良,找个人嫁掉,于是,她烧了这本性爱日记……   
  
[codes=jasss]    call FlushGameCache(udg_GC) [/codes]

  那么,再见了,性爱日记,谢谢你教会我们使用GameCache ^-^!

6.其它

  最后我想再补充一点内容,这些内容你可看作是我凑字的无聊举动,也可以忽略掉。
  当我们在GameCache中某类某项保存好一个类型的数据了,为什么在读取时,必须严格地按类型读取?
  GetStoredInteger是什么意思——它是“读取XX缓存XX类XX项中的整数型数据”,我之所以强调是“整数型数据”,因为完全可以从该项目中读出其它类型数据。我们看下面例子:

  木子美在日记上写下:

    age —— 34  2.00  “啦啦啦”  false // "age" 是那行的名字 而非记录的内容

或许你已经坐不住了,“还能这样写啊!”我先不解释为什么,往下看:
[codes=jass]
    local boolean a = GetStoredBoolean(udg_GC,"LiYaPeng", "age")
    local integer b = GetStoredInteger(udg_GC,"LiYaPeng", "age")
    local real c = GetStoredReal(udg_GC,"LiYaPeng", "age")
    local string d = GetStoredString(udg_GC,"LiYaPeng", "age") [/codes]
这时,我要问了,abcd四个变量里保存的数据是什么?没错,它们就是

    a=false  b=34  c=2.00  d=”啦啦啦”

我们可没有规定,在日记本上某行,只能写数字或文字,类似,我们也没规定GameCache中某类某项只能存储某种类型数据,因此,下面的写法是完全合理的
[codes=jass]
    call StoreInteger(udg_GC,"LiYaPeng", "age",34)
    call StoreReal(udg_GC,"LiYaPeng", “age”,1.78)
    call StoreString(udg_GC,"LiYaPeng", "age", "啦啦啦" )
    call StoreBoolean(udg_GC,"LiYaPeng", "age", false) [/codes]
这样一来,日记本上age那一栏会出现

    age —— 34  2.00  “啦啦啦”  false

但需要提醒的是,如果在那行添上添上个数字,如

    age —— 34  2.00  “啦啦啦”  false  25

这时,GetStoredInteger(udg_GC,"LiYaPeng", "age")的值,就不是34而是25了;在同一个类同一项中,同类型的数据只能保持一个,后面输入的数据,会将前面同类型的数据覆盖。

  大家别会错意,我介绍这种写法,并非倡导大家“节约纸张“把日记本上每一行写满,相反我不提倡这么写,一来容易混淆,二来清除缓存数据时很不方便(FlushStored_Type函数是清除整个项而不是只清除项中某类型数据,如果你只想清楚该项中某个数据,结果会把整个项都清理掉)。

7.部分应用

  之前流传一种说法,可用GameCache来检测游戏主机;究竟传说是否属实呢,我们来看最后要讲的一个函数——SyncStored_Type函数。

  “听都没听过,这又是冷门函数吧,YD男你又在凑字?”

  “汗,这的确是冷门函数,但讲它却不是为凑字——相信我吧,撒谎这种事,我经常做的,但偶尔也会良心发现。”

函数形式如下:
[codes=jass]
SyncStoredBoolean(native SyncStoredBoolean takes gamecache cache, string missionKey, string key returns nothing)
SyncStoredInteger(native SyncStoredInteger takes gamecache cache, string missionKey, string key returns nothing)
SyncStoredReal(native SyncStoredReal takes gamecache cache, string missionKey, string key returns nothing)
SyncStoredString(native SyncStoredString takes gamecache cache, string missionKey, string key returns nothing)
SyncStoredUnit(native SyncStoredUnit takes gamecache cache, string missionKey, string key returns nothing) [/codes]
这函数作用是同步缓存中的数据,来看例子:

  假设有有四个人用ABCD四台电脑联网打魔兽,他们分别是玩家一二三四;我们知道,联网的话,同步是个问题,各台电脑缓存中的数据也可能不同步;让我们看看Red_wolf举的缓存数据不同步的例子: [codes=jass]
   local string s1="a"
local string s2="b"
if GetLocalPlayer()==Player(0)then
   call StoreString(udg_GC,"X","Y",s1)
  else
   call StoreString(udg_GC,"X","Y",s2)
  endif [/codes]

这段代码是让玩家一的缓存里X类Y项里存储的值是a,其它玩家的则是b。

  在电脑A上,执行这段代码时候,“本地玩家是不是玩家一”,当然是了,于是电脑A的缓存里X类Y项里存储的值是a;在其它电脑上执行时,由于不满足条件,最后存储的值是b。

  那么让我们把目光回到同步数据上来,何谓同步数据?同步数据便是以主机的数据为准,根据这个原理,有人居然YD地用它写出了检测主机的代码。

好了,让我们来看下,这传说中能检测主机的代码
[codes=jass]
function GetHost takes nothing returns nothing
   local gamecache g = InitGameCache("map.w3v")
  call StoreInteger ( g, "Map", "Host", GetPlayerId(GetLocalPlayer ())+1)
  call TriggerSyncStart ()
  call SyncStoredInteger ( g, "Map", "Host" )
  call TriggerSyncReady ()
  set udg_Host = Player( GetStoredInteger ( g, "Map", "Host" )-1)
  call FlushGameCache( g )
  set g = null
endfunction[/codes]

相信既然大家都看到这来了,应该有一定基础能看懂些代码了吧——让我们一起来分析。

gamecache g = InitGameCache("map.w3v")    先定义一个游戏缓存类型的局部变量g,用它来保存生成的名为“map.w3v”的游戏缓存

  call
StoreInteger ( g, "Map", "Host", GetPlayerId(GetLocalPlayer ())+1)    这里的GetPlayerId(GetLocalPlayer ())+1,是获得玩家索引,譬如电脑A,由于是Player(0),执行完GetPlayerId(GetLocalPlayer ())+1后,值是1(执行过程:GetPlayerId(GetLocalPlayer ())+1——GetPlayerId(Player(0))+1——0+1——1)。其它BCD三台电脑缓存g中Map类Host项中的数据分别是2、3和4

  call
TriggerSyncStart ()    开始同步

  call
SyncStoredInteger ( g, "Map", "Host" )    同步缓存g中Map类Host项中的数据,如此一来,所有电脑上该项中的数据都是1了

  call
TriggerSyncReady ()    同步结束

  set udg_Host =
Player( GetStoredInteger ( g, "Map", "Host" )-1)    用个玩家型全局变量保存主机,由于之前说过,JASS中是以0为最小单位,所以这里要减1;由于1-1=0,所以最后udg_Host里保存的是Player(0)

  call
FlushGameCache( g )    清空并删除缓存

  set g = null    清空变量

  好了,现在真相已经浮出水面,主机就是……不要看别人,就是,电脑A(模仿金田一推理)。

  厉害吧,用缓存能解决检测主机这么经典的难题~

  不过最后要说的是……这段代码我没实验过,老狼也不知道它是否奏效,因此……大家揍我吧,别打脸~~ (这方法在实际运用中存在误差,为了比较准确判断出谁是主机,最好多判断几次,取出现频率最大的数。)

  缓存的使用,差不多全部讲完了,看,就这么简单~

综述

  终于把这章讲完了,但关于GameCache的故事却还没完,这章相当于仅仅介绍GameCache这位美女的身世,关于她更多的传奇,是要留到后面跟她两位炮友,啊不,蓝颜知己一并讲述的。
  GameCache的身世的确可怜。之前我讲unit类数据的存储和读取还有另一个原因,最初大家觉得GameCache除了战役传递数据外,唯一的用途GameCache来刷怪(生成和之前一模一样的单位);我为什么要把RestoreUnit比喻作受孕怀胎呢,无非是像GameCache这样的绝色美女,居然被仅仅当作传宗接代的工具,唯一的用途就是生小孩,跟母猪似的——是不是暴殄天物?
  卿本佳人,奈何薄命如斯。
  虽然GameCache她秀外慧中,却被那些婊子变量(韦春花,春三十娘等)抢走了男人的宠爱。直到她遇到了穷困潦倒的帅哥Return Bug,她的人生才有了翻天覆地的改观。(类似《Titanic》里Luce遇到Jack。)而在后来遇到怀才不遇的Timer后,他们三人联手干出番轰轰烈烈的千秋大业,史称“风尘三侠”。(可以把GameCache想象成红拂,Return Bug是虬髯公,Timer是李靖。)
  好了,我们终于要请另一位传奇人物——Return Bug登场了(闪光灯,尖叫声在哪里~);之前说过,Return Bug改变了GameCache作为受孕工具的地位,究竟他做了什么呢,且听下回分解~~

  “YD男,这就完了啊,真没意思!到底Return Bug把GameCach怎么了?”

  “啊恩!下回说。”

  “因为Return Bug是个变态,是个SM爱好者,他把茄子黄瓜什么东西都往GameCach身上放,使GameCach实现了存储任意类型数据的功能……”

  “啊,美美姐,不要说啊~”
回复

使用道具 举报

 楼主| 发表于 2008-1-12 02:58:08 | 显示全部楼层
[新手教程]从零开始学JASS

        “如果,你对JASS向往,渴望一个肩膀,帮你实现梦想……”(请以“有一种爱叫放手”的曲调唱出)呵呵,请不要误会,我可不是怂恿你放弃学习JASS;相反,如果你是一点基础都没有,刚接触JASS的新人;那么,这篇教程就是特地为你准备。不过倘若这之前你从未摸过WE,出于想学做图而来看这篇教程,那它不太适合你。“从零开始”的“零”,是指对JASS一无所知,而不是对WE;在看这教程前,你至少要对WE有一定认识,否则我爱莫能助了。

        即便你只是新手,但新手也可依据对JASS的熟悉程度进一步划分为初级、中级和高级新手。看下自己属于哪的阶段,然后按教程给出的方案去提高能力。

一、初级新手(“我英语不好,看不懂JASS”)

        如果你看不习惯面对GetTriggerUnit()、GetLastCreatedUnit()等常用函数,请不要以英语不好为借口而放弃学习JASS。我只能说,这是大家用惯了汉化版WE的弊端;对早些WEer来说,他们一面对照金山词霸,一面熟悉WE,虽然这样麻烦了点,但习惯英文版WE后,做图效率比起用汉化版要高出许多。并非是汉化者汉化得不够好,而是英文原有的味道,是无法汉化的。              

        所以,建议大家打开英文版WE看看,只要看熟几个英文单词,上手还是很快的。熟悉了英文版WE的人,学JASS自然轻车熟路。

        如果你有一点英语基础,那学习JASS就更容易了。你只需掌握几个常用单词,就能猜出大部分函数的意思。比如,Get 是“获得”,Trigger是“ 触发”,Unit是“单位”,那么GetTriggerUnit()就是获得触发单位;Last是“最后的”,Created是“被创建的”,那么GetLastCreatedUnit()就是获得最后创建的单位。很有意思,不是吗,那么再考你下,AddSpellEffect()是什么意思——如果你知道Add是“添加”,Spell是“技能”,Effect是“效果”,那么接下来你应该猜到这函数是做干什么的了。

        虽然我很希望各位能找来英文版WE熟悉下用法,为以后学JASS打好基础;但我相信很多人都不愿意一头扎进英文的汪洋大海中。因此,我推荐下面由151373880(GA疑难解答区版主)做的“初心者英汉对照UI”(应该是“初学者”吧,写成“初心者”,看来还真够“粗心”的,呵呵),我把它比作婴儿学步车,不愿意直接面对英文的朋友可以用下这个UI(使用方法,把这个UI文件夹放到war3根目录下)。

         UI.jpg
         151373880初心者英汉对照UI.rar (295 KB, 下载次数: 3288)
        
二、中级新手(“我不会编程,JASS实在太复杂了”)

        JASS可以说相当简单(跟其他程序语言相比几乎可以用小白来形容了);不过之前有个朋友要我比较下C++和JASS,他说JASS比C++还难,因为JASS的函数是那样多。天,谁叫你去记那些函数,我倒~


        笔者从来不去背函数的说,需要用函数时,常常把T转换成J查看;或者使用JASS工具的查询功能(说到这我想可能某些新人连JASSSHOP和JassCraft这样的工具都没有吧,没有就赶快去下载)。其实JASS的函数名差不多已经告诉你它是干嘛的了,去死记硬背完全是多此一举。

        我想,对于中级新手,眼下要做的就是经常把T转成J看代码,熟悉函数。说到这,有不少朋友学习JASS的方法,居然是查看别人地图的代码;天呐,几千行代码,又是那样的杂乱。如果你想自虐,那么你完全可以这样做;不过,如果你想学JASS,我建议你最好换种方法^-^!

        待看熟了由T转换的JASS代码,你便有了进一步学习的资本;现在你可以开始学习真正的函数了,要知道JASS中的函数跟T中的有很大不同,可不再是简单take nothing returns nothing,变得灵活多样。(如果你一味看别人地图里的代码,那你可能领悟不到什么是参数传递。)

        关于函数的参数传递和返回值,几乎每部JASS教程都有讲解;如果你不排斥YD气息,向你推荐月协出品的JASS教程(函数篇)。

三、高级新手(“我能看懂代码,但不知如何去写”)

        首先,恭喜你成为高级新手,这意味着JASS大门就在眼前,随时可能对你敞开。不过,你能看懂代码,不代码你了解JASS;在入门之前,你首先要明白, 什么是BJ函数,什么是CJ函数。

        BJ函数就是blizzard.j,CJ函数则是common.j——这两个函数库,JASSSHOP和JassCraft都自带有,你不妨打开看下
bj.jpg    



        我们在T中使用的函数,都是BJ中的函数,是将CJ中的函数面向对象封装后得到的。为了方便大家认识到CJ函数与BJ函数的关系,我们把下面这条T转换成J:
[trigger]T
    动作
        单位 - 创造 1 个 步兵 为了 玩家 1  (红色) 在 (区域 (可玩的地图区域) 的中心) 面对 默认的建筑朝向 度[/trigger]
转成JASS
[codes=jass]call CreateNUnitsAtLoc( 1, 'hfoo', Player(0), GetRectCenter(GetPlayableMapRect()), bj_UNIT_FACING )[/codes]
        这里的CreateNUnitsAtLoc()便是一个BJ函数,用来“制造人类”的(笑),如果我们把它输入到JASSSHOP或JassCraft的函数查询中,可以得到
[codes=jass]function CreateNUnitsAtLoc takes integer count, integer unitId, player whichPlayer, location loc, real face returns group
    call GroupClear(bj_lastCreatedGroup)
    loop
        set count = count - 1
        exitwhen count < 0
        call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face)
        call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
    endloop
    return bj_lastCreatedGroup
endfunction[/codes]
我们可以找到CreateUnitAtLocSaveLast(),从函数名中的“Create”和“Unit”来看,它也是用来“制造人类”的 ; 不过它同样是个BJ函数,将它放入到函数查询中,可得
[codes=jass]function CreateUnitAtLocSaveLast takes player id, integer unitid, location loc, real face returns unit
    if (unitid == 'ugol') then
        set bj_lastCreatedUnit = CreateBlightedGoldmine(id, GetLocationX(loc), GetLocationY(loc), face)
    else
        set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face)
    endif
    return bj_lastCreatedUnit
endfunction[/codes]
我们可以找到CreateUnitAtLoc(),谢天谢地,我们终于找到了“制造人类”的CJ函数。

        现在大家知道,为什么说用T效率低了吧——不就制造个人类么,用个CreateUnitAtLoc()就能轻松搞定;结果在T里,我们要调用先CreateNUnitsAtLoc(),在再CreateNUnitsAtLoc()中调用CreateUnitAtLocSave(),最后才在CreateUnitAtLocSave()中调用CreateUnitAtLoc()。   

        所以,在大家有一定基础后,尽量不要用BJ函数;但对初学者而言,BJ函数却又是很好的教程。

        我曾在新手这一阶段停留过很长一段时间,那时总感觉JASS学得倒会不会,只懂点皮毛,不知如何更上层楼。直到我跟别人一样,花了三天,把BJ函数全部看遍之后,我才真正懂了,什么是JASS……

        blizzard.j有九千多行代码,但在我看来,花上三天时间去看,完全值得;记住,是叫你去“看”,不是叫你去“背”,把它当茶余饭后的消遣浏览下就是了,看了便忘了也没关系。

        少林寺有十八铜人阵,打赢他们你就能下山;blizzard.j就是JASS中的十八铜人阵,看完了它,你便可以结束在JASS大门外的徘徊,正式登堂入室学习JASS了。

        好吧,倘若你已经老老实实看完了blizzard.j,那么,请你打开JASSSHOP或JassCraft,新建个文件夹,把下面这段函数中的“Everguo”换成你的名字,然后把这段代码一个一个敲到屏幕上(请不要直接复制,那没意义)。
[codes=jass]function HeroLearnJass takes nothing returns nothing
    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, ( "Everguo"+"want to learn Jass!"))
endfunction[/codes]
hanshu.jpg


        这个函数会很有意义,因为它是你亲自动手写的第一个函数。好了,欢迎你正式加入JASS培训班学习~

        你已不再是新手。在所有阶段中,新手这一阶段耗时是最长的,它是一个对JASS慢慢熟悉理解的过程;因此,新手向中手过渡的时间也相对最长。当你不再是新手后,JASS学习起来比以前快得多,成为高手也指日可待。
   
        现在,你可以继续看下篇教程了~        


      
回复

使用道具 举报

 楼主| 发表于 2008-1-14 02:22:29 | 显示全部楼层
[H出品]我也写一个jass教程(草稿版)


这是hackwaly以前写的教程,在个别地方讲解比较深入;或许它不是很适合做新手教程,但一定很对某些人的口味;因此我特地将它重新排版编辑,收入电子书——如果你有一定程序语言基础,我向你推荐这篇教程。

========================================================JASS培训班专用分割线========================================================  

Jass教程:
为什么要学JASS,JASS有什么用?
        JASS是魔兽争霸中的脚本语言,在WE中写的触发器实际上都是转换成jass在WAR3中起作用的。所以我们可以用JASS做一些触发器做不到的事情,而且还能提高完成的效率。另外一些好的地图都加了密,只要我们能看懂JASS就能学到地图中一些好的方法。


基本书写规范:
1.区分大小写:a和A是不同的。
2.jass中每个语句占一行,并且语句不需要结束符。(如果你留意的话会发现JASS的每个语句都是关键字开头的)
3.注释:jass中只有一种注释,单行注释,使用//开头,即//到行末的内容。
      注释中的内容绝对不会对代码有任何影响,相反它能提示你一些信息。应当养成写注释的良好习惯。
        另外我们可以临时把出错的代码注释起来,再进行调试。


一、基本变量类型:
boolean是非型(真假型):可以存放true和false两个状态。
integer 整型:可以存放整数。
real 实型:可以存放浮点数,也就是实数。
string字符串:可以存放字符串。支持转义字符,当你在JASS中用字符串表示path(路径)时,别忘了把\\改为\\\\
常用的转义字符有:\\n换行。
handle句柄:句柄这个概念可能还不太懂是吗?这里粗略的说一下吧。
      在魔兽争霸中很多要描述的物体(对象)都不是简单的变量类型。如unit(单位),ability(技能),item(物品),当一个函数需要以这些对象为参数的时候我们必须填写一个标识符在参数的地方。
        war3中的这种标识符就是handle类展开的变量。句柄就是这些对象的一个唯一的标识。
        war3通过一个句柄就可以知道这个句柄代表的是什么?是item还是itemtype(物品类型),是unit还是button(对话框)等等。
        并且能够通过句柄找到该物体(对象)在内存中的地址,对物体(对象)的数据进行访问。
        这一切是通过handle系统(实际上负责维护一个handle到指针的映射)完成的(这里不属于我们的讨论范畴)
        所以,句柄这个东西说白了就是一个标识,通常句柄都是在整型  基础上定义的,也就是说句柄几乎和整型通用。
        但要注意并不是每一个整数都对应了一个句柄,句柄这个东西是在句柄标识的东西存在的情况下才起作用的。如果我们把一个unit删除掉,那么这个unit对应的句柄也就失效了。而且我们删除该unit时恰好是用到unit的句柄来表示该unit。是不是很有趣!
        有很多变量其实都是句柄变量(该变量仅仅存储对象的句柄值):如unit ...
code类型code实际是function指针。当我们需要一个函数的指针的时候就可以用code变量来代替。
        注意:

                1.赋值: code c=function funcname
                2.code变量的实质:jass脚本函数的指针。你可以让它指向任意一个takes nothing returns nothing

                的函数,并且在需要code的地方用code变量代替——如注册触发器,过滤器等等。

二、基本变量的操作

赋值操作:所有的变量都支持赋值操作,使用set 开头,如set a=10当然等号两端得保持相同的变量类型。
boolean型:可以进行and与运算or或运算not非运算
        所谓与运算就是只要两个boolean型变量中有一个为假的时候结果就为假。
        或运算恰好相反,只要有一个为真那么结果就为真。

        非运算就是求一个boolean变量的反面。
integer型:+ - * /四则运算。其他的高级计算不直接支持,但是你可以自己写代码来进行高级计算。
real型:+ - * /四则运算。
string型:+运算,这个运算把两个string变量连接成一个string变量。
handle型:没有相关运算。
常用的库函数Pow(real x,real power)乘法运算。求x的power次方的值。注意x和power都是real型。
                            GetRandomInt(integer low,integer high)得到low到high之间的随机数,能取到边界。
                            GetRandomReal(real low,real high)用法同上面的函数,只是参数类型和返回类型不同

                            而已。
                            基本三角函数支持。Sin,Cos,Tan等等。
I2S:把integer转换成string,比如说你想在屏幕上显示一个integer变量的值就需要这个函数的帮助。
      同样有S2I,R2I,R2S,I2R,S2R这些函数完成Integer,real,string之间的相互转化。它们是非常有用的。
      另外对于string还有专门的函数:
              1.StringLength:求string的字节长度。注意汉字在war3中占3字节,而字母占1位。
              2.SubString(string s,integer m,integer n)分割string,留下m节点到n节点间的string。注意节点是指

              两个字节(不是字符)之间的点,而且string两端的端点属于节点。假如我要取得一个string的第一个汉
              字,我可以这样写,SubString(s,0,3)懂了吗。

        另外还有大小写转换的函数,这里就不一一列举了,自己去看commom.j文件。

      一个最常用的函数Player(integer i)通过玩家的序列,得到玩家。

三、简单的接口函数
        jass中使用war3提供的接口函数对war3进行一些操作。
        调用一个函数(不需要返回值时),需要在行首加上call,如

[codes=jass]call DisplayTextToPlayer(Player(0),0,0,"简单的调用") [/codes]
上面的语句中DisplayTextToPlayer就是一个接口函数,它完成这样一个功能,即在玩家Player(0)的屏幕上0,0位置显示字符串 “简单的调用”,当我们理解到这个函数的功能时,我们能够举一反三地写出更多的调用,在不同的玩家屏幕上,不同的位置,显示不同的字符串。是不是很简单。
        像这样的函数直接由函数名几乎就可以猜出功能了,再由参数类型和参数名,也能大概猜到调用方法。
        在commom.j函数中几乎所有的接口函数都是这样。而且war3针对不同的功能把借口函数分类放到一起,用户可以方便查找和掌握。只要你用jassshoppro打开common.j我就敢保证你能至少能了解到10个以上非常有用的函数。
        对于那些你还不是很了解或是不认识的英文,你可以不必去了解。毕竟we也没用全接口函数。

        一些返回句柄的函数通常可以用call调用。不必非拿一个句柄变量去装。如call CreateUnit(...)

四、选择分支和循环
        jass中判断分支语句是if-then-endif结构的,如果中间要进行多次判断,可以添加elseif-then在if判断体中;另外也支持else,注意else不用写条件。

例子:
[codes=jass]
if name=="杀残枫" then
    set name="天使:"+name
  elseif name=="hackwaly" then
    set name="垃圾:"+name
  else
    set name=name+"是谁?"
endif [/codes]
能看懂吗?一个判断中elseif-then可以有多条。但是if-endif只能有一个。if和endif必须成对出现。else可有可无,但必须是最后一个判断。

jass中的循环方法是最简单最“无招胜有招”的。

loop-endloop结构就是一个循环。代码执行到endloop时自动跳转到与该endloop成对的loop处执行。
例如:
[codes=jass]loop
    call DisplayTextToPlayer(Player(0),0,0,"循环啊循环")
endloop[/codes]
事实上上面的循环是死循环,因为循环不是有限次的,而且还不能跳出循环。

        exitwhen语句就是专门用在loop-endloop中间,跳出循环用的,一看就知道,是当条件满足时跳出循环;这比C语言的条件就满足执行循环灵活多了,如
[codes=jass]
set I=0
loop
    exitwhen I>11
    call DisplayTextToPlayer(Player(I),0,0,"要让每个玩家都看见这句话")
    set I=I+1
endloop[/codes]
当然你要这样写也是对的
[codes=jass]set I=0
loop
    if I==12 then
        exitwhen true
    endif
    call DisplayTextToPlayer(Player(I),0,0,"要让每个玩家都看见这句话")
    set I=I+1
endloop[/codes]
下面这种exitwhen的用法也并不是什么时候都像现在这样没什么额外的好处。有些时候正需要这样写。

其实要在每个玩家屏幕上显示消息,可以这样写。
[codes=jass]call DisplayTextToPlayer(GetLocalPlayer(),0,0,"每个运行到这条语句的终端都会显示本消息了") [/codes]
GetLocalPlayer()从字面上理解是取得本地玩家。为什么一条语句就能在完成呢?
        这里只是提一下,后面会有相关的解释。


五、函数
            在jass代码中,除去全局声明就是函数声明了。函数是现代所有的程序语言中的基本部分。

        如果前面提到的那么多次的函数概念你还没有一点头绪的话,可以看一下我对函数的理解:
1.没有函数可不可以?当然可以,没有函数的话直接写语句完成所有功能。
2.函数的作用?函数其实就是为了方便。我们可以把完成一个特定功能的代码段写成一个函数,
当我们需要完成该功能时,只需要调用一下函数就可以了,不需要重新去写已经写过的代码。
3.函数的参数?函数完成功能时需要知道一些参数,以便来完成功能,比如说一个max(m,n)函数,
它是求m,n中间较大的那个数,如果你调用max函数而不传给它m和n的值,max函数就无法完成它的功能了。
很简单的道理。
4.函数的返回值?调用函数是用来完成功能的,我们可能需要知道函数完成的怎么样了,或者说是完成的结果,这些都是函数的返回值来完成。如我们调用max(3,5)就会得到5,5就是max的返回值。
        我们可以把max(3,5)当作5来用,当参数不确定时,同样max(m,n)也可以当成一个变量来用。


函数的定义方法:
        前面我们讲了很多调用函数的例子,知道了函数怎么的神奇,可是神奇的函数是怎么来的呢?我们能不能创造自己的函数呢?
        答案是肯定的。

看max函数的定义吧
[codes=jass]function max takes integer m,integer n returns integer
    if m>n then
        return m
      else
        return n
    endif
endfunction[/codes]

能看懂吗?关键字function开头,然后是函数名,接着takes参数类型和参数名,这个参数名我们通常叫做形参,是我们在函数的内部用到的。我们的函数的调用不需要满足这个名,只需要满足变量类型相同即可。


        形参在函数内部可以当成变量来用,函数结束后,形参也会消失(被销毁)。
所有的参数写完了后是returns(注意有个s啊)返回类型。说明函数可以被当成什么类型的变量使用。
        在整个函数定义结束后是endfunction,同if,loop等一样,function和endfunction也成对。指示中间的代码是函数定义。
        我们可以看到函数内部有一个return语句,大家可能还不认识它。
        return语句负责返回函数值,返回的值就是return后面的表达式值(注意,jass中表达式和语句分得很清楚比c语言好多了)。
        表达式可以是变量,也可以是能返回值的函数,也可以是常数,等等,只要是能计算出值的都可以。
        当函数过程运行到return语句时就返回return后面的表达式的值,并且退出函数。返回到调用函数的地方去执行代码了。

[codes=jass]set a=max(3,5+4)[/codes]
上面的语句是个赋值语句,它的需求值是max(3,5+4),这个表达式是个函数调用,它又需求参数的值,所以正确的执行顺序是先计算5+4=9,然后计算max(3,9)这时就跳转到max函数内部去执行去了,此时形参m=3,n=9,m<n,所以return n,运行到这一句,函数过程就宣告结束了,然后把n的结果9返回给调用max(3,9)的语句,也就是赋值语句了,整个顺序基本上就是这样。

        现在能理解到执行的返回和值的返回了吧。


        当然函数就是这么简单,那那些厉害的函数是怎么写出来的?

        在回答问题之前,我得再讲一下,函数的局部变量,把函数部分讲完对不对。
        在函数内部声明变量,必须在函数过程开始之前,也就是写在函数定义的最前面才行了。同样是max函数为例,这次我们希望函数能够顺序运行到endfunction前才返回。也就是说,这次的max函数只有一个return.
[codes=jass]
function max takes integer m,integer n returns integer
    local integer maxer=m
    if m<n then
        set maxer=n
    endif
    return maxer
endfunction[/codes]
想起来这个函数没有前面那个函数快,事实上很多时候我们需要这样写,比如说,max函数的参数不是两个,而是10个或者更多。

        你不觉的一遍一遍return很麻烦吗。而且打乱了函数的结构。
        可以看出local关键字的用法了吗?和全局变量的声明一样,你可以把变量初始化,也可以等到你要用变量的时候才对它赋值。


        下面看一个调用接口函数完成公告消息功能的函数
[codes=jass]
function gonggao takes string message returns nothing
    local integer I=0
    loop
        exitwhen I>11
        call DisplayTextToPlayer(Player(I),0,0,message)
        set I=I+1
    endloop
endfunction [/codes]

看见returns后面跟的nothing了吗,这表示函数不返回值,也就是说你只能通过call来调用它。

        在函数体中同样可以使用return来退出函数,不同的是你不需要写return nothing而是直接写return就可以了
        怎么样?啊!不怎么样啊。如果你向没有学过JASS的人或者是还没学JASS时候的你介绍说这个函数能够完成让所有玩家不得不看消息。
        他肯定大吃一惊,佩服的说,这么少的代码啊。


六、数组
      要想写出好的函数不是光靠循环和判断就能写出来的,还需要数组。什么?你被这名字吓着了!
      别慌,数组其实就是一串变量,记住它是变量啊。


首先我们见识一下数组的好处,即为什么要有数组这个东西。(几乎所有的程序语言中都支持数组)
例如,我们写一个函数实现对每个玩家显示不同的消息。
[codes=jass]function Demo takes nothing returns nothing
    local string s1="玩家1的消息"
    local string s2="玩家2的消息"
    local string s3="玩家3的消息"
    call DisplayTextToPlayer(Player(0),0,0,s1)
    call DisplayTextToPlayer(Player(1),0,0,s2)
    call DisplayTextToPlayer(Player(2),0,0,s3)
endfunction[/codes]
幸好war3中只有12个玩家要是有30个玩家就相当麻烦了。这里仅仅写出3个玩家就不想写了
        如果我们还想让玩家2收到玩家1和玩家3的消息,玩家3能收到玩家2和玩家4的消息...不敢想像。

我们看用了数组的版本
[codes=jass]function Demo takes nothing returns nothing
    local string array s
    local integer I
    set s[1]=""
    set s[2]=""
    set s[3]=""
    //...
    loop
        exitwhen I>11
        if I>0 then
            call DisplayTextToPlayer(Player(I),0,0,s[I-1])
        endif
        call DisplayTextToPlayer(Player(I),0,0,s)
        if I<11 then
            call DisplayTextToPlayer(Player(I),0,0,s[I+1])
        endif
    set I=I+1
    endloop
endfunction[/codes]

看到了吗,上面的函数就能完成艰巨的任务,让每个玩家收到他邻位玩家的消息。
        如果你能够读懂的话,那么说明你有相当高的自学天赋了。
        array关键字用在变量名和变量类型之间,声明一个数组变量。简称数组,有称矩阵变量的。
        我们通过数组变量名加上中括号[](又称下标号),在中括号里面填上索引就可以访问到相对应的单元变量,如s[1],s[2]等等。最方便的是索引你可以使用一个整型表达式来代替。这样方便我们用循环来访问数组中每一个变量。

        在JASS中对数组很宽松,不用指定数组的大小,但默认情况下,数组索引为8192。数组是很重要的内容,建议你自己下来多实际试一下。熟练一下数组对于编程是很有帮助的。

七、触发器
        好吧,我们开始接触jass中的触发器吧。

        早在开篇我就提过,war3中的对象都是用handle标识的。触发器也不例外。
        一个触发器变量仅仅存放了标识触发器的handle,而不是触发器的真正数据。
        理解这个对于我们的jass相当重要。

回想WE中的触发器都由什么构成?
对了,就是事件环境动作三部分。

        在WE中,把三部分放到一起,方便我们设计触发器。而在jass中,三部分不必在同一地方,非常的灵活。

1.首先,得创建一个触发器对象
        要注意的是创建触发器对象并不是声明一个触发器变量——那该死的trigger变量仅仅是一个句柄变量。创建触发器对象的函数war3已经封装好了,是CreateTrigger 直接用就行了,不需要任何参数,它返回一个trigger句柄,你可以用一个trigger变量来存放,也可以不存放。不管你存不存放,它都已经在内存中创建好了。
        当然,没有任何的参数,你一定会奇怪,触发器的事件,环境,动作到哪里去了。
        不要急,先前就说过,触发器的设计在jass中是可以分开的;我们先创建一个空的触发器;以后再添加它的事件,动作也是可以的。


2.在jass中给触发器添加事件是通过调用相应的接口函数(API)来实现的,而且war3把它叫做注册事件函数。
      这种函数不只一个,有很多,不同类型的事件有不同的注册函数。这让jass写触发器的人很麻烦。
      我介绍一种简单的方法,可以避开自己去找哪个事件对应哪个函数:
              用WE中的触发编辑器,把你要的事件添加好之后,再把触发器转化为文本。就可以清楚的看到事件是该怎么注册的。
              到jassshoppro中查看这些注册函数,你会发现它们有个共同点,第一个参数都是trigger变量,即触发器句柄。这表示把事件注册到哪个触发器上面。我们在为一个触发器注册事件时,一定要保证这个参数是我们要设计的那个触发器的句柄,不然就会注册错。


3.好吧,事件注册完了,轮到环境和动作了。
        为什么把他们并到一块呢?
        因为它们有很多共同点:

              1>首先他们的注册函数只有一个。你此时肯定会想到WE中的条件和动作是那么的多;
              2>其次,他们注册的东西不是一个对象了,而是一个函数。


        这下能想通刚才的困惑了吧?
        环境注册函数把一个条件函数注册到触发器。动作注册函数把动作函数注册到触发器。
        这里以动作为例就不讲条件了。条件函数的要求是返回值类型为boolean。

        注册分先后,触发器触发时,动作函数的运行先后也确定了,和你的注册顺序是一样的。在jass下注册动作一般只注册一个动作函数。在那个动作函数里把所有的动作和判断都完成,我们还推荐你不要注册条件,而是把条件判断写到动作函数里。

        当然仅仅是对于静态的触发器而言,如果你的触发器需要不时的增加和减少条件动作,那么你不得不多写几个函数了。
        这种动态的触发器只是听说过和设想过,往往写成jass的只有很简单的触发了。

下面介绍一下jass下触发器的原理:
        当一个事件注册到触发器后,每当发生该事件war3都会创建一个线程运行该触发器的条件函数,一旦条件函数返回假,线程就停止运行。
        如果条件函数返回真,则运行下一个条件函数。直到所有的条件函数返回值都是真时运行动作函数。
      这时触发器的触发计数器加一。接着是其他的动作函数。记住,运行下一个动作函数是要等待上一个返回的。

下面讲触发器中最重要的部分:触发资源。
        为触发器注册的事件往往不是详细的事件,而是一个笼统的事件。比如说“一个单位进入某个区域” ,虽然区域指定了,但是单位没有指定,任何单位进入该区域我们的事件都会触发。我们怎么能知道进入区域的那个单位,是不是我们要等它进入的那个单位呢?
        war3在为触发器创建线程时,同时也创建了线程的全局资源(你可以理解为全局变量),之所以叫全局资源而不叫全局变量,是因为我们不能像访问变量一样访问它们,它们被要求是只读的;如果弄成全局变量我们很有可能错误的对它进行写操作,所以war3把它们设计成了只能用函数读取值的资源。


下面是几个常见的触发器资源读取API(接口)
GetTriggerPlayer()返回触发事件的玩家
GetTriggingTrigger()返回当前触发器的句柄
还有更多,它们和WE中的要求变量时可选的“功能”有一样的效果。当然你只能在相应的触发器下面使用相应的资源读取API,不同事件的触发器总能有一些相同命名的资源。

      你如果要了解更多资源,请使用jassShopPro。

      至此war3 jass最基本的东西算是讲完了

八、过滤器

        对不起,这个我也不太会,但是它是用Jass写(群杀)(光环)技能的重要工具。如果有人会这个,麻烦帖一下。



高级运用:
一、丢弃垃圾全局变量:我们打开一个用WE做的图可以看见全局变量有很多。用JassShopPro检查一下,发现这些变量我们根本就没调用过,说明是垃圾变量,这些变量大都是一些句柄变量,用来保存我们在地图上放置的单位,方便触发编辑器中调用。很多没用到的变量占用了资源,学过了JASS应该知道怎么办吧,记住:句柄变量不是对象,一个对象没有句柄变量也能存在。当一个对象创建完毕,不需要句柄变量时就可以
对句柄变量赋值另一个句柄;典型的用法是在触发器的设计上,当我们设计触发器时,必须有设计中的触发器对象的句柄,
设计完成后,就不需要该句柄了,触发器能自己去运行,如
[codes=jass]local trigger t
    set t=CreateTrigger()
    call TriggerRegisterEvent()
    call TriggetAddAction()
    set t=CreateTrigger()
    call TriggerRegisterEvent()
    call TriggetAddAction()
//...[/codes]

能理解吗?这一次不能理解我也不会解释了。

二、动作函数
        如果叫你用WE的触发器编辑器和jass来完成触发器的动作部分,你会选择JASS吗?
        在JASS里面,没有别扭的循环数A,循环数B。在JASS里面判断条件比在触发编辑器中找条件要方便得多,如果你用过。
        另外你可以在动作函数里面写一些资源,如数组的string,这是触发编辑器绝不可能办到的。

三、读图线程和触发线程
        main函数开始的线程是读图线程,读图的时候运行。
        触发动作函数开始的线程是触发线程。
        读图线程负责初始化全局变量,注册触发器,创建对象。在读图线程中的交互式操作无效。如显示消息,对话框等等。
        通常至少有一个触发器在读图线程中被注册。一般情况下触发器都由读图线程注册。如果你懂你可以在触发线程中注册另一个触发器或修改本身。

四、关于GetLocalPlayer()
        这个函数很简单。返回一个玩家。
        你需要理解jass脚本在war3中的执行方式:多终端同步执行,也就是说jass脚本不是只在建主机(OP)的那个机器上运行,而是所有的终端都在运行,终端之间的全局变量通常是完全一致的。

        这个函数的用法就是自己去理解吧,真难表述!后悔没学好语文。通常
[codes=jass]if GetTriggerPlayer()==GetLocalPlayer() then
    call action()
endif [/codes]

这样就能节约资源,要掉线也只是他一个掉,不会出现主机掉的情况。

我d发现:


1.RemoveUnit(null)这个函数调用不会被认为是错误,运行时也不会导致程序退出。但它让玩家不能使用UI键,而只能使用鼠标。
2.UnitRemoveAbility可以删除单位的基本技能如'Aatk'攻击,'Amov'移动,但不能添加这些技能,其他的技能不存在此问题。


回复

使用道具 举报

 楼主| 发表于 2008-1-18 04:48:11 | 显示全部楼层
[分享]仿暗黑佣兵系统
这是以前老狼帮我做的佣兵系统,佣兵会像暗黑里那样在英雄周围巡逻,并在拉开一定距离后会自动移动或传送到英雄身边;后来白银大人帮忙优化,增加了点新功能;出于教学需要,我把这系统代码发上来,虽然我不在乎在缓存文件名中使用“+”引起那点sting泄露,但为了不带坏新手,我重新按老狼无泄露的方法写了,并加入判断佣兵闲置状态的函数(当前单位状态为null并不等于单位真的处于闲置状态,不过在不要求那么精确的前提下可以近似判定为闲置)。



这是代码:



[codes=jass]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 I2Tm takes integer i returns timer
    return i
    return null
endfunction

function GetUnitCurrentFree_Func takes unit u returns boolean
    local string s = OrderId2String(GetUnitCurrentOrder(u))
    if s == null then
        return true
    endif
    return false   
endfunction


function SetGuard_Func takes nothing returns nothing
  local timer tm = GetExpiredTimer()
  local unit pet = I2U(GetStoredInteger(udg_GC, I2S(H2I(tm)), "Pet"))
  local unit captain = I2U(GetStoredInteger(udg_GC, I2S(H2I(tm)), "Captain"))
  local real x = GetUnitX(captain) - GetUnitX(pet)
  local real y = GetUnitY(captain) - GetUnitY(pet)
  local real d = x*x + y*y
  local real v = 0
  local real a = 0
  local effect e = 0
  local real life = GetStoredReal(udg_GC, I2S(H2I(tm)), "Life")
  local integer p = GetStoredInteger(udg_GC,I2S(H2I(tm)), "Percent")
  set v = GetStoredReal(udg_GC, I2S(H2I(tm)), "GuardRanger")      
  if GetUnitState(pet, UNIT_STATE_LIFE) > 0 then  
      if d<v*v then
        if GetUnitCurrentFree_Func(pet)==true and GetRandomInt(0,100)<p then
          set x = GetUnitX(captain)
          set y = GetUnitY(captain)
          set d = GetRandomReal(0,v)
          set a = GetRandomReal(0,360)
          call IssuePointOrder(pet, "patrol", x+d*CosBJ(a), y+d*SinBJ(a))
        endif
      else
        set v = GetStoredReal(udg_GC, I2S(H2I(tm)), "ReturnRanger")
        if d<v*v then
          if GetUnitCurrentFree_Func(pet)==true  then
            call IssuePointOrder(pet, "patrol", GetUnitX(captain), GetUnitY(captain))
          endif
        else
          set v = GetStoredReal(udg_GC, I2S(H2I(tm)), "OutRanger")
            if d!=0 and d>v*v then
              call SetUnitPosition(pet,GetUnitX(captain),GetUnitY(captain))
              set e =AddSpecialEffectTarget("Abilities\\\\Spells\\\\Human\\\\MassTeleport\\\\MassTeleportTarget.mdl" ,captain,"chest")
              call DestroyEffect(e)
            else
              call IssuePointOrder(pet, "move", GetUnitX(captain), GetUnitY(captain))
            endif
          endif
      endif
  endif
  set tm = null
  set pet = null
  set captain = null
endfunction


function SetGuard takes unit pet, unit captain, real timeout, real guardRanger, real returnRanger, real outRanger,integer percent returns nothing
    local timer tm = CreateTimer()  
    call StoreInteger(udg_GC, I2S(H2I(pet)), "Timer", H2I(tm))
    call StoreInteger(udg_GC, I2S(H2I(tm)), "pet",H2I(pet))
    call StoreInteger(udg_GC, I2S(H2I(tm)), "Captain", H2I(captain))
    call StoreInteger(udg_GC, I2S(H2I(tm)), "Percent", percent)  
    call StoreReal(udg_GC, I2S(H2I(tm)), "GuardRanger", guardRanger)   
    call StoreReal(udg_GC, I2S(H2I(tm)), "ReturnRanger", returnRanger)  
    call StoreReal(udg_GC, I2S(H2I(tm)), "OutRanger", outRanger)
    call TimerStart(tm, timeout, true, function SetGuard_Func)
    set tm = null
endfunction

function RemoveGuard takes unit pet returns nothing
    local integer tm = GetStoredInteger(udg_GC, I2S(H2I(pet)), "Timer")
    call FlushStoredMission(udg_GC, I2S(H2I(pet)))
    call FlushStoredMission(udg_GC, I2S(tm))
    call PauseTimer(I2Tm(t))
    call DestroyTimer(I2Tm(tm))
endfunction[/codes]


需要说明的是,根据JASS中的已知BUG,在上面RemoveGuard函数中,销毁计时器(DestroyTimer)前,应该先暂停计时器(PauseTimer);下面是那个BUG

3.已销毁的(Destroyed)定时器仍会过期(Expired)
        当你在倒计时函数的另一个函数里将此计时器销毁后(destroyed) ,它仍然不会停止倒计时。解决方法很简单,在移除它之前先将其暂停(PauseTimer)。
回复

使用道具 举报

 楼主| 发表于 2008-1-18 16:34:05 | 显示全部楼层
JASS培训班素材

这里为大家准备了大量的JASS演示,供大家参考和移植使用。



===================================================================JASS培训班专用分割线===========================================================



一、万能的环绕函数模板
        先声明,这演示并非原创——我是在前人的成果上总结出这么个模板。在做技能的时候,环绕的技能总是很漂亮,但过去的演示存在这么两个问题,一是内存泄露,二是运行起来卡。这个演示就是主要解决这两个问题。
        我以前很喜欢一个叫“精灵环绕”的技能,但那技能的内存泄露不是一般的严重;当我找AMP34修改后,他给我写了个万用的环绕函数,可以任意设置环绕的各种参数;不过这演示唯一美中不足的地方是使用等待的循环,按老狼的说法,最理想的应该是Timer加游戏缓存。我在参考了xielingjun的“风之盾”演示后,将这两个技能演示结合到一起,写了这么个万能的环绕函数。
        如果你要把这演示加入你图中,很easy,把那个写有函数的T直接复制过去就可以了,记得要定义个名字为gamecache的缓存全局变量。那么,怎么用这个函数呢,那更简单了,见下图的设置——你只需要修改几个参数就可以了——循环单位的类型,也可以修改(见附加的两个技能演示)
          这个技能模板还有个最大的好处,就是可以多人同时使用,也可以在一个T内多次调用(见技能演示二)
附图

[trigger]
a1
    事件
        单位 - 单位 开始一种技能的效果
    环境
        (使用的技能) 等于 0神圣护甲
    动作
        -------- -------------设置被环绕单位----------- --------
        设置 Hero = (触发单位)
        -------- -------------设置环绕单位----------- --------
        设置 ewsp = 小精灵
        -------- -------------设置环绕单位数量----------- --------
        设置 N = 5
        -------- -------------设置环绕半径----------- --------
        设置 R = 250.00
        -------- -------------设置技能持续时间----------- --------
        设置 T = 60.00
        -------- -------------设置环绕间隔----------- --------
        设置 I = 0.01
        -------- -------------设置环绕单位速度----------- --------
        设置 S = 1.00
        -------- -------------调用函数----------- --------
        自定义:  call  CreateEwsp(udg_Hero,udg_ewsp,udg_N,udg_R,udg_T,udg_I,udg_S)[/trigger]

01.jpg
[JASS培训班素材]环绕技能模板.w3x (43 KB, 下载次数: 42)



===================================================================JASS培训班专用分割线===========================================================



二、YD男的闪烁函数——情人节春雪的微笑
        今天(已经是昨天)素伟大的情人节啊
        一个憔悴的老男人孤独地在大街上游荡,不可否认他很帅,因为憔悴都能憔悴得这么有品味。
        他不断地问自己:“为什么每年的今天我都要黯然神伤呢!”
        他一便又一便地安慰自己,面包会有的,牛奶也会有的,所以老婆会有的,孩子也会有的。
        他噙着泪花仰望阴霾的天空,仿佛看见春雪美丽的笑靥——她在对自己说:
        “明天是很美好的。”

所以,所有在这万恶的一天中失魂落魄的朋友们,让我们一起来期待美好的明天吧!

02.jpg
[JASS培训班素材]万能闪烁函数——情人节春雪的微笑.w3x (128 KB, 下载次数: 49)



===================================================================JASS培训班专用分割线===========================================================



三、YD系列函数之三——万能的3C刷兵函数            
        3C地图是最常见的地图之一,不过很多图玩起来都卡,这跟“每隔XX时间就执行一次”的T用的太泛滥有直接关系;很多地方可以把这条T替换,用别的方法来实现,可刷兵这种周期性事件如何用别的方法来实现呢。以前我曾想过开发一种被成为地精时钟系统的来实现刷兵,但后来这系统的效果不素很理想。
        我很早就有用Timer做刷兵的想法,用Timer刷兵是非常环保的,无奈当时JASS神功尚未练成(舍不得自宫,恩恩) 。在写这函数前,我参考了c3c跟e3c的刷兵——它们都采用了Timer,不过每种兵都要单独写个函数,很烦琐;于是我采用了以老狼为形象代言人的GameCache+Return bug
这个函数很小巧实用,居家旅行杀人越货必备

      刷兵有几个参数:给谁刷兵(玩家)、刷几个(数量)、刷什么东东(类型)、多久刷一次(时间间隔)、在哪刷(出兵地点)、刷出来后面对的方向(朝向),刷出来往哪攻(目标点)

[trigger]
    动作
        -------- -----------------------光明刷兵----------------------------- --------
        设置 face = 0.00
        设置 playerid = 玩家 6  (橙色)
        -------- -----------------------光明上路刷兵----------------------------- --------
        设置 point = (单位 兵营 0001 <情报> 的位置)
        设置 targetpoint = (单位 兵营 0004 <情报> 的位置)[/trigger]
[trigger]
动作
        -------- ------------------------------第一波步兵---------------------------- --------
        设置 unitid = 步兵
        设置 N = 3
        设置 timeout = 10.00[/trigger]
以上两个T,我们可以看出:

face是朝向,0是向右看,180是向左看,所以光明是0,黑暗是180
playerid是玩家,在图里我们令它等于玩家6,也就是给光明方刷兵
point是刷兵的地点,在这里,由于是光明上路刷兵,我们设置这点为光明兵营01的位置
targetpoint是目标点,这里设置的是黑暗兵营01的位置
unitid是兵的类型,这里刷的是步兵
N是兵的数量,N=3表示刷三个兵,如果你想让中间刷的兵多些,可以令N=4
timeout是时间间隔,每个兵种的时间间隔各不相同,越高级的兵种出现的周期越长

最后说下清空缓存,由于是利用缓存来保存以上参数,因此某个兵种缓存一旦被清空,就不会再刷该类兵,不过对3C来说,是不需要这个东西的,因为3C的刷兵是固定的,不需要停止刷哪种兵。


[JASS培训班素材]万能的3C刷兵函数.w3x (21 KB, 下载次数: 24)



===================================================================JASS培训班专用分割线===========================================================



四、锁定单位飞行高度
        经常遇到这样的状况,飞行单位在移动过程中,会因地面凹凸不平而上下颠簸;而我们做的地形又不可能没有起伏。因此你需要这个演示,来锁定单位的飞行高度。
        这是个很简单的演示,没什么技术含量;但对想学习Return Bug+GameCache+Timer的人来说,这是很好的教材。

[JASS培训班素材]锁定飞行高度.w3x (22 KB, 下载次数: 36)



===================================================================JASS培训班专用分割线===========================================================



五、[YD,奔泪ing]RPG智能佣兵AI系统
        从现在开始,你只能疼我一個人,要爱我,宠我,不能骗我。答应我的每件事都要做到,对我讲的每一句话都要真心,不许欺负我、骂我,要相信我。別人欺负我,你要第一时间出来帮我;我开心,你要陪我开心;我不开心你要哄我开心。永远都要觉得我是最漂亮的,梦里也要梦到我。在你心里只有我。

        以上可以无视,那是以前一个变态女生非要逼我背的,当然,我可没说是我头像中这个

        好吧,转入正题  

        从现在开始,你只能跟我一個人,要保护我,照顾我,不能拖累我。把打我的每个怪都干掉,赚的每一分钱都要上缴,不许分我经验、抢我钱,要迁就我。怪物欺负我,你要第一时间出来帮我;我打怪,你要陪我打怪;我不打怪你要去引怪打我。永远都要觉得我是最强壮的,在祭坛里魂也要跟着我。在你身边只有我。

        以上是我对佣兵的标准 ,看到这是们是不是已经没语言了~

        这是我送给大家最后的礼物 ——RPG智能佣兵AI,这个AI包含两个函数: 一是模仿暗黑的佣兵巡逻AI,由老狼编写,白银优化; 另一部分是佣兵主动使用技能,我写的太烂,代码经过老狼优化。
[trigger]shezhi1
    事件
        时间 - 逝去的游戏时间是 2.00 秒
    环境
    动作
        -------- 单位 --------
        设置 hero = 牛头人酋长 0000 <情报>
        -------- 技能命令窜 --------
        设置 s = stomp
        -------- 释放几率 --------
        设置 i = 20
        -------- 以单位攻击为事件 --------
        设置 N = 1
        -------- 无目标技能 --------
        设置 NO = 3
        -------- 调用函数 --------
        自定义:  call AddAIOrder(udg_hero,udg_s,udg_N,udg_NO,udg_i)[/trigger]上面各个变量的用途都有说明,需要补充的是
N=1,表示以单位攻击为事件;N=0表示以单位被攻击为事件。
NO=1,敌目标;2,点目标;3,无目标;4,自己。

[JASS培训班素材]RPG英雄AI.w3x (35 KB, 下载次数: 29)



===================================================================JASS培训班专用分割线===========================================================



六、让英雄自动学习技能
  本演示代码原著为Greedwind,我修正了其中少量笔误,在参考了lars(C3C作者)的AI代码后,做了这个演示。
  本演示采用在T里调用函数形式,方便移植;技能顺序表也比较容易编排。关于AI制作,如果您有好的构思或建议,欢迎跟我探讨。
PS:由于Greedwind说S2I函数有BUG,因此他在代码里自己写了Dig2Str和Int2Str两个函数;我保留了他的写法。 03.jpg 上图调用了RecordAbility和RecordLearn_sequence两个函数,其中RecordAbility是储存英雄的技能
[trigger]set
    事件
        地图初始化
    环境
    动作
        -------- -----------------------巫妖------------------- --------
        设置 type = 巫妖
        设置 Ability[1] = 霜冻护甲 (自动施放)
        设置 Ability[2] = 霜冻新星
        设置 Ability[3] = 黑暗仪式
        设置 Ability[4] = 死亡契约
        设置 Ability[5] = 死亡凋零
        自定义:  call RecordAbility(udg_type,udg_Ability[1],udg_Ability[2],udg_Ability[3],udg_Ability[4],udg_Ability[5])[/trigger]

RecordLearn_sequence是储存技能的学习顺序。[trigger]    动作
        -------- -----------------------巫妖------------------- --------
        设置 learn_sequence[1] = ^1^5^10^15^20^25^30^35^40^44^
        设置 learn_sequence[2] = ^2^7^12^17^22^27^31^34^38^41^
        设置 learn_sequence[3] = ^3^8^13^18^23^28^32^36^39^42^
        设置 learn_sequence[4] = ^4^9^14^19^24^29^33^37^43^45^
        设置 learn_sequence[5] = ^6^11^16^21^26^
        自定义:  call RecordLearn_sequence(udg_type,udg_learn_sequence[1],udg_learn_sequence[2],udg_learn_sequence[3],udg_learn_sequence[4],udg_learn_sequence[5])[/trigger]

这里 learn_sequence[1] = ^1^5^10^15^20^25^30^35^40^44^  表示英雄分别在第1、5、10、15、20、25、30、35、40和44级时候学习第一个技能——霜冻护甲

[JASS培训班素材]AI-自动学习技能.w3x (26 KB, 下载次数: 24)



===================================================================JASS培训班专用分割线===========================================================



七、正太的逆推——功能纯洁的装备栏
  一年前,一个风雨交加的夜晚 , 一个没有自己的电脑的可怜小正太跑去网吧做图 ;有人拿了张装备栏的截图来GA技能区,求人帮忙做这个演示:
装备栏2.JPG
  这么变态的演示,一定是国外那帮变态怪叔叔用邪恶的JASS做的;小正太不会JASS,小正太不会做……
  但老大要他做,因为小正太是技能区版主;于是可怜的小正太只好用T做,做出来的装备栏BUG很多,可怜的小正太就这样被推倒了……
  看来不学JASS不行啊!

  前两天张罗JASS培训班,心血来潮把这演示做了;这演示原理很简单,就是事先把物品与技能绑定后的数据,存储到缓存里,构建一个库;当物品放入装备栏时,调用库中的数据,给单位添加技能。
  最初我按自己习惯的写法,在缓存的文件名里用了"+",而且一个物品只能绑定四个技能;后来我把代码优化,避免了缓存引起的string泄露,一个物品可以与任意多个技能绑定。利用这原理,我们还可以做翻页保留属性的多重物品栏。
  如果学WE只是为了做图,那真没有必要去学JASS;不过如果学会JASS,可以做出以前用T做不出的效果,很有成就感的。
  各位小正太心动了么,心动不如行动,来JASS培训班学习吧!
  我们的目标是: 把怪叔叔全推倒!
05.jpg

[JASS培训班素材]功能纯洁的装备栏.w3x (180 KB, 下载次数: 80)



===================================================================JASS培训班专用分割线===========================================================



八、翻页可保留属性的多重物品栏
        前些天为配合JASS教学,做了个翻页保留属性的双物品栏;本想留个作业——“翻页保留属性的多重物品栏”,但考虑到这演示难度稍大,不适合学员练手,因此我把它做出来,有需要的朋友可以下载使用。

本物品栏的优点:
1.能为任意单位指定任意个物品栏
2.可以通过注册物品技能,在翻页时保留物品属性
3.使用变身技能后仍能保留附加属性
4.移植方便 06.jpg         该演示可以说非常简单,这个函数也只有四个参数,分别是“使用物品栏的单位”、“使用的背包技能”、“辅助单位的类型”和“物品栏个数”。物品栏个数必须大于1,如果等于1,当你使用背包技能的时候,系统会提示你“该单位只有一个物品栏”。

        从上图可以看出,我给山丘3个物品栏,而给了恶魔猎手10个。

        另外需要说明的是,如果你希望翻页时能保留物品属性,那么必须给这些物品注册技能。如果你不需要翻页保留属性,那么可以把这演示当成普通多重物品栏使用。
07.jpg

[JASS培训班素材]翻页保留属性的多重物品栏.w3x (32 KB, 下载次数: 34)



===================================================================JASS培训班专用分割线===========================================================



九、英雄复活系统
      许多地图都有英雄复活系统,不过您是否在抱怨自己图里的英雄复活系统不是很完美呢;好吧,放上Timer写的英雄复活系统给大家——这系统是我半年前写的,它含有三个函数:

ReviveHeroTimerString
[codes=jass]function ReviveHeroTimerString takes unit hero,location point,real time,string TS returns nothing [/codes]
该函数是把需要复活的英雄、复活点、复活时间、计时器框中的文字录入缓存    并自动生成一个以单位死亡为触发的触发器,在指定时间耗尽后复活英雄。

  对于大多数图而言,只需要这个函数就够了;但假如你的图里英雄复活的时间和位置是可以更改的,那么你需要这个函数


ReplaceRviveHero
[codes=jass]function ReplaceRviveHero takes unit hero,location point,real time,string TS returns nothing [/codes]
该函数能更改英雄复活的时间和地点  在演示里有详细的示范

  如果你的图里有祭坛,能立刻复活英雄, 那么你还需要这个函数


FlushReviveHero
[codes=jass]function FlushReviveHero takes unit hero returns nothing[/codes]
它能立即清除显示复活剩余时间的计时器框

[JASS培训班素材]英雄复活系统.w3x (25 KB, 下载次数: 39)
回复

使用道具 举报

 楼主| 发表于 2008-1-25 02:19:23 | 显示全部楼层
正式版补充内容,麻烦鼠鼠加到书中相应地方

=======================================朴素的分割线=======================================

JASS培训班第三期作业
Blandheart-3C选英雄.w3x (20 KB, 下载次数: 22)        meidanzuo-3C选英雄.w3x (19 KB, 下载次数: 30)     zhuzeitou-3C_Select.w3x (24 KB, 下载次数: 34)     trevors-绵羊选英雄.w3x (18 KB, 下载次数: 33)   

注册动态事件提到的OrderSystem,我没布置这作业,trevors已经做出来了,鼠鼠你把它加到那章教程的后面
trevors-OrderSystem.w3x (21 KB, 下载次数: 19)

=======================================朴素的分割线=======================================

紧急通知:Return Bug+GameCache的应用(一)中,老狼的代码
[codes=jass]
function H2I takes handle h returns integer
      return h
      return 0
endfunction
function I2TC takes integer i returns triggercondition
      return i
      return null
endfunction
function I2TG takes integer i returns trigger
      return i
      return null
endfunction
function DestroyTriggerAllById takes integer t returns nothing   
            call TriggerRemoveCondition(I2TG(t),I2TC(GetStoredInteger (udg_GC,I2S(t),"TriggerCondition")))
            call DestroyTrigger(I2TG(t))
            call FlushStoredMission(udg_GC,I2S(t))
endfunction
function DestroyTriggerAll takes trigger trg returns nothing
            call TriggerRemoveCondition(trg,I2TC(GetStoredInteger(udg_GC,I2S(H2I(trg)),"TriggerCondition")))
            call DestroyTrigger(trg)
            call FlushStoredMission(udg_GC,I2S(H2I(trg)))
endfunction
//========================================================
function RegisterUnitAmortCond takes nothing returns nothing
                  call SetUnitInvulnerable(GetTriggerUnit(), true)
                  call DestroyTriggerAll(GetTriggeringTrigger())
endfunction
function RegisterUnitAmortEvent takes unit witchUnit returns nothing
            local trigger trg = CreateTrigger()
            call TriggerRegisterUnitStateEvent(trg, witchUnit, UNIT_STATE_LIFE, LESS_THAN_OR_EQUAL, 50)
            call StoreInteger(udg_GC,I2S(H2I(trg)),"TriggerCondition",H2I (TriggerAddCondition(trg,Condition(function RegisterUnitAmortCond))))
            set trg = null
endfunction[/codes]

含未知BUG(姑且命名为RPBug,关于该Bug更多描述-连接1),请大家用下面的代码替代
[codes=jass]
function H2I takes handle h returns integer
      return h
      return 0
endfunction
function I2TA takes integer i returns triggeraction
      return i
      return null
endfunction
function I2TG takes integer i returns trigger
      return i
      return null
endfunction
function DestroyTriggerAllById takes integer t returns nothing   
            call TriggerRemoveAction(I2TG(t),I2TA(GetStoredInteger (udg_GC,I2S(t),"Triggeraction")))
            call DestroyTrigger(I2TG(t))
            call FlushStoredMission(udg_GC,I2S(t))
endfunction
function DestroyTriggerAll takes trigger trg returns nothing
            call TriggerRemoveAction(trg,I2TA(GetStoredInteger(udg_GC,I2S(H2I(trg)),"Triggeraction")))
            call DestroyTrigger(trg)
            call FlushStoredMission(udg_GC,I2S(H2I(trg)))
endfunction
//========================================================
function RegisterUnitAmortAction takes nothing returns nothing
                  call SetUnitInvulnerable(GetTriggerUnit(), true)
                  call DestroyTriggerAll(GetTriggeringTrigger())
endfunction
function RegisterUnitAmortEvent takes unit witchUnit returns nothing
            local trigger trg = CreateTrigger()
            call TriggerRegisterUnitStateEvent(trg, witchUnit, UNIT_STATE_LIFE, LESS_THAN_OR_EQUAL, 50)
            call StoreInteger(udg_GC,I2S(H2I(trg)),"Triggeraction",H2I (TriggerAddAction(trg,function RegisterUnitAmortAction)))
            set trg = null
endfunction[/codes]

=======================================朴素的分割线=======================================
接到反馈说,序写得太垃圾了   好吧   我重新写过
=======================================以下为正文=======================================
序:

        春天,我把番茄种进土里;到了秋天,我收获了很多番茄。
        春天,我把老婆种进土里;到了秋天,我成了韦小宝。
        我们在春天办个JASS培训班,希望到了秋天能有很多会JASS的人~   

        欢迎大家加入到JASS学习的行列,我是Everguo(QQ:376856420  e-mail:[email protected]),很荣幸能为这部电子书写序;让一个曾对JASS敬而远之的人来负责撰写JASS教程,命运总是充满作弄人的意味。不过讽刺的是,正是因为在学习JASS的过程中遇到太多挫折,因此我写的教程比较能体恤新手(笑~);JASS不是什么很难的东东,因为像我这样的笨蛋都能学会~

        严正声明,本教程是大家共同的心血,只是由我整理出来罢了。此刻,我继承了GA悠久的历史与传统,eGust、Greedwind在这一刻灵魂护体;在这一刻我不是一个人在写序,我不是一个人~

        目前各种版本的JASS教程已经很多了,那我们为何还要出品这个教程呢:

        首先,最新的JASS教程都要追溯到一年半前的JASSALL,而这段时间以来,涌现很多新东西,需要重新归纳总结。
        其次,很多教程在语法和原理上讲解得比较详细,但讲解形式比较单一,容易让人感觉乏味。
        最后,很多人看明白了教程,但不知接下来该如何做,不能学以致用。


        针对以上几个方面,我们出品的JASS培训班教程具备以下特点:

        一、吸收了以前教程精华,并补充许多新东西。以前的教程,关于“动态注册事件”、“GameCache”、“Return Bug”和“Timer”等内容几乎没有,而在我们的教程中却有相当详细的讲解与大量的教学演示。
        二、本教程在讲解上形式灵活多样,举的例子丰富多采;即便你不想学JASS,也可当小说来看~
        三、本教程与其他教程最大的不同,是非常重视动手能力;因此在你学习本教程时,记得打开WE和JassShop(或JassCraft),照着教程一步步做。此外教程留有作业和考试,能很好地提高学员动手能力。

       当然,教程中的东西不可尽信,何况还是由我这种笨蛋编写的~   
       我写这教程的目的,与其说是传授些JASS方面知识给大家,倒不如说是手把手教大家怎么跨进JASS大门。不过,虽然我为这教程的通俗易懂而骄傲,但那不意味着你花几个小时把这教程看完就能掌握JASS。教程,只能引导;学习最重要的还是靠自身不断地实践和摸索。
      
        那么,首先简单介绍下吧:除了JASS培训班的教程,本电子书还收录了Greedwind和eGust等前辈编写的部分教程,zyl910和Red_wolf等高手的经典帖子,JASS培训班习题、试卷和优秀学员完成的作业,常见JASS问题与解答收录帖,以及大量JASS演示等资源。

        此外,月协出品的三章JASS教程,很黄很暴力,如此您对此反感,请不要看;我犹豫了很久,最终还是决定把这三章教程也收录进来;虽然个中内容有些YD,但据看过的朋友反映,月协的这几篇教程非常好懂。通俗易懂的教程就是好教程,至少我是这么认为滴~       


本电子书内容主要分为三部分
      一、基础教程。(为什么要学JASS,初学者如何上手,以及由Greedwind和eGust编写的部分教程。)
      二、JASS培训班教程。(电子书的主体部分,包含从初级到高级的教程。)
      三、其他资源。(经典帖子,月协出品的YD_JASS教程,大量演示,JASS培训班习题试卷和学员提交的作业。)

学习流程:
        一、首先,去这里(连接1)看下自己的JASS水平属于哪个阶段,每个阶段都有针对性的学习方法(连接2)。
        二、确认自己所处阶段后,按给出的学习方法学习;学习到一定阶段后,可以找试题自测(连接2),自测分数达标后可进入下一阶段学习。
        三、在学习完每章教程后,最后能主动完成作业;有兴趣可看下其他学员完成的作业(连接3)。
        四、当你能做出教程指定的演示(连接4),那么,你可以出师了。
        五、无论是在学习期间,还是学完教程内容以后,最好能多去论坛(
www.ourga.com)逛逛,多在QQ群(7847868)里发言,同其它人交流JASS,这对能力提高帮助很大。

寄语:
      我一直在想,我是什么人,又扮演什么角色,在别人眼里我是什么形象……
        我做图,做演示,写教程,写小说,搜集模型……涉猎太广,整天都很忙——我也不知道我在忙什么。
        我扮演的角色也很多,最让人印象深刻的,是YD男的形象;但有朋友说,他发现我不但不YD,反而很正派——或许我是在借YD来掩饰真实的自己吧!
        所以,有人说,我像火影里的自来也。
        渐渐地,我也觉得自己很像。
        
        自来也很强,但大家不是因他强而喜欢他,火影里比他强的大有人在;大家喜欢他是因为他很正派,有责任心,培养几个优秀的弟子。
        责任心,对我来说,多么沉重的负担;渐渐地我开始觉得振兴GA是一个不切实际的梦想,而我在把精力浪费在一个永远填不满的无底洞中。不过,我跟自来也一样,“直到最后都不肯轻言放弃”。
            
        火影每周更新一次,当看到自来也跟BOSS作战时,我也在战斗;自来也的敌人中有他曾经的弟子,在攻击我的人中有我曾经引荐来GA的人。
        
        自来也死了,我的帐号被删了;自来也说,“故事精彩与否全靠最后一笔。”而这个结局我很喜欢,我的故事也因完美的句号而那样精彩。
              

杀人扣分饼干屋 14:45:03
        白银你觉得,我会是那种顾忌别人说法的人嘛?
扫雷必爆无语众 14:45:13
        555
扫雷必爆无语众 14:45:25
        邪恶的蕾妮要做啥坏事老。
杀人扣分饼干屋 14:45:29
        实话告诉你吧,我现在就要去把everguo砍了
杀人扣分饼干屋 14:45:38
        而且不会留下任何解释


红衣番茄扣分者 18:27:28
        我已经把everguo砍掉了,怎么群里没有什么表示呀?
红衣番茄扣分者 18:27:50
        我好期待很多人哭爹喊娘
红衣番茄扣分者 18:28:07
        然后我会说,你们再怎么哭我也不会改变主意地
杀人扣分饼干屋 18:28:17
        嗯嗯,就是如此



      你错了,哭的人是你。
        “忍者的世界不追求生而追求死,忍者的世界重要的不是如何活着,而是以何种方式死。”
        这是自来也说的,也同样适合于我。
        这正是我追求的结局,我发现我做的一切是那样的有意义。

        自来也死前,将卷轴托蛤蟆传给弟子,将希望托付;那我在离开前,是不是也要为大家留下点什么。
        于是我借朋友的号,整理了教程,在朋友帮助下出了这本电子书。GA应以培养新人为主,现在GA很需要这样一部教程。

        我相信自来也的信念,会有人继承下去;而我振兴GA的梦想,也会有人帮我实现。

              那就,拜托大家了……


鸣谢:
        最该感谢的,自然是“牵着老鼠漫步”,他帮忙做这电子书,耗费很多时间与精力;感谢明之大魔王、trevors帮忙指出电子书上个版本中的BUG;谢谢hackwaly借我帐号完成这次编辑……还有更多要感谢的朋友,篇幅有限,只能把这份谢意藏于心中了。

备注:
        放上JassCraft绿色免安装中文版,它自带语法纠错功能,我一直在用。  


=======================================朴素的分割线=======================================

   
回复

使用道具 举报

发表于 2008-1-25 12:18:48 | 显示全部楼层
LZ。。。我对你五体投地啊,太THANK了
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-5-3 03:26 , Processed in 0.548909 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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