推荐地图:火影无级别混战推荐地图:楚汉争霸推荐地图:魔神推荐地图:人族无敌II
推荐专区:技能创作区推荐专区:WE讨论区推荐专区:模型作区推荐专区:申请公告区
发新话题
打印

[中级教程]月女Starfall + 澄海炼金沉沦之渊(巧用vJass struct + GameCache)

[中级教程]月女Starfall + 澄海炼金沉沦之渊(巧用vJass struct + GameCache)

本例子是将月之女祭司的大招,与沉沦之渊相结合。技能施放时,敌军将被困在沉下的区域中无法逃逸。如下图:


该技能的创作思路如下:

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 编辑 ]
第二贴:
这里解释一下struct用在计时器中的原理,说实话比较复杂,如果看不懂就多看几次,或回帖询问。

先是下面的内容:
starfall_ttl是一个整数,用于计算游戏中一共有多少个英雄在施放这个技能,如果它==0,就说明当前的施法者是第一个开始施放技能的,因此要启动计时器 starfalltimer。

而且要把所有需要在计时器函数中使用的内容,存进 starfall_data[starfall_ttl-1]里。这个“存”的过程,和缓存是一样的。
复制内容到剪贴板
代码:
  if ( starfall_ttl == 0 ) then
   call TimerStart( starfalltimer, 0.10, true, catch )
  endif
  set starfall_ttl = starfall_ttl + 1
  set starfall_data[starfall_ttl - 1] = star
有存,就有读,下面的语句就用于把内容读出来:
struct的“读”并不是和缓存一样,是个简单的“取出”过程,它其实是用一个循环,把所有的数据都处理一遍,也就是说,如果有10个玩家,控制10个月亮祭司一起施放此技能,电脑就会轮流着把10个女祭司的技能处理一次,而电脑并不关心顺序,它只是把需要的数据进行处理,不需要的数据则从内存剔除,直到所有10个玩家的月女完成施法,电脑就把计时器暂时停下。
复制内容到剪贴板
代码:
                local starfallData star
                local integer i = 0
                loop
                        exitwhen i >= starfall_ttl
                        set star = starfall_data[i]
                        set i = i + 1
                endloop
                if ( starfall_ttl == 0 ) then
                        call PauseTimer( starfalltimer )
                endif
这就是我们要在struct使用的同时,使用缓存的原因。struct的强项是快速,而不是读取指定内容。缓存的强项是存储,它能准确地存起某个对象,然后准确地读出来,但是它运行得比较慢。

最后还有一些辅助自定义函数要贴出来,免得上面的代码看起来让人莫名其妙。这些函数应该不是很难理解,就不一一叙述了:
复制内容到剪贴板
代码:
        function H2I takes handle h returns integer
                return h
                return 0
    endfunction

        function LocalVars takes nothing returns gamecache
                if ( bj_lastCreatedGameCache == null ) then
                        set bj_lastCreatedGameCache = InitGameCache("jasslocalvars.w3v")
                endif
                return bj_lastCreatedGameCache
        endfunction

        function SetHandleHandle takes handle h, string name, handle value returns nothing
                if value == null then
                        call FlushStoredInteger(LocalVars(),I2S(H2I(h)),name )
                else
                        call StoreInteger(LocalVars(), I2S(H2I(h)), name, H2I(value))
                endif
        endfunction

        function GetHandleUnit takes handle h, string name returns unit
                return GetStoredInteger( LocalVars(), I2S( H2I( h ) ), name )
                return null
        endfunction

        function GroupRemoveUnitEffect takes group whichGroup, string handleName returns nothing
                local group g = CreateGroup()
                local unit u
                call GroupAddGroup( whichGroup, g )
                loop
                        set u = FirstOfGroup( g )
                        exitwhen u == null
                        if ( handleName == null ) then
                                call RemoveUnit( u )
                        else
                                call DestroyEffect( GetHandleEffect( u, handleName ))
                                call FlushStoredInteger( LocalVars(),I2S(H2I( u )), handleName )
                        endif
                        call GroupRemoveUnit( g, u )
                endloop
                call DestroyGroup( g )
                set g = null
        endfunction
!!!!!!!!!!!!!
!!!!!!!!!!!!
本帖最近评分记录
  • feelerly 力量 -1 _________ 2007-10-8 22:43
有没有图下载看一下效果啊?

TOP

做的不错,不过方的看起来怪怪的

TOP

晕死...本来就对JASS一窍不通  好长啊~~
眼泪的存在,是为了证明悲伤不是一场幻觉

TOP

这样的才中级教程    看来我完蛋啦    菜鸟级别

TOP

看不东哎~~~~~~~~

TOP

VJASS...原理算是懂了..我改天看一下吧,Vjass语法方面还不太熟..

:L 涉嫌挖坟...没看日期..

[ 本帖最后由 spark6559498 于 2008-8-4 11:37 编辑 ]

TOP

我是在深渊里吗?好自卑
                 我的空间
    http://new.qzone.qq.com/740102298/infocenter

TOP

评价如下:
使用VJASS语法。大多数人都不怎么熟。事实上,VJASS通过编译后,还是会转成JASS代码,所以,过多的变量反而会让VJASS创建N多全局变量,十分浪费资源。
一般建议还是通过缓存来做这类技能,其思路将会比较直白地表现出来。

TOP

汗,,    我觉得,    个人认为啦
   个人认为如果你用1.22中文版,, 会简单的多

TOP

发新话题