|
楼主 |
发表于 2009-2-15 10:17:30
|
显示全部楼层
3
我们的帮手是————
【GameCache】
!!!
这个GameCache到底是什么东东呢?
先给你们看一些函数:
(这是从老狼那Copy过来的,把跟技能没关的函数全踢走了)
[codes=jass]native InitGameCache takes string campaignFile returns gamecache
创建游戏缓存
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 StoreBoolean takes gamecache cache, string missionKey, string key, boolean value returns nothing
native StoreUnit takes gamecache cache, string missionKey, string key, unit whichUnit returns boolean
native StoreString takes gamecache cache, string missionKey, string key, string value 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 GetStoredBoolean takes gamecache cache, string missionKey, string key returns boolean
native GetStoredString takes gamecache cache, string missionKey, string key returns string
native RestoreUnit takes gamecache cache, string missionKey, string key, player forWhichPlayer, real x, real y, real facing returns unit
读取缓存数据
native FlushGameCache takes gamecache cache returns nothing
删除并清空整个缓存
native FlushStoredMission takes gamecache cache, string missionKey returns nothing
删除并清空某个缓存分类
(下面的东东个人感觉没太大用)
native FlushStoredInteger takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredReal takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredBoolean takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredUnit takes gamecache cache, string missionKey, string key returns nothing
native FlushStoredString takes gamecache cache, string missionKey, string key returns nothing
清空某个缓存项所储存的指定数据类型的数据,但并不会删除缓存项 [/codes]
注意!!
在使用GameCache之前,最好在前面加一句:
[codes=jass]call FlushGameCache( InitGameCache( <your File> ) )[/codes]
就是说不能直接用InitGameCache(),最好能这样改一下:
[codes=jass]function DebugInitGameCache takes string campaignFile returns gamecache
call FlushGameCache( InitGameCache( campaignFile ) )
return InitGameCache( campaignFile )
endfunction[/codes]
只要想创建一个GameCache,就最好用这个DebugInitGameCache,否则可能会有上一次的缓存未清空,而覆盖这次缓存数据的事发生(我就在这上面吃了亏,血的教训啊~~)。
恩恩…………其实GameCache就是个数据库,用来存放东西的~~~~
怎样存东西呢??
一般来说,用StoreXXXX的话,都会跟ReturnBug函数连在一起。
比较标准的格式:
[codes=jass]call StoreReal( udg_GC, I2S( H2I( <a handle> ) ), <the key>, < value > )
//.......some codes here
local real r = GetStoreReal( udg_GC, I2S( H2I( <the handle> ) ), <the key>)[/codes]
比方说,我要每4秒,存储一个单位的现有生命值,对上一次(4秒前)存储的生命值进行比较,每有100点生命值的差距,对这个单位造成40点的伤害,再造成上一次对这个单位造成的伤害一样数值的伤害(这一次总伤害=(生命差距/100)*40+上一次的伤害)。
共造成3次伤害。
那么,我们可以这样做:
[codes=jass]globals
gamecache db_gc
endglobals
function Init takes nothing returns nothing
call FlushGameCache( InitGameCache( "db.w3v" ) )
set db_gc = InitGameCache( "db.w3v" )
endfunction
//先运行
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 DeBuffAbility_Action takes nothing returns nothing
local timer ti = GetExpiredTimer()
local string ti_h = I2S(H2I( ti ))
local integer time = GetStoredInteger( db_gc, ti_h, "Time" )
local unit u = null
local real life = 0.0
local real old_life = 0.0
local real damage = 0.0
if time > 3 then
call FlushStoredMission( db_gc, ti_h )
call DestroyTimer( ti )
set ti = null
else
set u = I2U(GetStoredInteger( db_gc, ti_h, "Unit" ))
set life = GetUnitState( u, UNIT_STATE_LIFE )
set old_life = GetStoredReal( db_gc, ti_h, "UnitLife" )
if old_life - life > 0.0 then
set damage = GetStoredReal( db_gc, ti_h, "Damage" )
set damage = I2R( R2I( old_life - life ) / 100 ) * 40.0 + damage
endif
call SetUnitState( u, UNIT_STATE_LIFE, life - damage )
call StoreReal( db_gc, ti_h, "UnitLife", life )
endif
set u = null
set ti = null
endfunction
function DeBuffAbility takes unit u returns nothing
local timer ti = CreateTimer()
local string ti_h = I2S(H2I( ti ))
call StoreInteger( db_gc, ti_h, "Unit", H2I( u ) )
call StoreReal( db_gc, ti_h, "UnitLife", GetUnitState( u, UNIT_STATE_LIFE ) )
call TimerStart( ti, 4, true, function DeBuffAbility_Action )
set ti = null
endfunction
//技能函数[/codes]
这个GameCache+ReturnBug的结合运用,使你可以把任何一个Handle/Code类型的变量的地址作为MissionKey,把剩下的一个名目作为Key来存储一些数据。也就是说,一个Handle/Code类型的实例,你可以通过GameCache+ReturnBug,对它捆绑一个指定含义的数据,以便你在其它地方使用这些数据。
对于那些使用GUI Trigger的人来说,这是不可能的…………
(我说的不包括专门改造过的GUI)
看到了timer,不知道大家知不知道这个timer的含义呢?
没错,就是GUI中的计时器。
至于everguo说的此timer非彼计时器的鬼(?)话,大可不必去信,timer就是计时器,计时器就是timer。
唯一的差别在于,
GUI的计时器,【开启计时器】只有三个参数:
计时器,是否循环,时间。
但Jass的timer,它的TimerStart,则是有四个参数:
timer,boolean,real,code。
timer:计时器,boolean:是否循环,real:时间,code:当到时间(每次循环都触发一次)时,执行这个code变量指向的takes nothing returns nothing的一个函数。
(code:函数指针类型 用于指向内存中的函数地址,只不过只能指向takes nothing returns nothing/boolean的函数)
一般来说,所有的传递给默认JASS函数的code变量,都是“function ”+你要指向的那个函数。
Trigger的Action是code,所有的condition和boolexpr都是通过Condition()将code转变的,timer到时后的执行函数是code,ForGroup的函数还是code。
code函数的参数为无,他真正的参数是通过各种各样的接口函数:如GetExpiredTimer(),GetTriggerUnit()………………传递的。
而code函数的参数取决于code传递到的那个函数(传递给Trigger则是看Trigger的Event(事件))。
TimerStart的参数只有一个:GetExpiredTimer()
就是【获得到时的计时器】的意思。
而我们通过这个timer,就可以从GameCache庞大的数据中找到我们所要的东西啦~~
比如刚才那个DebuffAbility,就存储了单位、单位上一次的生命、这个timer运行了几次等等的数据。
当然,可能上面这个演示看的你眼花缭乱,但是我为大家编了一个比较简单的演示:
改版随机伤害风暴之锤。
集合了GameCache+ReturnBug+Timer,大家可以参考。
我好像以前曾经发过这个东东,所以现在就长话短说吧:
原版随机伤害的风暴之锤,是在单位释放技能的时候就给予目标伤害。这是很不合理的。
但是现在我们有了timer这一利器,我们又可以从释放单位与目标距离和锤子的速率(以距离/秒为单位)算出锤子到达目标的时间。那么用timer将伤害拖后就行了。
函数如下:
[codes=jass]globals
gamecache xlgc = null
endglobals
function H2I takes handle h returns integer
return h
return 0
endfunction
function H2S takes handle h returns string
return I2S(H2I(h))
endfunction
function GetGC takes nothing returns gamecache
if xlgc == null then
call FlushGameCache(InitGameCache("XLGC.w3v"))
set xlgc = InitGameCache("XLGC.w3v")
return xlgc
endif
return xlgc
endfunction
function SetTimerUnit takes timer t, unit u, integer number returns nothing
call StoreInteger(GetGC(),H2S(t),"Unit"+I2S(number),H2I(u))
endfunction
function GetTimerUnit takes timer t, integer number returns unit
return GetStoredInteger(GetGC(),H2S(t),"Unit"+I2S(number))
return null
endfunction
function GetTimerReal takes timer t, string key returns real
return GetStoredReal(GetGC(),H2S(t),key)
endfunction
function DistanceBetweenUnits takes unit uA, unit uB returns real
local real dx = GetUnitX(uB) - GetUnitX(uA)
local real dy = GetUnitY(uB) - GetUnitY(uA)
return SquareRoot(dx * dx + dy * dy)
endfunction
function Bolt_GetFlyTime takes unit u,integer ability_level, unit target returns real
return DistanceBetweenUnits(u,target) / 200.00
endfunction
function Bolt_GetDamage takes unit u,integer ability_level,unit target returns real
local real damage = I2R(GetHeroStr(u,true))*ability_level
set damage = damage*250-I2R(GetHeroStr(target,true))
set damage = damage*GetRandomReal(1,1.33)
return damage
endfunction
function Bolt_Timer_Action takes nothing returns nothing
local timer ti = GetExpiredTimer()
local unit u = GetTimerUnit(ti, 0)
local unit target = GetTimerUnit(ti, 1)
local real damage = GetTimerReal(ti, "Damage")
call UnitDamageTargetBJ( u, target, damage, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL )
call CreateTextTagUnitBJ( I2S(R2I(damage)), target, 4, 10, 100, 0, 0, 0 )
call SetTextTagVelocityBJ( bj_lastCreatedTextTag, 64, GetTimerReal(ti,"UnitFacing") )
call SetTextTagPermanent( bj_lastCreatedTextTag, false )
call SetTextTagLifespan( bj_lastCreatedTextTag, 5 )
call SetTextTagFadepoint( bj_lastCreatedTextTag, 4 )
call FlushStoredMission(GetGC(),I2S(H2I(ti)))
call DestroyTimer(ti)
set ti = null
set u = null
set target = null
endfunction
function Bolt_Spell takes unit u,integer ability_level,unit target returns nothing
local real BoltFly_time = Bolt_GetFlyTime(u,ability_level,target)
local real damage = Bolt_GetDamage(u,ability_level,target)
local timer ti = CreateTimer( )
call SetTimerUnit(ti,u,0)
call SetTimerUnit(ti,target,1)
call StoreReal(GetGC(),H2S(ti),"Damage",damage)
call StoreReal(GetGC(),H2S(ti),"UnitFacing",GetUnitFacing(u))
call TimerStart(ti,BoltFly_time,false,function Bolt_Timer_Action)
set ti = null
endfunction[/codes]
其实,这也是有Bug的,因为这个锤子的伤害的延迟时间是建立在目标单位不会移动的情况下,实际情况必然不是这样的。
至于如何修改吗~~~~
大家自己好好想一想啦~~
(提示:用极端间隔的timer+不断存储坐标并按照速度靠近目标,直到目标与坐标相差不远模拟锤子的移动)
timer有三种计时方式,一是有循环,而是没有循环。但运用的方式基本却有三种:
第一种:
倒计时:非常简单,就是时间到了运行指定函数。一般用于技能中的简单等待。
(PS:说一句,GUI的等待,可能导致网络时卡机,而且效率和精度都比timer低,所以学了JASS,就别用等待了)
第二种:中长时间的循环运行,一般是隔X秒do something的技能用,比较经典的运用就是Dota/3C这种对抗地图的循环不间断刷兵了。
第三种:超短时间的循环运行,一般用来击退效果什么的用。
(PS:学了JASS,短距离的瞬移,最好就用SetUnitX+SetUnitY,效率很高。但是中长距离的瞬移,就只能用SetUnitPosition了)
还有特殊的一种,就是超短时间的倒计时,甚至于它的时间设置可能为0.0。这个是用来做一些特殊的事情的,一般不会碰到。
然后,再来向大家介绍一下,关于timer的一些函数:
[codes=jass]native CreateTimer takes nothing returns timer
//创建timer
native CreateTimerDialog takes timer t returns timerdialog
//创建计时器窗口(timerdialog)
native DestroyTimer takes timer whichTimer returns nothing
//删除timer
native DestroyTimerDialog takes timerdialog whichDialog returns nothing
//删除timerdialog
native GetExpiredTimer takes nothing returns timer
//获得到时的timer
native IsTimerDialogDisplayed takes timerdialog whichDialog returns boolean
//timerdialog是否可见
native TimerDialogDisplay takes timerdialog whichDialog, boolean display returns nothing
//设置timerdialog是否可见
native TimerDialogSetRealTimeRemaining takes timerdialog whichDialog, real timeRemaining returns nothing
//此函数玄妙无穷,下面特讲
native TimerDialogSetSpeed takes timerdialog whichDialog, real speedMultFactor returns nothing
//设置timerdialog的显示速度( 若speed设为2,原先为1, 则显示的时间为原先显示的时间*现speed/原speed )
native TimerDialogSetTimeColor takes timerdialog whichDialog, integer red, integer green, integer blue, integer alpha returns nothing
//timerdialog显示的时间的颜色
native TimerDialogSetTitle takes timerdialog whichDialog, string title returns nothing
//timerdialog的标题
native TimerDialogSetTitleColor takes timerdialog whichDialog, integer red, integer green, integer blue, integer alpha returns nothing
//timerdialog的标题的颜色
native PauseTimer takes timer whichTimer returns nothing
//暂停timer
native ResumeTimer takes timer whichTimer returns nothing
//恢复暂停的timer
native TimerGetElapsed takes timer whichTimer returns real
//获得timer已记的时间( Elapsed = TimeOut - Remaining )
native TimerGetRemaining takes timer whichTimer returns real
//获得timer剩下的时间( Remaining = TimeOut - Elapsed )
native TimerGetTimeout takes timer whichTimer returns real
//获得timer设置的时间( TimeOut = Remaining + Elapsed )
native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
//开启timer
native TriggerRegisterTimerEvent takes trigger whichTrigger, real timeout, boolean periodic returns event
//Trigger注册时间到的事件
native TriggerRegisterTimerExpireEvent takes trigger whichTrigger, timer t returns event
//Trigger注册timer到时的事件(不过这个直接用TimerStart里的code就行,这个现在没用了) [/codes]
注意!!
现在特别说一下:
[codes=jass]native TimerDialogSetRealTimeRemaining takes timerdialog whichDialog, real timeRemaining returns nothing[/codes]
这个函数可以将一个timerdialog显示的时间设置为timeRemaining ,而不管它对应timer的时间。
而且它的倒计时就直接按自己的走,如果对应的timer暂停,这个timerdialog的显示时间也会按照它的speed走动。
也就是说,这个timerdialog完全是自己一个timer了!
╮(╯▽╰)╭扯远了~~~
还有,再说一句,timer暂停后,你用ResumeTimer,貌似是不能把它恢复的⊙﹏⊙b汗。
只能直接用TimerStart对这个timer重来……………………
还有timer删除不能直接删,最好先PauseTimer再DestroyTimer。
基本上,timer只能和GameCache+ReturnBug出现,所以,请不要忘了用FlushStoredMission()清除timer的数据哦~
一般timerdialog要在timer之前被删除。
循环的timer(尤其是技能用),一般都会循环N次就停下,很少有不停止循环的。一般来说,我们都是这样做的:
[codes=jass]function TimerAction takes nothing returns nothing
local timer ti = GetExpiredTimer()
local integer run = GetStoredInteger( <GameCache>, I2S(H2I( ti )), "TimeRun" )
//你的timer的局部变量
if run > [你指定的循环次数] then
call FlushStoredMission( <GameCache>, I2S(H2I( ti )) )
call PauseTimer( ti )
call DestroyTimer( ti )
else
call StoreInteger( <GameCache>, I2S(H2I( ti )), "TimeRun", run + 1 )
//你的timer的动作
endif
set ti = null
endfunction[/codes]
这样在每一次timer计时到时,就将计数器+1,如果这个计数器超过了你指定的循环的次数,那么就删除这个计时器。
嗯~~~~timer讲完了~~该讲什么了呢?……………………
哦对了,貌似还有个动态注册事件,个人感觉跟技能没太大用,在LZ贴里的JASS培训班——JASS培训教程——中级教程——动态注册详解里有。有兴趣的,可以去看看。
嗯,在那里头,还有个动态注册事件的演示,是个技能模板(?),还不错,大家可以看一看。
Timer一个极为经典的运用就是无数技能中的击退效果了。
[codes=jass]globals
gamecache ba_gc = null
constant real Map_MinX = -2048.0
constant real Map_MinY = -2048.0
constant real Map_MaxX = 2048.0
constant real Map_MaxY = 2048.0
endglobals
function Init takes nothing returns nothing
call FlushGameCache( InitGameCache( "ba.w3v" ) )
set ba_gc = InitGameCache( "ba.w3v" )
endfunction
//先运行
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 SetCacheReal takes string MissionKey, string Key, real value returns nothing
call StoreReal( ba_gc, MissionKey, Key, value )
endfunction
function GetCacheReal takes string MissionKey, string Key returns real
return GetStoredReal( ba_gc, MissionKey, Key )
endfunction
function BackAbility_Action takes nothing returns nothing
local timer ti = GetExpiredTimer()
local string ti_h = I2S(H2I( ti ))
local real time = GetCacheReal( ti_h, "Time" )
local real speed = GetCacheReal( ti_h, "Speed" )
local real speedadd = GetCacheReal( ti_h, "SpeedAdd" )
local real timeout = GetCacheReal( ti_h, "TimeOut" )
local real angle = GetCacheReal( ti_h, "Angle" )
local unit target = I2U(GetStoredInteger( ba_gc, ti_h, "Target" ))
local real elapsed = GetCacheReal( ti_h, "Elapsed" )
local real x = GetUnitX( target ) + speed * Cos( angle * bj_DEGTORAD )
local real y = GetUnitY( target ) + speed * Sin( angle * bj_DEGTORAD )
local string effectpath = GetStoredString( ba_gc, ti_h, "Effect" )
local real effecttime = GetCacheReal( ti_h, "EffectTime" )
local effect e = null
if elapsed > timeout or GetUnitState( target, UNIT_STATE_LIFE ) <= 0.0 then
call FlushStoredMission( ba_gc, ti_h )
call PauseTimer( ti )
call DestroyTimer( ti )
call PauseUnit( target, false )
call SetUnitPathing( target, true )
else
call SetCacheReal( ti_h, "Elapsed", elapsed + time )
if Map_MinX < x and x < Map_MaxX then
call SetUnitX( target, x )
endif
if Map_MinY < y and y < Map_MaxY then
call SetUnitY( target, y )
endif
call PauseUnit( target, true )
if effectpath != null and effectpath != "" and ModuloReal( elapsed, effecttime ) == 0.0 then
set e = AddSpecialEffect( effectpath, GetUnitX( target ), GetUnitY( target ) )
call DestroyEffect( e )
endif
call SetCacheReal( ti_h, "Speed", speed + speedadd )
endif
set ti = null
set target = null
set e = null
endfunction
function BackAbility takes unit target, real time, real speed, real speedadd, real timeout, real angle, string effectpath, real effecttime returns nothing
local timer ti = CreateTimer()
local string ti_h = I2S(H2I( ti ))
call SetCacheReal( ti_h, "Time", time )
call SetCacheReal( ti_h, "Speed", speed * time )
call SetCacheReal( ti_h, "SpeedAdd", speedadd * time )
call SetCacheReal( ti_h, "TimeOut", timeout )
call SetCacheReal( ti_h, "Angle", angle )
call StoreString( ba_gc, ti_h, "Effect", effectpath )
call SetCacheReal( ti_h, "EffectTime", effecttime )
call StoreInteger( ba_gc, ti_h, "Target", H2I( target ) )
call PauseUnit( target , true )
call SetUnitPathing( target, false )
call TimerStart( ti, time, true, function BackAbility_Action )
set ti = null
endfunction[/codes]
好了,先说一下BackAbility的参数:
target = 被击退的那个单位
time = Timer运行的时间间隔(也就是多久单位位移一次)
speed = 单位位移的速度(按距离/秒来计算)
speedadd = 加速度(按距离/秒来计算)(也可以为负数)
timeout = 被击退单位多久才能恢复正常
angle = 击退的朝向
effectpath = 每次过一个effecttime时间时产生的特殊效果的路径
effecttime = 特殊效果添加的时间间隔(请注意一定是time的整数倍)
然后,我们再来重点看一下SetUnitX和SetUnitY的运用。
前面已经说了,SetUnitX+SetUnitY比SetUnitPosition效率要快得多。但是原因是SetUnitX和SetUnitY忽略了大量的检测,所以使用它会比使用SetUnitPosition要危险一些,但是做一些小小的规避即可避免灾难的发生。
1.SetUnitX+SetUnitY如果位移后的坐标超出了地图的总大小,游戏将崩溃。
2.SetUnitX+SetUnitY用在移动速度为0的单位上,虽然你用GetUnitX+GetUnitY发现它确实位移了,但是屏幕上显示的是这个单位还在原地傻站着。
3.SetUnitX+SetUnitY不能用在中长距离(横或纵坐标位移200距离以上)的瞬移上,可能会引发一些很恶心的BUG,这点我曾经在综合区发过贴,不过说得有点唠叨。
…………(可能还有其他的一些副作用,我们就先不深究了)
这些都非常好规避,相信大家应该能做出来吧~~
嗯…………说了这么多,就该说说【我认为的】JASS与GUI的最大区别了,那就是JASS的函数特性使得用JASS会比GUI多了无可比拟的灵活性。JASS可以将很多功能“拼插”在一起,让你可以在相对于使用GUI上节省大量的时间,来完成一个技能。
(╯▽╰)来举个例子,Dota中的裂魂人,会一个招,招名我忘了,但是它是一个攻击中有一定几率击退他攻击的单位并附加伤害的技能。
好了,来分析一下,这个招是怎么弄出来的……
首先是肯定有个击退的效果(当然了,可能还得有一个判断角度的东东)。然后是附加伤害和几率判断,都好弄。
还有一个就是判断它的攻击了。
由于【单位被攻击】这个event是有BUG的,我们就用【任意单位受到伤害】(这个魔兽默认不能实现,只能自己实现,在综合区有一大堆帖子写了)来实现单位被攻击到。当然,这个比较麻烦(而且还是有BUG),就直接写被攻击到之后的Condition吧(偷懒偷懒……):
[codes=jass]globals
gamecache ba_gc = null
constant real Map_MinX = -2048.0
constant real Map_MinY = -2048.0
constant real Map_MaxX = 2048.0
constant real Map_MaxY = 2048.0
endglobals
function Init takes nothing returns nothing
call FlushGameCache( InitGameCache( "ba.w3v" ) )
set ba_gc = InitGameCache( "ba.w3v" )
endfunction
//先运行
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 SetCacheReal takes string MissionKey, string Key, real value returns nothing
call StoreReal( ba_gc, MissionKey, Key, value )
endfunction
function GetCacheReal takes string MissionKey, string Key returns real
return GetStoredReal( ba_gc, MissionKey, Key )
endfunction
function BackAbility_Action takes nothing returns nothing
local timer ti = GetExpiredTimer()
local string ti_h = I2S(H2I( ti ))
local real time = GetCacheReal( ti_h, "Time" )
local real speed = GetCacheReal( ti_h, "Speed" )
local real speedadd = GetCacheReal( ti_h, "SpeedAdd" )
local real timeout = GetCacheReal( ti_h, "TimeOut" )
local real angle = GetCacheReal( ti_h, "Angle" )
local unit target = I2U(GetStoredInteger( ba_gc, ti_h, "Target" ))
local real elapsed = GetCacheReal( ti_h, "Elapsed" )
local real x = GetUnitX( target ) + speed * Cos( angle * bj_DEGTORAD )
local real y = GetUnitY( target ) + speed * Sin( angle * bj_DEGTORAD )
local string effectpath = GetStoredString( ba_gc, ti_h, "Effect" )
local real effecttime = GetCacheReal( ti_h, "EffectTime" )
local effect e = null
if elapsed > timeout or GetUnitState( target, UNIT_STATE_LIFE ) <= 0.0 then
call FlushStoredMission( ba_gc, ti_h )
call PauseTimer( ti )
call DestroyTimer( ti )
call PauseUnit( target, false )
call SetUnitPathing( target, true )
else
call SetCacheReal( ti_h, "Elapsed", elapsed + time )
if Map_MinX < x and x < Map_MaxX then
call SetUnitX( target, x )
endif
if Map_MinY < y and y < Map_MaxY then
call SetUnitY( target, y )
endif
call PauseUnit( target, true )
if effectpath != null and effectpath != "" and ModuloReal( elapsed, effecttime ) == 0.0 then
set e = AddSpecialEffect( effectpath, GetUnitX( target ), GetUnitY( target ) )
call DestroyEffect( e )
endif
call SetCacheReal( ti_h, "Speed", speed + speedadd )
endif
set ti = null
set target = null
set e = null
endfunction
function BackAbility takes unit target, real time, real speed, real speedadd, real timeout, real angle, string effectpath, real effecttime returns nothing
local timer ti = CreateTimer()
local string ti_h = I2S(H2I( ti ))
call SetCacheReal( ti_h, "Time", time )
call SetCacheReal( ti_h, "Speed", speed * time )
call SetCacheReal( ti_h, "SpeedAdd", speedadd * time )
call SetCacheReal( ti_h, "TimeOut", timeout )
call SetCacheReal( ti_h, "Angle", angle )
call StoreString( ba_gc, ti_h, "Effect", effectpath )
call SetCacheReal( ti_h, "EffectTime", effecttime )
call StoreInteger( ba_gc, ti_h, "Target", H2I( target ) )
call PauseUnit( target , true )
call SetUnitPathing( target, false )
call TimerStart( ti, time, true, function BackAbility_Action )
set ti = null
endfunction
function AngleBetweenTwoLocs2Angle_First takes real x1, real y1, real x2, real y2 returns real
local real angle = bj_RADTODEG * Atan2( y2 - y1, x2 - x1)
if angle < 0.0 then
set angle = 360.0 - angle
endif
return angle
endfunction
function AngleBetweenTwoUnits2Angle_First takes unit u1, unit u2 returns real
return AngleBetweenTwoLocs2Angle_First( GetUnitX( u1 ), GetUnitY( u1 ), GetUnitX( u2 ), GetUnitY( u2 ) )
endfunction
function AngleBetweenTwoLocs2Angle_Second takes real x1, real y1, real x2, real y2 returns real
return bj_RADTODEG * Atan2( y2 - y1, x2 - x1) + 180.0
endfunction
function AngleBetweenTwoUnits2Angle_Second takes unit u1, unit u2 returns real
return AngleBetweenTwoLocs2Angle_Second( GetUnitX( u1 ), GetUnitY( u1 ), GetUnitX( u2 ), GetUnitY( u2 ) )
endfunction
function Trig_AnyUnitDamaged_Condition takes nothing returns boolean
local unit target = GetTriggerUnit()
local unit source = GetEventDamageSource()
local real percent = 0.0
local real angle = 0.0
local string effectpath = null
if GetUnitTypeId( source ) == <裂魂人的ID> then
set percent = GetUnitAbilityLevel( source, <招的ID> ) * 15.0
set angle = AngleBetweenTwoUnits2Angle_Second( target, source )
set effectpath = "Abilities\\\\Weapons\\\\TreantMissile\\\\TreantMissile.mdl"
if GetRandomReal( 0.0, 100.0 ) < percent then
call BackAbility( target, 0.03, 300.0, -5.7, 1.5, angle, effectpath, 0.45 )
endif
call DisableTrigger( GetTriggeringTrigger() )
call UnitDamageTarget( source, target, 25.0, true, true, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FORCE, WEAPON_TYPE_WHOKNOWS )
call EnableTrigger( GetTriggeringTrigger() )
endif
set target = null
set source = null
return false
endfunction[/codes]
这里说一下,将【两点间方向】转换成360°的角度的方法(自己想会有点麻烦,最好记下来)
称【两点间方向】中第一个点(即第一个参数)为loc1,第二个点(即第二个参数)为loc2。
则,对于:
loc1,【两点间方向】的返回结果:
如果<0.0则角度为360.0 + 返回结果
其余情况角度=返回结果;
loc2,【两点间方向】的返回结果 + 180.0 = 角度
在上面的代码已经编好:
[codes=jass]function AngleBetweenTwoLocs2Angle_First takes real x1, real y1, real x2, real y2 returns real
local real angle = bj_RADTODEG * Atan2( y2 - y1, x2 - x1)
if angle < 0.0 then
set angle = 360.0 - angle
endif
return angle
endfunction
function AngleBetweenTwoUnits2Angle_First takes unit u1, unit u2 returns real
return AngleBetweenTwoLocs2Angle_First( GetUnitX( u1 ), GetUnitY( u1 ), GetUnitX( u2 ), GetUnitY( u2 ) )
endfunction
function AngleBetweenTwoLocs2Angle_Second takes real x1, real y1, real x2, real y2 returns real
return bj_RADTODEG * Atan2( y2 - y1, x2 - x1) + 180.0
endfunction
function AngleBetweenTwoUnits2Angle_Second takes unit u1, unit u2 returns real
return AngleBetweenTwoLocs2Angle_Second( GetUnitX( u1 ), GetUnitY( u1 ), GetUnitX( u2 ), GetUnitY( u2 ) )
endfunction[/codes]
First是指返回对于loc1的角度
Second是指返回对于loc2的角度
Units就是指算单位间的角度
嗯……你看,一个Dota中的技能几下就被我们给搞定了,是不是很有成就感呢?
那么,我们把等式写上:
击退+伤害+几率+单位被攻击判断=裂魂人的第三个招(⊙﹏⊙b汗,招名记不住啊)!
嗯,如果说,这个还需要一点点工夫的话,那么你直接用技能模板做出来的技能则是非常的方便!举个例子,Dota中的海巨人的大招叫“毁灭”,说白了就是放一圈穿刺…………
那么,我们要怎么做这个技能呢?
首先,我们有这个模板(其实就是我编的啦),功能就是在创造number个玩家p的u_id单位,对以x,y为中心的半径为radius的圆范围的360/number度的每一个点发布order命令,使其释放等级为ab_lv的ab_id技能,最后在lifetime秒后爆炸死亡。
[codes=jass]function SpellAroundAbility takes real x, real y, real radius, integer number, integer u_id, integer ab_id, integer ab_lv, player p, integer order, real lifetime returns nothing
local integer i = 0
local integer max = number - 1
local real angle = 360.0 / number
local unit u = null
local real facing = 0.0
local real targetx = 0.0
local real targety = 0.0
loop
exitwhen i > max
set facing = i * angle
set u = CreateUnit( p, u_id, x, y, facing )
call ShowUnit( u, false )
call SetUnitPathing( u, false )
call SetUnitInvulnerable( u, true )
call SetUnitUseFood( u, false )
call SetUnitX( u, x )
call SetUnitY( u, y )
call UnitAddAbility( u, ab_id )
call SetUnitAbilityLevel( u, ab_id, ab_lv )
set targetx = x + radius * Cos( facing * bj_DEGTORAD )
set targety = y + radius * Sin( facing * bj_DEGTORAD )
call IssuePointOrderById( u, order, targetx, targety )
call UnitApplyTimedLife( u, 'BHwe', lifetime )
call SetUnitExploded( u, true )
set u = null
set i = i + 1
endloop
set u = null
endfunction[/codes]
隐藏单位的东东全都弄好了,那怕你弄个默认的步兵上去(前提是你的隐藏技能不耗魔),一样可以完成。
那么使用这个模板,那就很好搞出“毁灭”了,根据Dota中的技能显示,“毁灭”是个无目标技能,也就是站在原地施放,穿刺以他为中心扩散。
那么模板中的x和y就是GetUnitX(海巨人)和GetUnitY(海巨人)。radius由于穿刺不管发布点的距离多少都会一路刺过去。所以只要小于穿刺的施法距离就可以。number的话,我觉得像是“毁灭”有18个穿刺……,然后u_id就用'hfoo',ab_id就是穿刺的id。ab_lv应该就是GetUnitAbilityLevel( GetTriggerUnit(), GetUnitSpellAbilityId() ),p就是GetOwningPlayer( GetTriggerUnit() ),order就是穿刺的id(852555),lifetime就设成1.0就可以了。
如果要写到触发里,可以先local unit u = GetTriggerUnit(),这样可以省去大量字符。
那么这个技能就是:
[codes=jass]local unit u = GetTriggerUnit()
call SpellAroundAbility( GetUnitX(u), GetUnitY(u), 10.0, 18, 'hfoo', <穿刺id>, GetUnitAbilityLevel( u, GetSpellAbilityId() ), GetOwningPlayer( u ), 852555, 1.0 )
set u = null[/codes]
触发是:
[trigger]Spell
事件
单位 - 任意单位 发动技能效果
条件
(施放技能) 等于 毁灭
动作
自定义代码: local unit u = GetTriggerUnit()
自定义代码: call SpellAroundAbility( GetUnitX(u), GetUnitY(u), 10.0, 18, 'hfoo', <穿刺id>, GetUnitAbilityLevel( u, GetSpellAbilityId() ), GetOwningPlayer( u ), 852555, 1.0 )
自定义代码: set u = null
[/trigger]
是不是很简单呢?
模板能让使用者非常简单的完成一项任务,只需要在参数中设置,就可以达到使用者想要的效果。
这是模板的使用部分的解说,一般来说模板都是由JASS功力很高的人编写的,虽然不至于完美无缺,但是对于一般人而言,不出BUG的几率确实少了很多。而且不会有泄露的问题。当然了,你要是看都不看模板本身一眼就使用的话,出了BUG就不怪这模板了哟~~╮(╯_╰)╭
所以说,技能模板是好处多多的呀~~~~~
当然了,如果大家想自创模板,也是没问题的。
基本上就是把一个技能经过不断地抽象(就是将所有的可变可见效果全部提取成可变量),然后就是优化,将这个模板的开放性增强,或者优化其他的方面。嗯…………也可以说是将技能最基本的动作保留,剩下的都弄成参数让使用者设置。…………形象一点,把技能的肉都扒了,只剩下骨头,至于在使用者手中是塑造出妩媚动人的娇躯呢,还是凶神恶煞的肌肉男,那就不管我们的事啦(稍稍YD一下……)~~~(当然还有可能和别的模板拼在一起或者是被某些人把骨头都改装了~~啦~~)
不过你要看清下面的要求哟~~(别被吓哭了)
对于模板创造者来说,要求一定是严格再严格。
1.不许泄露(string泄露除外)
2.创造者自己能想到的全部BUG都要改(想不到的,也不能强求不是,毕竟我的模板还可能有BUG呢~~)。
3.效率优化,要保证自己的模板又快又好
4.开放性强(下面特别强调)
5.代码要求简明易懂,或者写注释,一定要让使用者明了这个模板运行中会干什么,它是怎样达到效果的(以防止使用者的一些东东和模板发生冲突)。
嗯……重点讲一下【开放性强】这一点吧。就是能够最大限度的满足各种要求,不能让使用者因为模板没有对口的功能而自己改动模板。就拿我开发SpellAroundAbility来说,我的灵感来自于Dota海巨人的“毁灭”,这个技能是以海巨人为中心发射穿刺,如果我拿来就写模板的话,那这个模板的开放性就比较小。我改动的有下列几点:
1.我将中心变为一个点,不是根据单位(海巨人)的坐标来确定中心。
这样的话,使用者就可以不废工夫创造一个以点为中心的技能,而不是创建一个单位再用模板或自己改模板
2.添加半径,穿刺是不需要释放半径的,哪怕就是原地施放穿刺,只要单位的facing对,就可以。我想到如果使用者想达到6烈焰风暴环的效果的话,那这肯定不行。
3.添加lifetime。可以由使用者设定单位的死亡时间,可以让使用者根据自己的情况看单位要不要再活一段时间(比如释放的隐藏技能如果是个娜迦女海巫的龙卷风那就需要单位活上一段时间了)。当然了,一些人还会让马甲单位“瞬间施法”(单位选项中【显示-动画-魔法释放点】和【显示-动画-魔法释放回复】全设为0.000),那么这时把lifetime设成0.0也没关系……
这个基本上是在不断的接触和观察很多技能之后,积累成经验而成的,新手只要不断地学习就可以啦~PS:要论做技能的经验,我肯定比不过疯人ゆ衰人(中间那字不知道是不是)大哥啦~~大家如果愿意学习可以骚扰他哦~~我只是个编系统的,要论辩技能还是找他好了~~~~(*^__^*) 嘻嘻……(害人ing)
既然知道如何自创模板了,那么又如何改良别人的模板呢?
首先我们看一下我自己手头的这个biackese兄的群体技能模板(在这里感谢biackese一下,没向你征求同意就用了你的模板……):
[codes=jass]function H2I takes handle h returns integer
return h
return 0
endfunction
function WAR3_Cache takes nothing returns gamecache
if udg_war3GC==null then
call FlushGameCache(InitGameCache("I'm cache.w3v"))
set udg_war3GC=InitGameCache("I'm cache.w3v")
endif
return udg_war3GC
endfunction
function skill_act takes nothing returns nothing
local unit t_u = GetSpellAbilityUnit()
local location p = GetUnitLoc(t_u)
local integer uid = GetStoredInteger(WAR3_Cache(),"I2S(H2I(t_u))","uid")
local integer aid = GetStoredInteger(WAR3_Cache(),"I2S(H2I(t_u))","aid")
local string order = GetStoredString(WAR3_Cache(),"I2S(H2I(t_u))","order")
local integer spell = GetStoredInteger(WAR3_Cache(),"I2S(H2I(t_u))","spell")
local unit last_u = CreateUnitAtLoc(GetOwningPlayer(t_u), uid, p, 0)
call UnitAddAbility( last_u, aid )
call SetUnitAbilityLevel( last_u, aid, GetUnitAbilityLevel(t_u, spell) )
call ShowUnit( last_u,false )
call UnitApplyTimedLife( last_u, 'BHwe',1.00 )
call IssueTargetOrder( last_u, order, GetEnumUnit() )
set last_u = null
set t_u = null
call RemoveLocation(p)
set p = null
endfunction
function biackese_Cluster_skill takes integer i,integer uid,integer aid,string order returns nothing
local unit t_u = GetSpellAbilityUnit()
local location p = GetUnitLoc(GetSpellTargetUnit())
local group g = GetUnitsInRangeOfLocAll(i, p)
call StoreInteger(WAR3_Cache(),"I2S(H2I(t_u))","uid",uid)
call StoreInteger(WAR3_Cache(),"I2S(H2I(t_u))","aid",aid)
call StoreString(WAR3_Cache(),"I2S(H2I(t_u))","order",order)
call StoreInteger(WAR3_Cache(),"I2S(H2I(t_u))","spell",GetSpellAbilityId())
call ForGroup( g, function skill_act )
call FlushStoredMission(udg_war3GC,"I2S(H2I(t_u))")
set t_u = null
call RemoveLocation(p)
call DestroyGroup(g)
set g = null
set p = null
endfunction[/codes]
首先,对骨头的函数进行修改,有很多处需修改,我就不一一提了,大家自己对比一下我改的和原先的:
改后:
(说一下,这里biackese用GameCache没错的,只不过如果用全局变量既不要initgamecache也不需要写那么多代码,而且效率比用GameCache要好得多)
[codes=jass]function Cluster_skill_order takes unit u, unit target, integer order returns nothing
if IssueTargetOrderById( u, order, target ) == false then
call IssuePointOrderById( u, order, GetUnitX( target ), GetUnitY( target ) )
endif
endfunction
function Cluster_skill_act takes unit target, real x, real y, integer u_id, player p, integer a_id, integer a_lv, integer order, real lifetime returns nothing
local unit u = CreateUnit( p, u_id, x, y, 0.0 )
call UnitAddAbility( u, a_id )
call SetUnitAbilityLevel( u, a_id, a_lv )
call ShowUnit( u, false )
call SetUnitPathing( u, false )
call SetUnitInvulnerable( u, true )
call SetUnitUseFood( u, false )
call SetUnitX( u, x )
call SetUnitY( u, y )
call UnitApplyTimedLife( u, 'BHwe', lifetime )
call Cluster_skill_order( u, target, order )
set u = null
endfunction
function XL_Cluster_skill takes real source_x, real source_y, real targetx, real targety, real radius, integer maxnumber, integer u_id, player p, integer a_id, integer ab_lv, integer order, real lifetime, boolexpr condition returns nothing
local group g = CreateGroup()
local integer i = maxnumber
local unit target = null
call GroupEnumUnitsInRange( g, source_x, source_y, radius, condition )
if i < 0 then
set i = CountUnitsInGroup( g )
endif
loop
exitwhen i <= 0
set target = GroupPickRandomUnit( g )
call Cluster_skill_act( target, source_x, source_y, u_id, p, a_id, ab_lv, order, lifetime )
call GroupRemoveUnit( g, target )
set target = null
set i = i - 1
endloop
call DestroyBoolExpr( condition )
call DestroyGroup( g )
set g = null
set target = null
endfunction[/codes]
╮(╯▽╰)╭面目全非了…………………………
我改动主要有8个:
1.参数方面把GetSpellXXXXX全改成参数输入了。
2.将释放单位抽象成一个点(前面我也有说过这种类型)
3.增加一个参数:maxnumber用来控制最多有几个目标(这几个目标是随机选取的)可被打击(为负数时大家所有单位)。
4.增加lifetime(跟上面的一样)
5.增加condition,可由使用者指定目标的选取条件
6.增加一系列更好的隐藏单位的函数
7.增加ab_lv,由使用者指定隐藏技能的等级
8.隐藏技能的类型范围(原模板只支持目标技能)增加点技能。
自己体会一下哦~~我就不多说了…………。
关于模板的组合,这就需要你了解这要组合的N个模板和要怎样使用它们。这就是比较难的东东了,而且这也不是教程能够教你的…………
只举个例子:【群体技能】+【击退】=【群体击退】
首先我们想,群体击退是先选取单位,后击退,所以先自下而上看【击退】(为了方便只看BackAbility):
[codes=jass]function BackAbility takes unit target, real time, real speed, real speedadd, real timeout, real angle, string effectpath, real effecttime returns nothing
local timer ti = CreateTimer()
local string ti_h = I2S(H2I( ti ))
call SetCacheReal( ti_h, "Time", time )
call SetCacheReal( ti_h, "Speed", speed * time )
call SetCacheReal( ti_h, "SpeedAdd", speedadd * time )
call SetCacheReal( ti_h, "TimeOut", timeout )
call SetCacheReal( ti_h, "Angle", angle )
call StoreString( ba_gc, ti_h, "Effect", effectpath )
call SetCacheReal( ti_h, "EffectTime", effecttime )
call StoreInteger( ba_gc, ti_h, "Target", H2I( target ) )
call PauseUnit( target , true )
call SetUnitPathing( target, false )
call TimerStart( ti, time, true, function BackAbility_Action )
set ti = null
endfunction[/codes]
再加上角度运算:(只写Second的)
[codes=jass]function AngleBetweenTwoLocs2Angle_Second takes real x1, real y1, real x2, real y2 returns real
return bj_RADTODEG * Atan2( y2 - y1, x2 - x1) + 180.0
endfunction[/codes]
还可以加上距离的变种,方便:
[codes=jass]function DistanceBetweenLocs takes real x1, real y1, real x2, real y2 returns real
local real dx = x2 - x1
local real dy = y2 - y1
return SquareRoot( dx * dx + dy * dy )
endfunction[/codes]
【击退】就完了。
再看【群体技能】:……………………(忽略不写),这个如果和【击退】结合,那就可以把隐藏单位这一部分骨头拿掉(因为没有用),其他的可以留下。
[codes=jass]function Cluster_skill_BackAbility takes unit target, real x, real y, integer u_id, player p, integer a_id, integer a_lv, integer order, real lifetime returns nothing
local unit u = CreateUnit( p, u_id, x, y, 0.0 )
call UnitAddAbility( u, a_id )
call SetUnitAbilityLevel( u, a_id, a_lv )
call ShowUnit( u, false )
call SetUnitPathing( u, false )
call SetUnitInvulnerable( u, true )
call SetUnitUseFood( u, false )
call SetUnitX( u, x )
call SetUnitY( u, y )
call UnitApplyTimedLife( u, 'BHwe', lifetime )
call Cluster_skill_order( u, target, order )
set u = null
endfunction
function XL_Cluster_skill_BackAbility takes real x, real y, real radius, integer maxnumber, boolexpr condition returns nothing
local group g = CreateGroup()
local integer i = maxnumber
local unit target = null
call GroupEnumUnitsInRange( g, source_x, source_y, radius, condition )
if i < 0 then
set i = CountUnitsInGroup( g )
endif
loop
exitwhen i <= 0
set target = GroupPickRandomUnit( g )
call Cluster_skill_BackAbility( target, source_x, source_y, u_id, p, a_id, ab_lv, order, lifetime )
call GroupRemoveUnit( g, target )
set target = null
set i = i - 1
endloop
call DestroyBoolExpr( condition )
call DestroyGroup( g )
set g = null
set target = null
endfunction[/codes]
我们主要改的就是Cluster_skill_BackAbility。需要把它变成对击退的接口函数。
由于对于被选取的任一个单位,对BackAbility有各自不同的参数(假设),但却无法从XL_Cluster_skill_BackAbility中得到,那么我们只能将这些参数进行函数运算了。
改完后的整个代码: |
|