|
原本打算循序渐进地发一些教程,但是最近家里有事估计不能完成这一计划,因此直接用一个实际例子把做复杂技能的思路说一下。请注意本文属于高级vJass教程,阅读者必须具备vJass的语法知识,我不会对程序进行逐行注解(那样估计得上万字的文章),如果你在阅读过程中遇到语法问题请自行在本区查阅vJass使用指南,不便之处还望见谅。
技能是实际地图中提取出来的,因此运行完美没有泄漏,为了更容易理解本文的内容,我建议您先观看技能录像或下载技能地图观看一遍效果。在正式开始之前我们需要些全局性的辅助函数,由于每个技能几乎都要用到这些辅助函数,他们会被放在触发器的预载入部分:
[codes=jass]
//======================================================================
constant function DummyU takes nothing returns integer
return 'uloc'
endfunction
//! textmacro abilitiesglobals takes structName
globals
private timer $structName$timer = CreateTimer()
private integer $structName$_ttl = 0
private $structName$Data array $structName$_dataendglobals
//! endtextmacro
//! textmacro playerunitevent takes trg, eventId local integer i = 0 set $trg$ = CreateTrigger() loop
exitwhen i > 11 if GetPlayerSlotState( Player(i)) == PLAYER_SLOT_STATE_PLAYING then call
TriggerRegisterPlayerUnitEvent( $trg$, Player(i), $eventId$, null ) endif set i = i + 1 endloop //!
endtextmacro
//======================================================================
[/codes]
另外,我们还需要一些技能本身需要的特殊辅助函数,我们同样把他们列在下面,由于必须使用全局变量,我们需要用到在触发中用到 scope:
[codes=jass]
//======================================================================
scope dragonfeastsTrg
//! runtextmacro abilitiesglobals( "dragonfeasts" )
constant function DragonfeastsCarrionswarmPriA takes nothing returns integer
return 'A027'
endfunction
constant function DragonfeastsCarrionswarmSecA takes nothing returns integer
return 'A028'
endfunction
constant function DragonfeastsShockwaveA takes nothing returns integer
return 'A029'
endfunction
constant function DragonfeastsDragonDis takes nothing returns real
return 50.0
endfunction
constant function DragonfeastsStep takes nothing returns real
return 80.0
endfunction
//======================================================================
[/codes]
以上函数从上到下依次是:
主要辅助技能一的原始代码;
主要辅助技能二的原始代码;
主要辅助技能三的原始代码;
技能中左右两条青龙飞行中离中线(英雄正面视野直线)的距离(数值增大,两条龙会分隔较远);
青龙飞行后气功波的发射间隔距离(间隔越小,气功波越密集,但总体长度会变小)。
//======================================================================
接下来是程序的核心部分,也是最复杂的部分,幸好vJass提供了一个非常好的特性: struct,这样我们可以暂时抛开如何解决多人同时施法这
个复杂的难题,而把精力集中在效果呈现的部分:
[codes=jass]
//======================================================================
struct dragonfeastsData
unit caster
real ox
real oy
real face
unit spI
unit spII
integer bounces
integer level
//首先,在郭靖施放技能的开始瞬间,我们需要把一些必要的参数保存起来,包括“是谁在施放技能”,
//“他朝哪个方向放技能”,“技能施放的等级”。这个保存的过程我们用个method来处理:
method initiate takes unit trgUnit, real tx, real ty, integer level returns nothing
set .caster = trgUnit
set .face = GetUnitFacing( trgUnit )
set .ox = GetUnitX( trgUnit )
set .oy = GetUnitY( trgUnit )
set .bounces = 15 + 20
set .level = level
//call UnitAddAbility( .caster, MagicImmuneA())
//call CustomUnitInvul( .caster, true )
//call PlayAbilityAckSound( .caster )
endmethod
//现在开始程序的主部分,我们的思路是,用两个辅助单位 spI, spII,在适当的时候让其施放辅助
//技能,通过计时器的控制,使整个过程看起来形成我们所要的效果,具体步骤如下:
method mainFire takes nothing returns nothing
local real dx
local real dy
local player p
local sound s = null
if .bounces == 20 then
//===========================================================================
// .bounces是一个整数变量,它告诉触发器哪个时候做哪些事情,当.bounces = 20的时候,释
// 放龙头,并制造龙吟的效果。
//===========================================================================
set s = CreateSound( "Units\\Creeps\\AzureDragon\\DragonWhat1.wav", false, true, true, 10, 10, "DefaultEAXON" )
call SetSoundVolume( s, 127 )
call AttachSoundToUnit( s, .caster )
call StartSound( s )
call KillSoundWhenDone( s )
set s = null
call SetUnitInvulnerable( .caster, false )
set p = GetOwningPlayer( .caster )
set dx = .ox + DragonfeastsDragonDis()*Cos(( .face-90.0 ) * bj_DEGTORAD )
set dy = .oy + DragonfeastsDragonDis()*Sin(( .face-90.0 ) * bj_DEGTORAD )
set .spI = CreateUnit( p, DummyU(), dx, dy, 0.0 )
set dx = .ox + DragonfeastsDragonDis()*Cos(( .face+90.0 ) * bj_DEGTORAD )
set dy = .oy + DragonfeastsDragonDis()*Sin(( .face+90.0 ) * bj_DEGTORAD )
set .spII = CreateUnit( p, DummyU(), dx, dy, 0.0 )
call UnitRemoveAbility( .spI, 'Amov' )
call UnitRemoveAbility( .spII, 'Amov' )
call UnitAddAbility( .spI, DragonfeastsCarrionswarmPriA())
call UnitAddAbility( .spI, DragonfeastsShockwaveA())
call UnitAddAbility( .spII, DragonfeastsCarrionswarmPriA())
call UnitAddAbility( .spII, DragonfeastsShockwaveA())
call SetUnitAbilityLevel( .spI, DragonfeastsCarrionswarmPriA(), .level )
call SetUnitAbilityLevel( .spI, DragonfeastsShockwaveA(), .level )
call SetUnitAbilityLevel( .spII, DragonfeastsCarrionswarmPriA(), .level )
call SetUnitAbilityLevel( .spII, DragonfeastsShockwaveA(), .level )
//===========================================================================
// 以上一大段是创建辅助单位,并加上龙头技能,技能基于“冲击波”,使用“占据”
// 作为飞行道具。
//===========================================================================
call SetUnitScale( .spI, 2.0, 2.0, 2.0 )
call SetUnitScale( .spII, 2.0, 2.0, 2.0 )
//===========================================================================
// 我们要让龙头看起来稍微大一点设置
//===========================================================================
set dx = GetUnitX( .spI ) + 500.0 * Cos( .face*bj_DEGTORAD )
set dy = GetUnitY( .spI ) + 500.0 * Sin( .face*bj_DEGTORAD )
call IssuePointOrder( .spI, "carrionswarm", dx, dy )
set dx = GetUnitX( .spII ) + 500.0 * Cos( .face*bj_DEGTORAD )
set dy = GetUnitY( .spII ) + 500.0 * Sin( .face*bj_DEGTORAD )
call IssuePointOrder( .spII, "carrionswarm", dx, dy )
//===========================================================================
// 命令辅助单位施放“龙头”技能。
//===========================================================================
call UnitRemoveAbility( .spI, DragonfeastsCarrionswarmPriA())
call UnitRemoveAbility( .spII, DragonfeastsCarrionswarmPriA())
set p = null
//===========================================================================
// 因为龙头只有一个,因此在施放辅助技能后,我们要把它移除。
//===========================================================================
elseif .bounces < 20 and .bounces > 10 then
//===========================================================================
// 施放龙头后,下一步就要施放龙身,由于龙身是长长的一条,而不像龙头那样只有
// 一个,因此我们需要在一定时间内反复施放,我们就把这个时间段规定从 .bounces = 19
// 到 .bounces = 11 这个区间吧:
//===========================================================================
if .bounces == 19 then
call UnitAddAbility( .spI, DragonfeastsCarrionswarmSecA())
call UnitAddAbility( .spII, DragonfeastsCarrionswarmSecA())
call SetUnitAbilityLevel( .spI, DragonfeastsCarrionswarmSecA(), .level )
call SetUnitAbilityLevel( .spII, DragonfeastsCarrionswarmSecA(), .level )
call SetUnitScale( .spI, 1.5, 1.5, 1.5 )
call SetUnitScale( .spII, 1.5, 1.5, 1.5 )
//===========================================================================
// 这是龙身技能,同样基于“冲击波”,只是飞行道具有所不同,我使用的是鹿人的攻击道具
// 由于是龙身,看起来应该比龙头稍微小点,我们把scale设置成 1.5
// 由于青龙飞翔后,我们还要继续制造震波的效果,我们把震波技能也顺带加上
//===========================================================================
endif
call UnitResetCooldown( .spI )
call UnitResetCooldown( .spII )
set dx = GetUnitX( .spI ) + 300.0 * Cos( .face*bj_DEGTORAD )
set dy = GetUnitY( .spI ) + 300.0 * Sin( .face*bj_DEGTORAD )
call IssuePointOrder( .spI, "carrionswarm", dx, dy )
set dx = GetUnitX( .spII ) + 300.0 * Cos( .face*bj_DEGTORAD )
set dy = GetUnitY( .spII ) + 300.0 * Sin( .face*bj_DEGTORAD )
call IssuePointOrder( .spII, "carrionswarm", dx, dy )
call UnitResetCooldown( .spI )
call UnitResetCooldown( .spII )
//===========================================================================
// 以上这段很简单,就是反复让 .spI和.spII使用“龙身”的辅助技能。
//===========================================================================
elseif .bounces <= 10 and .bounces > 0 then
set dx = .ox + DragonfeastsStep()*I2R(10-.bounces)*Cos( .face*bj_DEGTORAD )
set dy = .oy + DragonfeastsStep()*I2R(10-.bounces)*Sin( .face*bj_DEGTORAD )
call SetUnitPosition( .spI, dx, dy )
call SetUnitPosition( .spII, dx, dy )
call SetUnitScale( .spI, I2R(11-.bounces)*0.1, I2R(11-.bounces)*0.1, I2R(11-.bounces)*0.1 )
call SetUnitScale( .spII, I2R(11-.bounces)*0.1, I2R(11-.bounces)*0.1, I2R(11-.bounces)*0.1 )
call UnitResetCooldown( .spI )
set dx = GetUnitX( .spI ) + 300.0*Cos(( .face - 60.0 )*bj_DEGTORAD )
set dy = GetUnitY( .spI ) + 300.0*Sin(( .face - 60.0 )*bj_DEGTORAD )
call IssuePointOrder( .spI, "shockwave", dx, dy )
call UnitResetCooldown( .spI )
call UnitResetCooldown( .spII )
set dx = GetUnitX( .spII ) + 300.0*Cos(( .face + 60.0 )*bj_DEGTORAD )
set dy = GetUnitY( .spII ) + 300.0*Sin(( .face + 60.0 )*bj_DEGTORAD )
call IssuePointOrder( .spII, "shockwave", dx, dy )
call UnitResetCooldown( .spII )
//===========================================================================
// 最后阶段,给技能加上震波。要注意的是,震波的排布和龙不一样,并不是直线地
// 往前飞,而是有点斜度地向两边分开。另外我们希望震波看起来是龙飞过留下的,而
// 不是郭靖发出的,因此我们要改变 .spI和 .spII的位置。
//===========================================================================
endif
endmethod
endstruct
// struct做完了,接下来我们要把struct应用到我们的具体技能中,并且要解决一个关键问题:支持多人同时施放
// 为了便于理解,我建议各位可以先看 function Trig_Dragon_Feasts_Actions 这个函数。由于在Jass里,只有
// 后面的函数能调用前面的函数,因此在触发的运作过程中,其被执行的顺序是相反的,从后面的函数看起,会更
// 容易理解。
function Trig_Dragon_Feasts_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A026'
endfunction
// 请先阅读 function Trig_Dragon_Feasts_Actions 的说明。
function Trig_Dragon_Feasts_Effects takes nothing returns nothing
local dragonfeastsData dragon
local integer i = 0
// 我们先说说这个 loop干了什么,它的作用就是把所有的 dragonfeastsData 轮询一遍,并按照我们预先
// 设定好的程序来做事(上面已经说了,按照.bounces的值来判断,哪一步该干什么。),轮询的频率是0.1秒
loop
exitwhen i == dragonfeasts_ttl
set dragon = dragonfeasts_data
if GetUnitState( dragon.caster, UNIT_STATE_LIFE ) == 0.0 or dragon.bounces == 0 then
//===========================================================================
// 首先我们要看郭靖是不是挂了,或者技能是不是施放完了。如果是,我们要把辅助单位移除,并
// 清空当前被轮询的这个 struct,以便留给别的技能使用(或曰排泄)。
//===========================================================================
//call UnitRemoveAbility( dragon.caster, MagicImmuneA())
call RemoveUnit( dragon.spI )
call RemoveUnit( dragon.spII )
call dragon.destroy()
set dragonfeasts_data = dragonfeasts_data[dragonfeasts_ttl-1]
set dragonfeasts_ttl = dragonfeasts_ttl - 1
set i = i - 1
//===========================================================================
// 清空这个实例后,我们还要把它剔除出数列,并刷新 dragonfeasts_ttl,纪录施放技能的人数
//===========================================================================
else
if dragon.bounces == 25 then
call SetUnitAnimation( dragon.caster, "spell" )
endif
//===========================================================================
// 刚才可能已经有人提出疑问,.bounces = 15+20,我们是从20开始放置龙头,那之前的1.5秒
// 在干什么?答案就在这里。郭靖施放技能的前1.5秒是在“摆造型”。。。。。。
//===========================================================================
if dragon.bounces <= 20 then
call dragon.mainFire()
endif
//===========================================================================
// 当.bounces <=20的时候,就照着我们的设定执行程序。
//===========================================================================
set dragon.bounces = dragon.bounces - 1
endif
set i = i + 1
endloop
if dragonfeasts_ttl == 0 then
call PauseTimer( dragonfeaststimer )
endif
//===========================================================================
// 最后,我们还要判断是不是地图中已经没有郭靖在施放这个技能了,如果是,我们要
// 停下计时器。注意,我们只是停下它,而不是 Destroy,这样我们从游戏开始到结束,
// 都是用同一个计时器去控制亢龙有悔,不需要频繁 create和 destroy,既环保又提高
// 游戏速度。
//===========================================================================
endfunction
function Trig_Dragon_Feasts_Actions takes nothing returns nothing
local dragonfeastsData dragon = dragonfeastsData.create()
local real tx = GetLocationX( GetSpellTargetLoc())
local real ty = GetLocationY( GetSpellTargetLoc())
local integer level = GetUnitAbilityLevel( GetTriggerUnit(), GetSpellAbilityId())
//call UnitAbilityTT( GetTriggerUnit(), GetSpellAbilityId())
call dragon.initiate( GetTriggerUnit(), tx, ty, level )
//===========================================================================
// 这是我们刚才预先编写好的 method initiate,它的作用是把要用到的参数保存起来
//===========================================================================
//call SetUnitState( GetTriggerUnit(), UNIT_STATE_MANA, 0.0 )
//call ManaStateCheck( GetTriggerUnit())
if dragonfeasts_ttl == 0 then
call TimerStart( dragonfeaststimer, 0.1, true, function Trig_Dragon_Feasts_Effects )
endif
//===========================================================================
// 以上三句相当重要,我们可以看到,只有当 dragonfeasts_ttl == 0 时,计时器才
// 会启动,也就是说,无论地图上有多少个郭靖,只有当第一个郭靖施放技能时,我们
// 才会启动计时器,后续再有郭靖使用同样的技能时,我们只是让它“排队”执行。这
// 样的方法很节省系统资源,因为它只用一个计时器去处理其他所有类似的行为。
//===========================================================================
set dragonfeasts_ttl = dragonfeasts_ttl + 1
set dragonfeasts_data[dragonfeasts_ttl-1] = dragon
//===========================================================================
// 如果 dragonfeasts_ttl != 0,说明地图中已经有一个郭靖在使用该技能,这样我们
// 只是简单地让它“排队”,排队的方法是,把整个struct加入到 dragonfeasts_data
// 这个数列里,然后把 dragonfeasts_ttl + 1,这样 dragonfeasts_ttl 就纪录了当前
// 有多少个郭靖在施放技能。
//===========================================================================
endfunction
endscope
//===========================================================================
function InitTrig_Dragon_Feasts takes nothing returns nothing
//! runtextmacro playerunitevent( "gg_trg_Dragon_Feasts", "EVENT_PLAYER_UNIT_SPELL_EFFECT" )
call TriggerAddCondition( gg_trg_Dragon_Feasts, Condition( function Trig_Dragon_Feasts_Conditions ) )
call TriggerAddAction( gg_trg_Dragon_Feasts, function Trig_Dragon_Feasts_Actions )
endfunction
[/codes]
|
|