回傳錯誤的應用

此篇利用回傳錯誤(return bug)的特性來製作下面的技能,若讀者對回傳錯誤的原理不熟悉,可先閱讀物件身分證中的回傳錯誤說明。

冰球 - 具飛射效果的寒冰爆

感謝 hobill 提供此範例的構想。

讀者可下載範例地圖的技能製作範例以觀看本應用的原始檔。

//udg_LocalVars   地圖初始化時用GUI或JASS做初始化:set udg_LocalVars = InitGameCache("MyCache.che")

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 LocalVars takes nothing returns gamecache
    return udg_LocalVars //這裡使用你定義的全域變數
endfunction

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

function FlushHandleLocals takes handle subject returns nothing
    call FlushStoredMission(LocalVars(), H2S(subject) )
endfunction

function GetHandleInt takes handle subject, string name returns integer
    return GetStoredInteger(LocalVars(), H2S(subject), name)
endfunction

function GetHandleTrigger takes handle subject, string name returns trigger
    return GetStoredInteger(LocalVars(), H2S(subject), name)
    return null
endfunction

function GetHandleTriggerAction takes handle subject, string name returns triggeraction
    return GetStoredInteger(LocalVars(), H2S(subject), name)
    return null
endfunction
//=========================================================================== 
// 
//    常數
// 
//    在物件編輯器內按Ctrl+D可以查看各物件的ID,再按一次還原。
//    技能的命令字串可在物件編輯器中查詢。
// 
//    這些務必調整,使其與物件設定相符:
//    IceBall_SpellId     基底技能的ID  。本範例為一個瞬發無效果的技能
//    IceBall_SubSpellId  子技能的ID   。本範例為寒冰爆(Frost Nova)
//    IceBall_Order       子技能的命令字串。本範例為寒冰爆使用的命令字串
//    IceBall_CasterId    隱藏施法者的ID 。本範例為無攻擊力(-1+1d1),
//    具有投射攻擊的隱藏部隊。擁有技能:蝗蟲群、100%必中暴擊
// 
//    這個視需要調整:
//    IceBall_Lifespan    偵測傷害觸發的生命時間,通常不需要動到它
// 
//===========================================================================

constant function IceBall_SpellId takes nothing returns integer
    return 'A014'
endfunction

constant function IceBall_SubSpellId takes nothing returns integer
    return 'A013'
endfunction

constant function IceBall_SubSpellOrder takes nothing returns string
    return "frostnova"
endfunction

constant function IceBall_HiddenCasterId takes nothing returns integer
    return 'o001'
endfunction

constant function IceBall_Lifespan takes nothing returns real
    return 15.
endfunction


//===========================================================================
//    主要觸發 
//===========================================================================

function IceBall_Hit takes nothing returns nothing
    local trigger T1 = GetTriggeringTrigger()
    local unit ut = GetTriggerUnit()
    local unit hc = GetEventDamageSource()
    local fogmodifier view
    //檢查DamageSource是否為該隱藏施法者
    //(使用儲存於隱藏施法者內的T1和本觸發進行比對;如果DamageSource非此隱藏施法者,
    //GetHandleTrigger傳回的值必不等於此觸發)
    if GetHandleTrigger( hc, "T1" ) == T1 then
        call DisableTrigger(T1)
        //為了防止萬一目標在被擊中時不可視,所以我們必須人工創造暫時的視野
        set view = CreateFogModifierRadius(GetOwningPlayer(hc),FOG_OF_WAR_VISIBLE, 下行接續
        GetUnitX(ut),GetUnitY(ut),150.0,false,true)
        call FogModifierStart(view)
        call IssueTargetOrder( hc, IceBall_SubSpellOrder(), ut )
        call DestroyFogModifier(view)
    endif
    set T1 = null
    set hc = null
    set ut = null
    set view = null
endfunction

function IceBall_End takes nothing returns nothing
    local unit    hc = GetTriggerUnit()
    local trigger T1 = GetHandleTrigger( hc, "T1" )
    local trigger T2 = GetTriggeringTrigger()

    //刪除觸發動作
    call TriggerRemoveAction( T1, GetHandleTriggerAction( hc, "A1" ) )
    call TriggerRemoveAction( T2, GetHandleTriggerAction( hc, "A2" ) )

    //刪除觸發器
    call DestroyTrigger( T1 )
    call DestroyTrigger( T2 )

    //清空連結資料
    call FlushHandleLocals( hc )

    //清空變數
    set hc = null
    set T1 = null
    set T2 = null
endfunction

function Trig_IceBall_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == IceBall_SpellId()
endfunction

function Trig_Ice_Ball_Actions takes nothing returns nothing
    //簡單的縮寫: uc = unit caster; ut = unit target; hc = hidden caster
    local unit uc = GetTriggerUnit()
    local unit ut = GetSpellTargetUnit()
    local unit hc = CreateUnit( GetOwningPlayer(uc), IceBall_HiddenCasterId(), 下行接續
         GetUnitX(uc), GetUnitY(uc), 0. )
    local trigger T1 = CreateTrigger()
    local trigger T2 = CreateTrigger()

    //登錄新觸發的事件 
    call TriggerRegisterUnitEvent( T1, ut, EVENT_UNIT_DAMAGED )
    call TriggerRegisterUnitEvent( T2, hc, EVENT_UNIT_DEATH )

    //連結資料 
    call SetHandleHandle( hc, "T1", T1 )
    call SetHandleHandle( hc, "A1", TriggerAddAction( T1, function IceBall_Hit ) )
    call SetHandleHandle( hc, "A2", TriggerAddAction( T2, function IceBall_End ) )

    //隱藏施法者相關 
    call ShowUnit( hc, false )
    call UnitApplyTimedLife( hc, 'BTLF', IceBall_Lifespan() )
    call UnitAddAbility( hc, IceBall_SubSpellId() )
    call SetUnitAbilityLevel( hc, IceBall_SubSpellId(), GetUnitAbilityLevel( uc, IceBall_SpellId() ) )
    call SetUnitFacingToFaceUnitTimed( hc, ut, 0 )
    call IssueTargetOrder( hc, "attackonce", ut )

    //清空區域變數
    set uc = null
    set ut = null
    set hc = null
    set T1 = null 
    set T2 = null 
endfunction


//===========================================================================
//    初始化
//===========================================================================

function InitTrig_Ice_Ball takes nothing returns nothing 
    local trigger t = CreateTrigger() 
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) 
    call TriggerAddCondition(t, Condition(function Trig_IceBall_Conditions)) 
    call TriggerAddAction(t, function Trig_Ice_Ball_Actions) 
    set t = null
endfunction

範例解說

  1. 這是一個典型的回傳錯誤應用實例。
    在技能施展後,我們建立一個隱藏部隊對目標發動攻擊。

    同時創造一個觸發,在目標被隱藏部隊擊中時,命令隱藏部隊對其施展寒冰爆。 這個觸發可以GetTriggeringTrigger()取得自身,由GetEventDamageSource()取得發動攻擊的部隊,我們透過反查的比對技巧來確認是否為隱藏部隊擊中。 而GetTriggerUnit()自然就是目標,因為這個觸發唯一的事件就是目標受到傷害。

    關於收尾方面,我們給隱藏部隊設定一個生命時間,並且建立一個觸發,使它在隱藏部隊死亡時啟動。 那麼GetTriggerUnit()自然就是隱藏部隊了,我們又可從隱藏部隊身上提取其它的資料,對它們一一做清空。

    本範例使用回傳錯誤與遊戲暫存的最重要意義是利用反查法取得隱藏部隊擊中的那個瞬間。 次要的則是記錄所有臨時創造的相關觸發及觸發動作,並且清空它們。

  2. 各位可以想想,如果不使用JASS,不使用回傳錯誤,有沒有辦法做出此效果?
    其實可以,如果全地圖只有一個部隊能施展此技能,那麼我們可以用一個全域變數記錄目標,並且新增傷害事件,然後在傷害事件的條件中對它們進行比對。 不過這樣一來,隨著技能施展次數的增加,會累積很多不必要的傷害事件,它們很可能不斷地被觸動,造成資源的浪費。

    再者,如果此類的部隊很多呢?如果此技能沒有冷卻時間呢?其它的做法難以達到這種要求……。 使用回傳錯誤的好處就是,無論施法者有多少,施法次數有多頻繁,我們只要透過這個連結系統,就能精確地由一個物件推到所有相關的物件,精確地做比對、處理、以及刪除。

  3. 這個範例也展示了純JASS技能和GUI觸發技能的差異,純JASS技能通常會把所有使用的觸發和函數放在一起,所以只要在觸發編輯器新增一個觸發就好了。
    而GUI觸發技能往往要新增好幾個觸發,並且在其中進行開、關、調用。所以相對而言,純JASS製件的技能在複製使用上比較方便,讀者可以參考範例地圖中的技能範例,裡面的寫法就非常適合複製到別的地圖使用。

    另外,JASS技能是在施法後才新增觸發處理後續動作;而GUI則是一開始就寫好所有觸發擺著,在施法後開啟以供使用,不過如此一來,支援多重施法就變得相當麻煩。

    當然純JASS技能撰寫費時,如果有很多pick unit之類的子函數,程式碼更是長得不得了。所以JASS的使用應自己拿捏。 不需要JASS就不必用;只需要一兩行JASS的話,可以用GUI的插入單行語法;有必要時才用純JASS寫。

    如果地圖全部用純JASS寫,可以省去很多不必要的程式碼,使地圖執行效率更高。不過代價就是,你得花上好幾倍的時間。 夫尺有所短,寸有所長,JASS和GUI都只是工具,能善用工具的人,才能做出比別人更好的效果。

綜合教學/jass入門教學/e5.回傳錯誤.txt · 上一次變更: 2007年11月11日 3:37 pm 來自 wasabi
www.chimeric.de Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0