本例子是将月之女祭司的大招,与沉沦之渊相结合。技能施放时,敌军将被困在沉下的区域中无法逃逸。如下图:
该技能的创作思路如下:
1,由施法者开始施放技能触发;
2,创建沉沦之渊的效果:在施法者的站立点创建一个“弹坑”,并在四周排布一圈“隐身单位”,在这些隐身单位身上加“瀑布”特效;在施法者四周排布“水滴”特效;中间的水花飞溅效果则用一个隐身单位施放修改过的“死腐术”来营造;
3,使用计时器,每隔0.1秒,将靠近弹坑周边的敌人“拉”回区域中间;
4,监测施法者的状态,一旦其停止或完成施法,或者死亡,立刻停止沉沦之渊的所有效果。
在此思路下,需要解决的难点:
在停止沉沦之渊效果的时候,“瀑布”“水滴”特效如何删除?有的人可能会说,何必使用特效呢,学澄海的作者那样,直接用一个辅助单位,把它的模型改成需要的效果,不就可以了吗?的确,这样就不存在删除特效的问题,但这种每制作一个技能就必须建立一些辅助单位的做法是很麻烦的,因为制作辅助单位的时间非常长(比建立一个辅助技能的时间还长很多),这样一张地图下来,制作时间就拖得很慢了。所以我还是倾向于是用特效。
另一个就是计时器在多人对战中的使用问题。在澄海里这个问题不存在,因为澄海一次游戏里,只有一个人能使用沉沦之渊,但是现在我们要做的是一个在多人游戏中的技能,因此就需要考虑在本地变量中使用计时器。为了提高运作效率,这里选择了使用vJass的 struct,来传递函数之间的数据。(计时器的工作原理,是每隔一定时间,反复执行一个独立的函数,如何把原函数中的数据高效率传递给这个反复执行的独立函数,一直有很多人提出不同的方法,这里选择用 struct来传递。这是 struct的一个最重要功能。)
接下来我们分开不同功能模块来看看代码的书写,和我们是如何解决以上问题的:
第一部分,准备工作:
我们把需要用到的代码,全局变量,struct准备好,以便在程序中使用。
复制内容到剪贴板
代码:
//--------------------------------------------------------------------------
constant function StarfallDeathandDecayA takes nothing returns integer
return 'A02A'
endfunction
constant function DeathanddecayOrder takes nothing returns string
return "deathanddecay"
endfunction
//--------------------------------------------------------------------------
globals
private timer starfalltimer = CreateTimer()
private starfallData array starfall_data
private integer starfall_ttl = 0
endglobals
struct starfallData
player who
unit caster
unit assistsp
real ox
real oy
real rad
integer bounces
boolean isinvul
group waterfall
endstruct第二部分,创建需要使用的自定义函数:
此技能对沉沦之渊作了小小的改动,不是使用圆形,而是正方形来排布水流,因此我们需要一个自定义函数,以方便在触发中调用。
SquareLoc返回的是单位的位置
GetSquareFace返回的是单位的面向角度(所有瀑布必须面向施法者)
复制内容到剪贴板
代码:
function SquareLoc takes real centreX, real centreY, real squareWidth, real polarAngle returns location
local real x
local real y
if ( polarAngle == -45.0 or polarAngle == 45.0 or polarAngle == 135.0 or polarAngle == 225.0 or polarAngle == 315.0 ) then
set x = centreX + squareWidth * 0.5 * SquareRoot( 2.0 ) * Cos( polarAngle * 3.14159 / 180.0 )
set y = centreY + squareWidth * 0.5 * SquareRoot( 2.0 ) * Sin( polarAngle * 3.14159 / 180.0 )
endif
//! textmacro starfallSquareloc takes minAngle, maxAngle, xpolar, ypolar
if ( polarAngle > $minAngle$ and polarAngle < $maxAngle$ ) then
set x = centreX + squareWidth * 0.5 $xpolar$
set y = centreY + squareWidth * 0.5 $ypolar$
endif
//! endtextmacro
//! runtextmacro starfallSquareloc( "-45.0", "45.0", "* Cos( 0 )", "* Tan( polarAngle * 3.14159 / 180.00 )")
//! runtextmacro starfallSquareloc( "45.0", "135.0", "/ Tan( polarAngle * 3.14159 / 180.00 )", "* Sin( 3.14159 / 2 )")
//! runtextmacro starfallSquareloc( "135.0", "225.0", "* Cos( 3.14159 )", "* Tan( polarAngle * 3.14159 / 180.00 )")
//! runtextmacro starfallSquareloc( "225.0", "315.0", "/ Tan( polarAngle * 3.14159 / 180.00 )", "* Sin( 270.0 * 3.14159 / 180 )")
//! runtextmacro starfallSquareloc( "315.0", "360.0", "* Cos( 0 )", "* Tan( polarAngle * 3.14159 / 180.00 )")
return Location( x, y )
endfunction
function GetSquareFace takes real polarAngle returns real
local real face
//! textmacro starfallSquareface takes minAngle, maxAngle, face
if ( polarAngle > $minAngle$ and polarAngle < $maxAngle$ ) then
set face = $face$
endif
//! endtextmacro
//! runtextmacro starfallSquareface( "-45.0", "45.0", "180.0" )
//! runtextmacro starfallSquareface( "45.0", "135.0", "270.0" )
//! runtextmacro starfallSquareface( "135.0", "225.0", "0.0" )
//! runtextmacro starfallSquareface( "225.0", "315.0", "90.0" )
//! runtextmacro starfallSquareface( "315.0", "360.0", "180.0" )
if ( polarAngle == -45.00 or polarAngle == 315.0 ) then
set face = 135.0
endif
//! textmacro starfallSquarefaceII takes angle, face
if ( polarAngle == $angle$ ) then
set face = $face$
endif
//! endtextmacro
//! runtextmacro starfallSquarefaceII( "135.0", "315.0" )
//! runtextmacro starfallSquarefaceII( "225.0", "45.0" )
//! runtextmacro starfallSquarefaceII( "45.0", "225.0" )
return face
endfunction准备工作已经完成,接下来是触发的主部分,其实一个只有两个模块,一个是触发的动作,一个是计时器的运行函数:
计时器每隔0.1秒便自动运行一次的函数:
//触发器的条件
function Trig_Starfall_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'AEsf' ) ) then
return false
endif
return true
endfunction
//计时器函数“抓取”敌人的条件,为了方便我使用了文本宏,宏的使用可参考我另一篇文章
function Trig_Starfall_Func001002003 takes nothing returns boolean
//! textmacro starfallconditioncon takes conditon
if ( $conditon$ ) then
return false
endif
//! endtextmacro
//! runtextmacro starfallconditioncon( "IsUnitType( GetFilterUnit(), UNIT_TYPE_STRUCTURE )" )
//! runtextmacro starfallconditioncon( "IsUnitType( GetFilterUnit(), UNIT_TYPE_FLYING )" )
//! runtextmacro starfallconditioncon( "not ( GetUnitState( GetFilterUnit(), UNIT_STATE_LIFE ) > 0.00 )" )
//! runtextmacro customedgroupcon( "GetUnitAbilityLevel( GetFilterUnit(), 'B008' ) > 0 " )
return true
endfunction
//计时器的执行函数:
function Trig_Starfall_Effect takes nothing returns nothing
local unit tg
local unit u
local starfallData star
local real dx
local real dy
local real dis
local group g = CreateGroup()
local boolexpr fil = Condition( function Trig_Starfall_Func001002003 )
local integer i = 0
local integer f = 0
//! runtextmacro generaldebug()
loop
exitwhen i >= starfall_ttl
set star = starfall_data
//-- Main loop -------------------------------------------
//--判断施法者的状态,如果施法者死亡,或停止/完成施法,则执行以下蓝色部分的动作。
if ( GetUnitState( star.caster, UNIT_STATE_LIFE ) == 0.0 or not( GetStoredBoolean( LocalVars(), I2S(H2I( star.caster )), "starfall" ))) then
call RemoveUnit( star.assistsp )
//--通过游戏缓存,把储存的特效删除,这是本例子要说的一个关键技巧:
call GroupRemoveUnitEffect( star.waterfall, "bubble" )
call GroupRemoveUnitEffect( star.waterfall, "waterfall" )
call GroupRemoveUnitEffect( star.waterfall, null )
call SetUnitPathing( star.caster, true )
call TerrainDeformCrater( star.ox, star.oy, ( star.rad - 50.00 ), -512.00, 5000, true )
call star.destroy()
//--以下绿色部分比较复杂,将跟贴另外解释:
set starfall_data = starfall_data[starfall_ttl - 1]
set starfall_ttl = starfall_ttl - 1
set i = i - 1
//--如果施法者仍然在施法,就需要执行下面的动作:
else
//--施法者有一小段无敌时间,此段用于判断无敌时间是否过了,如果是,就把施法者设成非无敌。
if ( star.bounces <= 0 and star.isinvul == true ) then
call SetUnitInvulnerable( star.caster, false )
set star.isinvul = false
endif
//--将靠近弹坑周边的敌人“拉”回区域中间
call GroupEnumUnitsInRange( g, star.ox, star.oy, star.rad + 200.0, fil )
call DestroyBoolExpr( fil )
loop
set tg = FirstOfGroup( g )
exitwhen tg == null
set dx = star.ox - GetUnitX( tg )
set dy = star.oy - GetUnitY( tg )
set dis = dx * dx + dy * dy
if ( dis > star.rad * star.rad and IsUnitEnemy( tg, star.who )) then
call SetUnitPosition( tg, star.ox, star.oy )
endif
call GroupRemoveUnit( g, tg )
endloop
call DestroyGroup( g )
set g = null
//-- star.bounces用于计算施法者的无敌时间
set star.bounces = star.bounces - 1
set starfall_data = star
endif
//-- Main loop -------------------------------------------
set i = i + 1
endloop
//-- 此部分另外解释
if ( starfall_ttl == 0 ) then
call PauseTimer( starfalltimer )
debug set message = "Starfall timer is " + SetTextColor( "pause", "Blue" ) + "."
debug call BJDebugMsg( message )
endif
endfunction
最后是触发的动作函数,相对比较简单,与普通触发的动作类似:
function Trig_Starfall_Actions takes nothing returns nothing
local unit wat
local real face
local real angle = 15.00
local location loc
local string water = "Doodads\\Terrain\\CliffDoodad\\Waterfall\\Waterfall.mdl"
local string bubble = "Doodads\\Ruins\\Water\\BubbleGeyser\\BubbleGeyser.mdl"
local code catch = function Trig_Starfall_Effect
local integer f = 0
local starfallData star = starfallData.create()
//! runtextmacro generaldebug()
call IssueImmediateOrder( GetTriggerUnit(), "starfall" )
//--存储施法者的状态,告诉程序其正在施法
call StoreBoolean( LocalVars(), I2S(H2I( GetTriggerUnit())), "starfall", true )
call UnitAddAbility( GetTriggerUnit(), 'ANcl' )
call SetUnitPathing( GetTriggerUnit(), false )
call SetUnitInvulnerable( GetTriggerUnit(), true )
set star.caster = GetTriggerUnit()
set star.who = GetOwningPlayer( star.caster )
set star.ox = GetUnitX( star.caster )
set star.oy = GetUnitY( star.caster )
set star.rad = 550.00
set star.assistsp = CreateUnit( star.who, DummyU(), star.ox, star.oy, 270.00 )
set star.bounces = R2I( 10.0 / 0.1 )
set star.waterfall = CreateGroup()
set star.isinvul = true
call TerrainDeformCrater( star.ox, star.oy, ( star.rad - 50.00 ), 512.00, 5000, true )
call UnitAddAbility( star.assistsp, StarfallDeathandDecayA())
call IssuePointOrder( star.assistsp, DeathanddecayOrder(), star.ox, star.oy )
//--创建瀑布和水滴:
loop
exitwhen f > ( R2I( 360.0 / angle ) - 1 )
set loc = SquareLoc( star.ox, star.oy, star.rad * 2.00, I2R( f ) * angle )
set face = GetSquareFace( I2R(f) * angle )
set wat = CreateUnitAtLoc( star.who, DummyU(), loc, face )
call RemoveLocation( loc )
//--红色部分,是把特效存储在缓存里,以便将来删除
call SetHandleHandle( wat, "waterfall", AddSpecialEffectTarget( water, wat, "origin" ))
call SetUnitFlyHeight( wat, 0.00, 0.00 )
set loc = SquareLoc( star.ox, star.oy, star.rad * 2.00 - 100.0, I2R( f ) * angle )
call SetHandleHandle( wat, "bubble", AddSpecialEffectLoc( bubble, loc ))
call RemoveLocation( loc )
call GroupAddUnit( star.waterfall, wat )
set f = f + 1
endloop
set wat = null
//-- struct用在计时器中的基本运作方法。
if ( starfall_ttl == 0 ) then
call TimerStart( starfalltimer, 0.10, true, catch )
debug set message = "Starfall timer is " + SetTextColor( "stared", "Blue" ) + "."
debug call BJDebugMsg( message )
endif
set starfall_ttl = starfall_ttl + 1
set starfall_data[starfall_ttl - 1] = star
endfunction
从以上代码看本触发的特点:
1,在计时器的运行中,全部使用struct传递数据,这样保证了计时器的运行效率;
2,通过游戏缓存把使用的特效保存起来,并在不用的时候删除,节省了系统资源。游戏缓存只在技能的运行开始/结束部分进行读写,而不是每运行一次计时器就频繁读写,不会拖累系统资源。
[未完待续]
[
本帖最后由 宇宙黑洞 于 2007-09-24 11:48 编辑 ]