|
楼主 |
发表于 2008-12-11 19:00:03
|
显示全部楼层
第二章 jass使用举例
一 函数的使用
1〉 递归调用
跟我的专业有关,我很喜欢递归调用,语句简单而思想深刻,可以说,这就是一种美。递归调用好处很多,也有缺点,好处在于:
1。一般来说不需要任何变量
2。处理诸如调换参数位置的问题时很方便
3。引起迭代(这点是递归独一无二的)
缺点在于,比起循环体占用更多系统资源
对于函数的递归调用,一般用于数学计算,下面给出几个递归调用的实例:
function Nn takes integer a,integer b returns integer
if b==0 then
return 1
endif
return a*Nn(a,b-1)
endfunction
这个函数在做正整数的正整数次幂运算,比起原有的实数的实数次幂运算效率必然更高,应用也是比较广泛的
function ABC takes integer a,integer b,integer c returns integer
if (a<=b and b<=c) then
call BJDebugMsg(I2S(a)+" "+I2S(b)+" "+I2S(c))//BJ函数 作用是对所有玩家发送消息
endif
if b<a then
return ABC(b,a,c)
endif
if c<b then
return ABC(a,c,b)
endif
if c<a then
return ABC(c,b,a)
endif
return 0
endfunction
这个函数则是将abc三个整数从小到大排序
毕竟在实际制作地图的时候 数学计算并不会如此复杂 用到的递归调用也是非常之少的,在此仅作为一种兴趣去看看就好了。
2〉代替重复的语句
下面举2个例子,不仅在函数中实现了代替重复语句的作用,而且应用坐标计算,可以彻底抛弃点变量
function LandMoveByFace takes unit u,real S returns nothing
local real face = GetUnitFacing(u) //获取单位面向角
local real X = GetUnitX(u) + S * CosBJ(face)//计算新X坐标
local real Y = GetUnitY(u) + S * SinBJ(face)//计算新Y坐标
call SetUnitX(u,X)
call SetUnitY(u,Y)
endfunction
按单位面向方向移动单位
function UnitHelper takes unit WhichUnit,integer UnitType,integer SkillID,string ActID,real CreateX,real CreateY,real TargetX,real TargetY returns nothing
local unit u=CreateUnit(GetOwningPlayer(WhichUnit),UnitType,CreateX,CreateY,0)
call ShowUnit(u,false)
call UnitApplyTimedLife(u,'BHwe',2)
call UnitAddAbility(u,SkillID)
call IssuePointOrder(u,ActID,TargetX,TargetY)
set u=null
endfunction
又比如说向我们以前要创建一个辅助单位释放技能要写一堆东西,现在只需要1条函数就可以搞定
函数的实例不需要很多 它是基础中的基础 在很多别的地方还会再见到它的
二 动态注册
前面我花费了大量的口舌在讲解触发器原理,如果你看懂了,那么接下来的动态注册问题你会轻松很多
动态注册着力于解决2个问题
1。触发器运行效率问题
2。某些事件只能监视指定单位 对于任意单位 希望也可以以此事件为触发事件怎么办?
function CameraTimer takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer ID = GetInteger(t,"id")
if udg_Real_CameraCircle_H[ID] != 0 then
set udg_Real_CameraCircle_T[ID] = udg_Real_CameraCircle_T[ID] + udg_Real_CameraCircle_H[ID]
endif
if udg_Real_CameraAngle_H[ID] != 0 then
set udg_Real_CameraAngle_T[ID] = udg_Real_CameraAngle_T[ID] + udg_Real_CameraAngle_H[ID]
if udg_Real_CameraAngle_T[ID] < 300 then
set udg_Real_CameraAngle_T[ID] = 300
endif
if udg_Real_CameraAngle_T[ID] > 345 then
set udg_Real_CameraAngle_T[ID] = 345
endif
endif
if GetLocalPlayer() == Player(ID - 1) then
call SetCameraField(CAMERA_FIELD_ROTATION,udg_Real_CameraCircle_T[ID],0)
call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK,udg_Real_CameraAngle_T[ID],0)
endif
set t = null
endfunction
function CameraControl takes nothing returns boolean
local trigger tri = GetTriggeringTrigger()
local eventid eID = GetTriggerEventId()
local integer ID = GetInteger(tri,"id")
if(eID == ConvertPlayerEvent(261) or eID == ConvertPlayerEvent(264))then
set udg_Real_CameraCircle_H[ID] = udg_Real_CameraCircle_H[ID] + .7
endif
if(eID == ConvertPlayerEvent(262) or eID == ConvertPlayerEvent(263))then
set udg_Real_CameraCircle_H[ID] = udg_Real_CameraCircle_H[ID] - .7
endif
if(eID == ConvertPlayerEvent(265) or eID == ConvertPlayerEvent(268))then
set udg_Real_CameraAngle_H[ID] = udg_Real_CameraAngle_H[ID] + .3
endif
if(eID == ConvertPlayerEvent(266) or eID == ConvertPlayerEvent(267))then
set udg_Real_CameraAngle_H[ID] = udg_Real_CameraAngle_H[ID] - .3
endif
set tri = null
set eID = null
return false
endfunction
function CameraTrigger takes integer ID returns nothing
local trigger tri = CreateTrigger()
local player p = Player(ID - 1)
local timer t = CreateTimer()
call SaveInteger(t,"id",ID)
call SaveInteger(tri,"id",ID)
call TimerStart(t,.01,true,function CameraTimer)
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(261)) //left Press
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(262)) //Release
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(263)) //right Press
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(264)) //Release
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(265)) //down Press
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(266)) //Release
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(267)) //up Press
call TriggerRegisterPlayerEvent(tri,p,ConvertPlayerEvent(268)) //Release
call TriggerAddCondition(tri,Condition(function CameraControl))
set t = null
set tri = null
endfunction
方向键控制镜头的函数 用到了动态注册
一般来说 方向键操作使用动态注册比较多
新手的话重点观察最后一个函数CameraTrigger
所谓动态注册 是指对T的事件注册 关于前面的函数可以暂时无视
三 缓存代替全局变量的动态创建
有人说缓存的使用乃是jass的精华所在,我觉得与其说这是jass的精华,不如说这是前辈们给我们留下的一笔财富,由于缓存的存在,我们曾经讨厌的全局变量冲突问题终于很好的解决了(虽然在效率上不是很完美),废话不多说,下面来具体介绍。
所有用UI的人一定在作图的时候碰到这样的问题,一个技能需要用到全局变量,而又有时间间隔,那么这个技能所使用的全局变量如果不是数组那就只能给某个单位独自享用了,或者即使用了数组,恐怕也不过是一个玩家只能同时静态拥有有限的那么几个单位可以享用这个技能,否则,将无法解决变量冲突问题。关于后者,其实在实践中已经较好的解决了变量冲突问题,因为实际运用中需要一个玩家动态拥有一些单位可以享用上述技能是很少见的,但是在理论上并没有解决问题,我们希望真正在理论上解决此问题,让我们的好创意不受任何阻碍,能让我们YY无极限,接下来便为大家隆重介绍缓存的使用。这里是一大块内容,所以请大家有耐心的看下去。
1〉缓存是什么鬼?
缓存全称游戏缓存,E文名GameCache,作用是储存数据用途,可以储存整数,实数,布尔,字符串4种类型(还可以储存单位,不过不必理会),所储存数据的生存期和全局变量相同,简单说就是只要你不删除,所储存的数据永远在那不会变。那么它和全局的区别在于?
全局数组是一维数组,而且数组上限8000+,一个数组也就只能储存8000+数据,缓存看作变量的话则是2维数组,而且很有意思的是,缓存这个特殊的数组的数组角下标不是整数,而是字符串,那么这个特别的数组可以说所能储存的数据是无限的(其实有上限,不过我向你保证,你绝对用不到),不过对缓存储存数据的方式并不像给变量赋值那么方便,是通过使用CJ函数来实现储存数据和读取数据的
native StoreBoolean takes gamecache cache, string missionKey, string key, boolean value returns nothing
native StoreInteger takes gamecache cache, string missionKey, string key, integer value returns nothing
native StoreReal takes gamecache cache, string missionKey, string key, real value returns nothing
native StoreString takes gamecache cache, string missionKey, string key, string value returns boolean
native GetStoredBoolean takes gamecache cache, string missionKey, string key returns boolean
native GetStoredInteger takes gamecache cache, string missionKey, string key returns integer
native GetStoredReal takes gamecache cache, string missionKey, string key returns real
native GetStoredString takes gamecache cache, string missionKey, string key returns string
一定有人看到这里就开始害怕了,不知道上面的是什么东东,这些都是CJ函数,你想看习惯,那就把所有的native换成function当成普通函数去看就行了
来解释一下这几条函数,Store表示储存,后面跟的是指储存的类型,GetStored表示获取已储存过的内容,后面跟的是所储存的类型。拿第一条以及和它相对应的第五条来举例:
call StoreBoolean(udg_Cache,"First","Second",true)
set a=GetStoredBoolean(udg_Cache,"First","Second")
这里第一句表示对udg_Cache这个变量所指代的缓存创建一个名为First的文件夹,名为Second的文件,Second这个文件所储存的数据是true
第二句表示设置a变量值为udg_Cache这个变量所指代的缓存的一个名为First的文件夹,名为Second的文件所储存的数据
很简单吧
这里要注意几点
1。缓存必须要在使用它之前被创建,创建缓存的函数请自行查询
2。缓存文件名和文件夹名不区分大小写
3。缓存在没有存入数据时候默认值分别是false,0,0.00,""
2〉传说中的return BUG?
在说return BUG以前,来回顾一下变量,我们见过的变量类型千千万,但实际上基本类型只有5种,布尔,整数,实数,字符串和句柄
前4种在全局变量中我们都见过了,最后一种使用UI的人一般不怎么熟悉,而这最后一种却又是非常重要的一种,它类似于内存的一个地址,很多变量的基变量都是它。
啥是基变量?(我觉得这里准确说是基类,然后以此为基的类则是继承与派生,学过C++的会很快理解这一段,没学过的无视吧)举个例子,比如说单位类型是我们常见的变量类型,如果是你,会通过什么方式记录单位类型呢?起名字呗~很好,这是很好的方法,但只是针对人类而言,人类对有意义的东西处理速度较快,但是机器却反之,机器处理一串数字要比一个名字(字符串)快得多,所以实际上采取整数型作为单位类型的基变量,一个整数对应一个单位类型(且是一一对应)。所以我们看这样一个常用的函数
native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit
带入值的第2个unitid大概是单位类型的意思,但是仔细看参数类型,没错,是整数型,但是,我们在调用这个函数的时候,第二个带入值仍然可以是单位类型这个变量类型,不过如果要求带入单位类型,却不能够带入整数型,原因么很简单,整数型变量的取值范围比单位类型的大,如果要求带入单位类型却带入整数,而这个整数却不在单位类型取值范围内则会出错,扩展到一般的基变量和派生类别也有相同的结论。那么,现在好理解什么是基变量了吧?
了解基变量以后,来介绍一个非常重要的变量类型,句柄型,handle,它是众多变量类型的基变量,比如单位,比如物品,比如单位组,曾经有人列过表,表里说的很详细,哪些变量类型是基于handle的,我就不列了,你可以参考其它教程,比如GA出品everguo写的渣字图标的教程附录,那里面很详细,不过一般来说你不需要死记,判断一个变量类型是否以handle为基变量,如果这个变量类型在一张图中可以创建多个不同的具体对象,它一般就是以handle为基变量的,比如步兵可以在地图中创建多个,每个步兵都是具体对象,互相区分,而步兵类型不论你创建多少个步兵,步兵这种类型只能有一个,那么步兵这种类型对应的变量类型就不是以handle为基变量的变量类型。
下面介绍return BUG
所谓return BUG就是jass的一个漏洞,而这个漏洞却帮助我们完成了伟大的事业。来看标准的return BUG漏洞下的函数
function H2I takes handle H returns integer
return H
return 0
endfunction
前面已经说过检查机制的漏洞,这个只是原理,你就算不理解也没有关系(很多教程老爱强调原理,实际上对于新手原理根本不是重点),那不是重点,重点在于,上面这个函数在干嘛?我们从它的运行过程去理解它,带入了一个handle型变量,前面说过,由于这是基变量,那么我们可以带入任何以此为基变量的变量类型,比如我们熟悉的单位,这个单位对应一个handle值,于是函数把这个handle值返回了,然而要求返回类型确是整数型,这个时候计算机会怎样呢?不让你猜了,让你猜你也猜不出来,告诉你答案,计算机这个时候会把handle值转化成一段整数,这个整数有的人(比如学过C的人)可能会理解为变量地址,那么我告诉你,这个是不是地址我不清楚,也不重要,关键在于,转化后的整数集合与原有的handle所构成集合是同构的!通俗来说就是一一对应的。我们理解这一点要比知道return BUG原理重要得多,因为你就算理解return BUG原理对你应用它本身也没有什么帮助,而因为这里的一一对应性,我们可以理解为,转化后的整数就是handle的一个编号。并且肯定地说,H2I转化过来的值再转化回去不会发生偏差,也就是说H2I如果将一个单位转化成一段整数,那么拿这段整数转化回去仍然是之前的那个单位。不过以下代码
function I2H takes integer I returns handle
return I
return null
endfunction
虽然没有任何语法错误,却没有意义,因为handle作为众多变量类型的基变量,本身并没有处理价值,也就是说我们对于handle,它仅仅是只鸡(基)而已,要想有意义,那你想转成什么就要求返回什么吧,比如单位
function I2U takes integer I returns unit
return I
return null
endfunction
好了,讲了这么多东西,现在最精彩的地方才刚刚开始!
3〉实现数据绑定
其实数据绑定我们早就见过了,比如设置单位自定义值,这其实就是数据绑定于一个单位上,但是,这里限制颇多,一个单位只能设置一个自定义值,而且只能是整数!现在我们用缓存+return BUG(实际上利用的是数据转化)来实现数据绑定,以下是我写每张图的开头必有的内容,它就是数据绑定的基础
//------------------------------------------------------------------------------
//转化基础
//------------------------------------------------------------------------------
//return BUG
//------------------------------------------------------------------------------
function H2I takes handle H returns integer
return H
return 0
endfunction
function I2Item takes integer I returns item
return I
return null
endfunction
function I2U takes integer I returns unit
return I
return null
endfunction
function I2Tri takes integer I returns trigger
return I
return null
endfunction
function I2TC takes integer I returns triggercondition
return I
return null
endfunction
function I2TA takes integer I returns triggeraction
return I
return null
endfunction
function I2Tm takes integer I returns timer
return I
return null
endfunction
function I2Snd takes integer I returns sound
return I
return null
endfunction
//------------------------------------------------------------------------------
//简化缓存使用
//------------------------------------------------------------------------------
//基础缓存绑定数据到handle
//------------------------------------------------------------------------------
function SaveBoolean takes handle WantToSave,string Ex,string SaveName,boolean SaveData returns nothing
local string S = I2S(H2I(WantToSave)) + Ex
call StoreBoolean(udg_Cache,S,SaveName,SaveData)
endfunction
function GetBoolean takes handle WantToLoad,string Ex,string LoadName returns boolean
local string S = I2S(H2I(WantToLoad)) + Ex
return GetStoredBoolean(udg_Cache,S,LoadName)
endfunction
function SaveInteger takes handle WantToSave,string SaveName,integer SaveData returns nothing
call StoreInteger(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetInteger takes handle WantToLoad,string LoadName returns integer
return GetStoredInteger(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
function SaveReal takes handle WantToSave,string SaveName,real SaveData returns nothing
call StoreReal(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetReal takes handle WantToLoad,string LoadName returns real
return GetStoredReal(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
我为了方便举例,再把多余的东西删掉
//------------------------------------------------------------------------------
//转化基础
//------------------------------------------------------------------------------
//return BUG
//------------------------------------------------------------------------------
function H2I takes handle H returns integer
return H
return 0
endfunction
function I2U takes integer I returns unit
return I
return null
endfunction
//------------------------------------------------------------------------------
//简化缓存使用
//------------------------------------------------------------------------------
//基础缓存绑定数据到handle
//------------------------------------------------------------------------------
function SaveInteger takes handle WantToSave,string SaveName,integer SaveData returns nothing
call StoreInteger(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetInteger takes handle WantToLoad,string LoadName returns integer
return GetStoredInteger(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
function SaveReal takes handle WantToSave,string SaveName,real SaveData returns nothing
call StoreReal(udg_Cache,I2S(H2I(WantToSave)),SaveName,SaveData)
endfunction
function GetReal takes handle WantToLoad,string LoadName returns real
return GetStoredReal(udg_Cache,I2S(H2I(WantToLoad)),LoadName)
endfunction
第2部分,你可以随便找一条函数来看,这就是数据绑定的实现,比如我要邦定一个整数100到单位XX作为这个单位的飞行高度(比如起个名字"flyheight"),只需要
call SaveInteger(XX,"flyheight",100)
OK了
想要获取这个单位的飞行高度只需
return GetInteger(XX,"flyheight")
读者请仔细观察SaveInteger GetInteger SaveReal GetReal并自行体会这里面的用法,这就是数据绑定啦~
一般来说 数据绑定对象常用的有
单位unit 物品item 计时器timer 触发器trigger
当然并不限于这个,你可以发挥自己的无限YY能力,开发更强大的功能
上面说常常绑定于timer计时器上,而之前说的时间间隔导致全局变量冲突问题,实际上解决这个问题就是靠数据往timer上绑定来完成的,由于这些内容涉及到timer计时器,以及与其相配套的函数TimerStart,那么就放到下一节内容里讲吧~
四 触发器运行效率竟是如此之低
之前介绍过触发器的运行原理,我们知道一个触发器需要被注册事件,然后触发器开始监视对象,当条件满足时则触发器开始运行,然后进入条件判断,然后。。。总之有一大堆过程要做,比起函数要复杂的多,那么可想而知触发器的运行效率必然好不到哪去。曾经时候,我们做个跳跃的技能,想必不是用到计时器倒计时就是用了每XX秒事件,这些都是通过触发器来实现的,所以很多人说时间间隔过小多人游戏就容易卡~于是我们拿另外一个东西代替,直接运行函数而不是触发器,效率肯定会高许多:
函数TimerStart
native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
举个最简单的例子
function timer takes nothing returns nothing
call DisplayTextToPlayer(Player(0),0,0,"!!!")
endfunction
function one takes nothing returns nothing
call TimerStart(CreateTimer(),1,true,function timer)
endfunction
如果function one被运行,则玩家一每1秒钟会被发送一条消息,内容是 !!!
这就使TimerStart的基本用法于是,我们再把前面的缓存数据绑定结合起来,跳跃技能的实现,下面给出跳跃函数
function H2I takes handle H returns integer
return H
return 0
endfunction
function I2U takes integer I returns unit
return I
return null
endfunction
function Distance takes real X,real Y,real x,real y returns real
return SquareRoot((X-x)*(X-x)+(Y-y)*(Y-y))
endfunction
function SaveInteger takes handle WantToSave,string SaveName,integer Data returns nothing
call StoreInteger(udg_GC,I2S(H2I(WantToSave)),SaveName,Data)
endfunction
function GetInteger takes handle WantToLoad,string LoadName returns integer
return GetStoredInteger(udg_GC,I2S(H2I(WantToLoad)),LoadName)
endfunction
function SaveReal takes handle WantToSave,string SaveName,real Data returns nothing
call StoreReal(udg_GC,I2S(H2I(WantToSave)),SaveName,Data)
endfunction
function GetReal takes handle WantToLoad,string LoadName returns real
return GetStoredReal(udg_GC,I2S(H2I(WantToLoad)),LoadName)
endfunction
function MoveByAngle takes unit u,real S,real angle returns nothing
call SetUnitX(u,GetUnitX(u)+S*CosBJ(angle))
call SetUnitY(u,GetUnitY(u)+S*SinBJ(angle))
endfunction
function JumpTimer takes nothing returns nothing
local timer t=GetExpiredTimer()
local unit u=I2U(GetInteger(t,"Unit"))
local integer Help=GetInteger(t,"Help")
local real r=GetReal(t,"distance")
local real angle=GetReal(t,"angle")
call MoveByAngle(u,r,angle)
call SetUnitFlyHeight(u,GetUnitFlyHeight(u)+Help/10,99999)
if (Help==-100) then
call PauseTimer(t)
call DestroyTimer(t)
else
set Help=Help-1
call SaveInteger(t,"Help",Help)
endif
set t=null
endfunction
function Jump takes unit u,location TargetPoint returns nothing
local timer t=CreateTimer()
local real distance=Distance(GetUnitX(u),GetUnitY(u),GetLocationX(TargetPoint),GetLocationY(TargetPoint))
call SaveInteger(t,"Help",100)
call SaveInteger(t,"Unit",H2I(u))
call SaveReal(t,"distance",distance/200)
call SaveReal(t,"angle",Atan2(GetLocationY(TargetPoint)-GetUnitY(u),GetLocationX(TargetPoint)-GetUnitX(u))*bj_RADTODEG)
call TimerStart(t,0.01,true,function JumpTimer)
set t=null
endfunction
好了,到此止,如果你全部掌握了,那么你已经可以较好地利用jass干活了,要想深入掌握jass则需要大量的练习和实践,希望各位热爱WE的朋友们能够真正享受WE带给我们的乐趣
五 扩展版 C2I简介
如果你理解H2I了,那么C2I的原理是一样的,实际上,如果全局变量中有code这个变量,我宁愿使用全局变量,可惜的是,没有!于是当我们希望全局传递code变量就变得很麻烦,于是用C2I将code存在缓存中,进行全局传递。具体内容可以参考everguo的帖子
http://bbs.tbswe.com/thread-40406-1-1.html
以上,基本上把前人的研究成果都挖出来了,希望对想学jass的朋友有所帮助 |
|