+ Reply to Thread
Page 1 of 9 123 ... LastLast
Results 1 to 10 of 82
  1. #1
    Fenix

    JASS Tutorial 1.8

    JASS Tutorial
    Version History
    1.9 ปรับเนื้อหาให้เข้ากับความรู้ตอนนี้
    1.71 Minor Update เรื่อง String
    1.7 เพิ่มเนื้อหาของ elseif กับ return + ปรับเนื้อหาบางส่วน
    1.6 อธิบายการทำงานของ Function เพิ่มเติม (หัวข้อ 4.7)
    1.5 แก้บั๊กที่อาจเกิดขึ้นในเรื่องของ Timer + แก้ Leak นิดหน่อย
    1.4 เพิ่มเนื้อหาในเรื่องของ function และ Variable
    1.3 แก้ Leak เรื่อง Location ใน Script
    1.2 Update เรื่องการทำ Unit Group และ Timer
    กับการแก้ไข Leak ในเรื่องของ Sound
    1.1 เพิ่ม Warning + แก้ไข quote บางอัน
    1.0 Document Created

    Requirement:
    1. Mapmaker ที่มั่นใจว่าจำคำสั่งใน GUI ได้ 90% และรู้ว่าทำอะไรได้
    2. ความรู้พื้นฐานด้านภาษาโปรแกรม เช่น C++
    3. คำเดียว "เปิดใจ" พร้อมเมื่อไหร่ก็อ่านได้ หรือจะมาอ่านตอนหลังก็ได้นะ

    1. มาคุยกันก่อน

    JASS เป็นชื่อที่ Mapmaker ทุกท่านรู้จักกันเป็นอย่างดี ในการจัดการ Spell ที่ GUI เข้าถึงไม่ได้
    รวมทั้งยังใช้ สร้าง System ที่ใช้ใน Map ได้ ถ้าเทียบกับ GUI แล้วนับว่าเป็นการสร้างที่ยาก
    มากกว่าเท่าสองสามเท่าตัวเลยทีเดียว เพราะไม่ใช่การมาตั้งแถบคำสั่งให้ Trigger ทำงาน
    แต่จะเป็นการเขียน Script แบบสดๆ ลงไปเลย พื้นฐานของ Jass ก็คือภาษา C (ภาษาเครื่อง)
    แน่นอนครับว่าต้องจำคำสั่งที่ใช้เขียนทั้งหมด ซึ่งมหาศาลกว่า GUI หลายเท่าตัว
    ถ้าคุณรู้เกี่ยวกับ C ไม่ยากอะไรครับ แต่ถ้าที่คนไม่รู้ภาษาเลย พออ่านปุ๊ปก็งง ไม่เข้าใจ
    Tutorial นี้จะช่วยให้รู้การทำงานของ JASS ก่อนที่จะเริ่ม ให้ดาวน์โหลด JassCraft ก่อน
    เป็นโปรแกรมที่จะช่วยตรวจสอบว่า Script ที่เขียนไว้ผิดหรือไม่และรวบรวมคำสั่ง JASS เอาไว้ทั้งหมด

    Credit: www.hiveworkshop.com

    Warning =คำเตือน=
    *ก่อนที่จะอ่านต่อจากนี้ไป ผมขอความกรุณาอย่างหนึ่งนะครับ ถ้าคุณผู้อ่านเพิ่งเริ่มหัดทำ WE แล้วไม่ค่อยมีความรู้กับ Trigger
    ช่วยไปอ่าน Tutorial ทุกอันในเว็บไซต์นี้ก่อนนะครับ มีคนเขียนสิ่งดีๆ เอาไว้ให้คุณศึกษาและเรียนรู้เยอะครับ
    เพราะสิ่งที่คุณจะได้อ่านต่อไปนี้ ต้องอาศัยการฝึกปรือมาในระดับสูงพอสมควร*

    2. ทำไมถึงต้องใช้ JASS?

    ง่าย เร็ว สั้น การทำงานที่มีประสิทธิภาพมากกว่า GUI หลายเท่าตัว
    จุดเด่นของ JASS จะลงหลักในเรื่องของการทำ Spell และ System
    ซึ่งมีหลายอันที่ GUI เข้าถึงไม่ได้ (Leak และ เปลือง Memory)
    แต่ JASS สามารถที่จะทำได้ดีกว่า ซึ่งรวมถึงเรื่องของ Effect ที่ไหลลื่น
    เหนือสิ่งอื่นใด JASS ไม่เรื่องมาก จินตนาการว่าหลุดจากกรอบของ GUI
    ยิ่งเรื่องการทำงานของฟังก์ชั่น นับว่าต่างกันโดยสิ้นเชิง

    รวมทั้งยังมี Function ลับที่สามารถเรียกใช้ได้ซึ่งสามารถหาดูได้ง่ายๆ ใน Jass Craft
    หรือเปิดไฟล์ native.j ที่แถมมากับ Jass Craft ได้

    ผมไม่ได้โจมตี GUI นะครับ เพราะมีบางสิ่งที่ JASS ทำไม่ได้เหมือนกัน
    เช่น การทำ Cinematic, การทำ Quest ทำได้ช้ากว่า GUI เยอะครับ
    แต่ Jass อาจจะไม่ถึงขั้นทำ Cinematic เองไม่ได้ เพียงแต่จะไม่มี Preview Camera ให้ดู
    ก็นั่งคำนวน แกน x,y,z กันจนบ้าไปเลยนั่นแหละ
    ยิ่งการเปลี่ยนค่าตัวเลขในหลายๆ Trigger โดยใช้ Local Variable อันนี้ก็เป็นไปได้ยาก
    ยกเว้นว่าเอามายัดรวมกันใน Handle เลยน่ะนะ

    ดังนั้น ไม่ต้องกลัวว่า GUI จะไม่มีประโยชน์ สองอย่างนี้มีความเกี่ยวข้องกันเสมอครับ
    และแทนที่กันไม่หมดอย่างแน่นอน

    3. การทำงานของ Function ใน JASS
    เริ่มต้นกันที่ String กันก่อน

    ให้สร้าง Trigger ขึ้นมาอันหนึ่ง และเซ็ทตามนี้

    Init
    Events
    Time - Elapsed game time is 5.00 seconds
    Conditions
    Actions
    Game - Display to (All players) the text: Get Ready!
    *ให้ไปที่ Edit > Covert to Custom Text ถึงจะเปลี่ยนเป็น JASS Script ครับ

    function Trig_Init_Actions takes nothing returns nothing
    call DisplayTextToForce( GetPlayersAll(), "TRIGSTR_032" )
    endfunction

    //===========================================================================
    function InitTrig_Init takes nothing returns nothing
    set gg_trg_Init = CreateTrigger( )
    call TriggerRegisterTimerEventSingle( gg_trg_Init, 5 )
    call TriggerAddAction( gg_trg_Init, function Trig_Init_Actions )
    endfunction
    ถ้าคนที่ไม่เคยเห็นก็คงจะมีความรู้สึกหูอื้อตาลายไปหมด ว่ามันคืออะไร
    เราจะเห็นว่า Get Ready! กลายเป็น "TRIGSTR_032" ไปซะแล้ว
    วิธีที่จะเขียนข้อความใน JASS คือ

    call DisplayTextToForce( GetPlayersAll(), "Get Ready!" )
    ต้องมี " " ปิดหัวท้ายตลอดนะครับ เหมือนการจบ Function ทั่วๆ ไป
    ไม่อย่างนั้น สิ่งที่เราพิมพ์ไป จะถือว่าเป็น Variable ไปโดยปริยาย
    ลองทำกันดูนะครับ หรือว่าอยากจะเพิ่มมากกว่านี้ก็ได้
    *หรือถ้าอยากให้ Null(โล่งๆ) ให้เขียน "" ไว้ครับ

    ลองมาเพิ่ม text กันครับ

    call DisplayTextToForce( GetPlayersAll(), "Get Ready!" )
    call TriggerSleepAction( 2 )
    call DisplayTextToForce( GetPlayersAll(), "Stand Up!" )
    call TriggerSleepAction( 2 )
    call DisplayTextToForce( GetPlayersAll(), "Hook Up!" )
    กรณีที่เป็นการทำ Concentration String

    function Trig_Str_Actions takes nothing returns nothing
    local string a = "If you love me then,"
    local string b = "...hold me, I'm so scare..."
    call DisplayTextToForce( GetPlayersAll(), a + b)
    endfunction
    ถ้าหากว่าเราอยากที่จะเว้นบรรทัดนั้น ห้ามกด Enter นะครับ
    ไม่อย่างนั้น Crash ทันที แต่ต้องอาศัยพื้นฐานภาษานิดหน่อย
    ให้เติม \n ในกรณีที่จะขึ้นบรรทัดใหม่ เช่น

    call DisplayTextToForce( GetPlayersAll(),"Watch out! They are preparing a giant slime thing.\nConcentrates your mind warrior.")

    พอในเกมจะเห็นเป็น
    Watch out! They are preparing a giant slime thing.
    Concentrates your mind warrior.

    ตรงนี้ผมว่าหลายๆ คนคงเข้าใจแล้วล่ะ
    ว่าตอนเขียนรายละเอียด Quest ใน JASS มันจะนรกขนาดไหน
    ยิ่งรวมเว้นบรรทัดด้วยนี่ก็ ตาลาย @_@

    function InitTrig_Init takes nothing returns nothing
    set gg_trg_Init = CreateTrigger( )
    call TriggerRegisterTimerEventSingle( gg_trg_Init, 5 )
    call TriggerAddAction( gg_trg_Init, function Trig_Init_Actions )
    endfunction
    ทีนี้ผมจะอธิบายถึง Function ที่ทำให้เกิดเป็น Trigger ขึ้นมา

    function InitTrig_Init takes nothing returns nothing
    ตรง Init "ชื่อที่เราสร้างขึ้นมาใน GUI" หรือ Untitled ที่เราตั้งขึ้นมาในตอนแรก
    ดังนั้นหากว่ามีการเปลี่ยนจากชื่อนี้เป็นอย่างอื่นแล้วไม่สอดคล้องกับตอนที่เราสร้างขึ้นมา WE จะ Crash

    set gg_trg_Init = CreateTrigger( )
    เป็นการสร้าง Trigger ครับ โดย CreateTrigger( )
    การจะให้ Trigger เริ่มทำงานได้นั้น จะเริ่มจาก Event ตามหลักการ ดังนั้นสิ่งที่จะทำให้ทำงานได้คือ

    call TriggerRegisterTimerEventSingle( gg_trg_Init, 5 )
    เราสามารถใส่ Event กี่อันก็ได้ครับ ไม่จำกัด
    และถ้ามี Condition ก็จะเป็น

    call TriggerAddCondition
    *ในกรณีของ Condition นั้นสามารถใส่ได้เพียงอันเดียวเท่านั้น*
    ส่วน Action นั้นจะเป็นดังนี้

    call TriggerAddAction
    มาดูตัวอย่างต่อไปจะเป็น Trigger Spell

    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A00J' ) ) then
    return false
    endif
    return true
    endfunction

    function Trig_Destructive_Force_Actions takes nothing returns nothing

    endfunction

    //===========================================================================
    function InitTrig_Destructive_Force takes nothing returns nothing
    set gg_trg_Destructive_Force = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Destructive_Force, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Destructive_Force, Condition( function Trig_Destructive_Force_Conditions ) )
    call TriggerAddAction( gg_trg_Destructive_Force, function Trig_Destructive_Force_Actions )
    endfunction
    Trigger นี้จะทำงานเมื่อมียูนิตร่ายเวทย์ออกมา โดยจะมี Event ดังนี้
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Destructive_Force, EVENT_PLAYER_UNIT_SPELL_EFFECT )

    call TriggerAddCondition( gg_trg_Destructive_Force, Condition( function Trig_Destructive_Force_Conditions ) )

    และมีเงื่อนไขที่ว่าเวทย์ที่ร่ายออกมาเป็นเวทย์ตัวไหน ซึ่งจะมี Condition ที่ตรวจเช็คดังนี้

    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A00J' ) ) then
    return false
    endif
    return true
    endfunction
    ใน Jass คุณไม่ต้องใช้การตรวจที่ยาวขนาดนี้ครับ สั้นๆ เพียงเท่านี้ก็พอ

    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A00J'
    endfunction
    ถ้าหากเงื่อนไขถูกต้อง พอถึงส่วน Action จะเป็นการเรียกฟังก์ชั่นเกี่ยวกับ Spell ขึ้นมา
    call TriggerAddAction( gg_trg_Destructive_Force, function Trig_Destructive_Force_Actions )

    function Trig_Destructive_Force_Actions takes nothing returns nothing

    endfunction
    ในที่นี้ผมให้ดูเพียง Action ว่าต้องทำ Function เอาไว้ แต่ไม่มีการกระทำใดๆ ทั้งสิ้น
    เช่นเดียวกับ Condition ต้องมี Function วางอยู่ด้านบน ไม่อย่างนั้น WE จะไม่เจอ
    function นี้ และจะเกิดเป็น Syntax Error ขึ้นมา

    ตัวอย่างที่ถูกต้อง

    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A00J'
    endfunction

    function InitTrig_Destructive_Force takes nothing returns nothing
    set gg_trg_Destructive_Force = CreateTrigger( )
    call TriggerAddCondition( gg_trg_Destructive_Force, Condition( function Trig_Destructive_Force_Conditions ) )
    endfunction
    ตัวอย่างที่ผิด

    function InitTrig_Destructive_Force takes nothing returns nothing
    set gg_trg_Destructive_Force = CreateTrigger( )
    call TriggerAddCondition( gg_trg_Destructive_Force, Condition( function Trig_Destructive_Force_Conditions ) )
    endfunction

    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A00J'
    endfunction
    คุณสามารถเปลี่ยนชื่อ Function ของ Trigger ได้ตามใจชอบ แต่ต้องมีความสอดคล้องกันนะครับ
    ไม่อย่างนั้น WE จะ Crash จำเอาไว้เลยนะครับว่า Function จะชื่ออะไรก็ได้ แต่ห้ามซ้ำกับฟังก์ชั่นอื่น

    ตัวอย่างที่ถูกต้อง
    function Airbrush takes nothing returns nothing
    endfunction
    function Artistic takes nothing returns nothing
    endfunction
    แบบด้านบนถูกต้องครับแต่ไม่ใช่แบบด้านล่างนะ

    ตัวอย่างที่ผิดและทำให้ WE Crash
    function Airbrush takes nothing returns nothing
    endfunction
    function Airbrush takes nothing returns nothing
    endfunction
    เอาล่ะลองเปลี่ยนชื่อของฟังก์ชั่นใน Trigger กัน

    function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A00J'
    endfunction

    function Actions takes nothing returns nothing

    endfunction

    function InitTrig_Destructive_Force takes nothing returns nothing
    set gg_trg_Destructive_Force = CreateTrigger( )
    call TriggerAddCondition( gg_trg_Destructive_Force, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Destructive_Force, function Actions )
    endfunction
    ที่ชื่อ Conditions ต้องเปลี่ยนตามด้วย เพราะ Condition/Action ใน Trigger ต้องอาศัยฟังก์ชั่นที่อยู่ด้านบน

    function และ endfunction คือจุดเริ่มต้นและจุดสิ้นสุดของ Function
    ถ้าไม่ใส่ endfunction ปิดท้าย WE จะ Crash ได้อีกเช่นกัน

    function Airbrush nothing returns nothing
    เช่นในกรณีนี้ WE จะ Crash ถ้าเราไม่ใส่ endfunction

    คุณจะสร้าง Function ได้ไม่จำกัด แต่ห้ามนำมาซ้อนกันเด็ดขาด เช่น

    function condtion takes nothing returns nothing
    function action takes nothing returns nothing
    endfunction
    endfunction
    แบบนี้ไม่ถูกต้องครับ Crash แน่นอน ถ้าให้ถูกก็คือ

    function condtion takes nothing returns nothing
    endfunction
    function action takes nothing returns nothing
    endfunction
    การทำงานของ Action ใน Function ต้องมีเรียกชื่อ Function ออกมา

    call name
    ซึ่ง name ก็คือชื่อของ Function นั้นๆ เช่น

    call TriggerSleepAction( 5 )
    call RestartGame()
    โดยที่ TriggerSleepAction คือ Wait ใน GUI นั่นเอง
    ส่วน RestartGame ถ้าชื่อไม่ผิดก็ลองเอาไปใส่ดูว่าจะเกิดอะไรขึ้น???

    หรือผมอยากที่จะใช้ ฟังก์ชั่น Airbrush ที่ผมสร้างด้านบนก็เรียกด้วยวิธีเดียวกัน

    call Airbrush()
    *ในการที่เราจะทำอย่างนี้ได้ ใน WE จะมีที่เฉพาะให้ใส่ JASS Code ลงไปได้เลย
    อยู่ด้านบนสุดของ Trigger ที่เป็นไอคอนรูป MAP ครับ จะใส่ไปเท่าไหร่ก็ได้
    หรือเราจะตั้ง Trigger ขึ้นมาหนึ่งอัน แล้วใส่ Function เอาไว้ก็ได้ครับ
    เพียงแต่ Trigger ที่ว่านี้ต้องไม่ Intially On ถึงจะเรียก Function ออกมาใช้งานได้
    และในบางทีถ้าเราตั้ง Intailly On ไว้ WE จะ Crash ในตอนที่เซฟไฟล์ เพราะหา Function ไม่เจอ*

    ระวัง! ในการที่เราจะเรียก function ให้ทำงานได้นั้น function จะต้องอยู่ด้านบน
    เพื่อที่ WE สามารถตรวจสอบ function ได้ว่ามีอยู่จริง คือต้อง เตรียมไว้ก่อน ถึงจะเรียกได้
    มีเทคนิคสำหรับ Jass อีกอย่างหนึ่งจะมี native ที่ชื่อว่า call ExecuteFunc("functionname")
    สามารถที่จะเรียก function อันไหนให้ทำงานก็ได้ ไม่ว่าจะอยู่บนหรือล่าง

    จบส่วนของ Trigger แล้วครับ แต่จะมีพูดถึงอีกครั้งในบทต่อไป

    4. Local and Global Variable
    เป็นชื่อที่จะใช้ใน Memory (หน่วยความจำ) ซึ่งจะมีชื่อต่างกันเหมือนกับฟังก์ชั่น
    ใน GUI ซึ่งมีลักษณะของ Global Variable ที่แยกตามหมวดหมู่ไป

    4.1 Local Variable เป็นสิ่งที่อยู่ใน Function นั้นๆ และสามารถใช้ได้ในฟังก์ชั่นเดียวเท่านั้น
    ไม่สามารถที่จะนำไปใช้กับอันอื่นได้ (คือใช้แล้วเดี๋ยวนั้นจบตรงนั้นเลย) ก่อนที่เราจะใช้นั้นต้องมีการเรียกกันก่อน

    local type name
    type คือ ลักษณะของ Variable นั้นๆ และ Name ก็คือชื่อของ Variable

    เช่น local unit u

    unit คือลักษณะของ Vairable ซึ่งก็คือยูนิต ส่วน u คือชื่อของ Variable
    โดยที่เราสามารถเรียกใช้ได้หลากหลายได้ในฟังก์ชั่นตราบใดที่ชื่อไม่เหมือนกัน เช่น

    local unit u
    local unit cast
    แต่ถ้า

    local unit cast
    local unit cast
    Crash ครับ

    ที่สำคัญ ชื่อของ Variable นั้นไม่ต้องตั้งเหมือนกับ Variable ใน GUI
    สามารถเขียนลงไปได้เลยครับ (ประหยัดเนื้อที่ดีไหม? ไม่ต้องนั่งจำด้วย)
    นอกจากนั้นเราสามารถที่จะมีชื่อ Variable ซ้ำกันในหลายๆ Function ได้
    ตัวอย่าง:

    function disaster takes nothing return nothing
    local unit cast
    endfunction
    function chaos takes nothing return nothing
    local unit cast
    endfunction
    function doom takes nothing return nothing
    local location cast
    endfunction
    *มีเงื่อนไขหลักๆ อยู่แค่ข้อเดียวในการใช้ local Variable
    นั่นคือ ห้ามใช้หลังจาก Function call เด็ดขาด*

    ตัวอย่าง:
    function chaos takes nothing return nothing
    set cast = GetSpellAbilityUnit()
    local unit cast
    endfunction
    WE จะไม่รู้ครับว่า cast เป็น Variable ตัวไหนเพราะมาทีหลัง

    4.2 Global Variable

    ใช้เฉพาะใน GUI มันเหมือนเป็นการสร้าง Variable ขึ้นมาเหมือนกับการสร้าง Trigger
    เพียงแต่จะมีลักษณะที่แตกต่างกันตรงที่ Global จะมี "udg_name" นำหน้า
    ซึ่ง name ก็คือชื่อของ Variable นั้นๆ

    Global และ Local สามารถมีชื่อเหมือนกันได้ (ไม่ Crash หรืออะไรใดๆ ทั้งสิ้น)
    ซึ่งจะมี udg_ ที่เป็นตัวจำแนกว่า Variable นั้นคือ Global ซึ่งจะเรียกใช้โดยฟังก์ชั่นไหนก็ได้
    โดยที่ Local จะได้เฉพาะ Function นั้นๆ เท่านั้น

    *สิ่งที่ต่างกันอย่างชัดเจนระหว่าง Local และ Global ก็คือ
    Global จะถูกเรียกใช้จาก Trigger อันไหนก็ได้ เปลี่ยนแปลงค่าได้ตลอดเวลา
    แต่ Local จะใช้ได้เฉพาะ Trigger นั้น ฟังก์ชั่นอันนั้นเท่านั้นครับ*

    4.3 Local Trigger

    ในที่นี้ผมจะขอพูดถึงเรื่อง Trigger ต่อจากหัวข้อที่แล้ว
    เพราะอะไรหรือครับ? เพราะมันมีวิธีการเปลี่ยน Global Trigger ให้เป็น Local น่ะสิ

    function InitTrig_Destructive_Force takes nothing returns nothing
    set gg_trg_Destructive_Force = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Destructive_Force, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Destructive_Force, Condition( function Trig_Destructive_Force_Conditions ) )
    call TriggerAddAction( gg_trg_Destructive_Force, function Trig_Destructive_Force_Actions )
    endfunction
    (ถ้าเป็นไปได้ ลองเปลี่ยนเองดูก่อนครับ ถ้าไม่ไหวแล้วก็ดูเฉลยด้านล่างเอา)

    function InitTrig_Destructive_Force takes nothing returns nothing
    local trigger t
    set t = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Trig_Destructive_Force_Conditions ) )
    call TriggerAddAction( t, function Trig_Destructive_Force_Actions )
    set t=null
    endfunction
    อย่าลืมเปลี่ยน gg_trg_Destructive_Force เป็น t นะครับ
    เพราะเราตั้ง t ให้เป็น ชื่อของ Local Trigger ไปแล้ว
    อย่าลืมข้อดีข้อเสียนะครับ ทำได้ครับทำได้ แต่เรียกใช้จาก Function หรือ Trigger อื่นไม่ได้ครับ

    4.4 Variable Array

    เหมือนเดิมครับแต่ต่างกันนิดหน่อย เช่น

    local unit array y
    set y[1] = GetSpellAbilityUnit()
    set y[2] = GetSpellTargetUnit()
    เพียงแค่เติม array คั่นระหว่าง ลักษณะของ Variable(Unit) และ ชื่อ Variable(y)
    (อ้างอิง) JassCraft บอกเอาไว้ว่า array จัดแจงสูงได้สุดที่ 8192 array

    4.5 Integer กับ Real Variable

    แน่นอนครับว่าสองอย่างนี้ก็มี Local ด้วยเช่นกัน

    ตัวอย่าง:
    local integer int1 = 1
    local integer int2 = 8
    local real angle = 55
    ถ้าจะเปลี่ยนจาก Integer เป็น Real ให้ใช้ I2R ครับ

    local integer real = I2R(int1)
    ห้ามลืมวงเล็บนะครับ ไม่งั้น Crash
    ถ้าจาก Real เป็น Integer ก็สลับกันเป็น R2I

    4.6 Point หรือใน JASS เราจะเรียกว่า Location

    local location p
    set p = GetSpellTargetUnit()
    เป็นการตั้งค่า p ให้จำตำแหน่งของยูนิตที่ร่ายเวทย์ครับ

    4.7 การนำค่า Variable ไปใช้งานในฟังก์ชั่นอื่น

    ผมได้กล่าวไว้ข้างต้นแล้วว่าการสร้าง Function ต้องเป็นแบบนี้ใช่ไหมครับ

    function name takes nothing returns nothing
    endfunction

    แต่ในความจริงแล้วนั้น nothing สามารถใส่ Variable ลงไปได้
    โดยที่ takes สามารถนำ Variable มาใช้งานได้ในตอนที่เรามีการเรียก function
    ส่วน returns เป็นการคืนค่าของลักษณะ Variable ที่เราได้ตั้งเอาไว้

    *ในการใช้ returns จะจำกัดเพียง Variable เดียวเท่านั้น*

    อย่างใน Condition ที่มีการคืนค่าของ Boolean ว่า true หรือ false

    เอาล่ะ มาดูตัวอย่างการเรียกใช้งาน function กัน

    function SynergyPowerUp takes unit u, integer a returns nothing
    if a=='A03D' then
    call IncUnitAbilityLevel(u,'Awrg')
    endif
    endfunction
    function Trig_Synergy_Check_Actions takes nothing returns nothing
    call SynergyPowerUp(GetTriggerUnit(),GetLearnedSkill())
    endfunction
    function InitTrig_Synergy_Check takes nothing returns nothing
    set gg_trg_Synergy_Check = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Synergy_Check, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddAction( gg_trg_Synergy_Check, function Trig_Synergy_Check_Actions )
    endfunction
    Trigger นี้ทำงานเมื่อยูนิตที่เป็น Hero เรียนรู้ Skill
    จากนั้นจะมีการนำ GetTriggerUnit() กับ GetLearnedSkill() ไปประมวลผลใน ฟังก์ชั่น SynergyPowerUp
    พอตั้งค่าเสร็จเรียบร้อยแล้วก็จะมีการเรียกใช้ Function SynergyPowerUp
    ซึ่งจะนำค่า u กับ a มาใช้งาน ซึ่งหากว่า integer เป็น 'A03D'
    จะมีการเพิ่ม Level ให้กับ Ability ของ Hero ที่เรียนรู้สกิล

    Tip: Variable ใน takes นั้นจะมีค่าอยู่จนกว่าจะจบฟังก์ชั่นอันนั้นเท่านั้น
    โดยที่จะเป็นค่าถาวรตราบใดที่เราไม่เปลี่ยนมัน โดยการ set และจะไม่มีการเขียนซ้ำทับกันเด็ดขาด

    ขอยกตัวอย่างนะ

    function testmessage takes string s returns nothing
    call DisplayTextToForce( GetPlayersAll(), s )
    endfunction

    function Trig_Map_Function_Actions takes nothing returns nothing
    call testmessage("What is the JASS?")
    call testmessage("It is a programming language developed by Blizzard.")
    call testmessage("Are you telling me that only programmer can use it?")
    call testmessage("Nope, every mapmaker can use it without any knowledge in language like C++")
    call testmessage("Blizzard made it for everyone but you must open your mind.")
    call testmessage("Can you tell me what is different between GUI and JASS?")
    call testmessage("This question is your own to find it out.")
    endfunction

    function InitTrig_Map_Function takes nothing returns nothing
    set gg_trg_Map_Function = CreateTrigger( )
    call TriggerRegisterTimerEventSingle( gg_trg_Map_Function, 0.00 )
    call TriggerAddAction( gg_trg_Map_Function, function Trig_Map_Function_Actions )
    endfunction
    เมื่อ Trigger นี้ทำงานโดยเข้าไปในเกมเพียง 0.00 จะมีการเรียกใช้ Function testmessage
    โดย call testmessage(" ตรงนี้ให้ใส่ String ") และจะมีการใช้ variable s ที่เป็น string
    ซึ่งจะมาจากข้อความที่เราเขียนนั่นเอง ดังนั้น เมื่อเข้าไปในเกม เราจะเห็น Message Display ทั้งหมดดังนี้

    What is the JASS?
    It is a programming language developed by Blizzard.
    Are you telling me that only programmer can use it?
    Nope, every mapmaker can use it without any knowledge in language like C++
    Blizzard made it for everyone but you must open your mind.
    Can you tell me what is different between GUI and JASS?
    This question is your own to find it out.

    ในตอนสุดท้าย ไม่จำเป็นที่จะต้องใส่ s=null เพราะเมื่อจบฟังก์ชั่น
    Variable นี้ก็จะถูกยกเลิกการใช้ไปโดยปริยาย ในเรื่องของการ null นั้น สามารถที่จะอ่านได้ในหัวข้อต่อไปครับ

    ผมพยายามแสดงให้เห็นว่าแม้จะไม่มี wait ก็ตามแต่ function
    ก็สามารถทำงานได้โดย variable เดียวกัน โดยที่ไม่ซ้ำซ้อนกัน

    ข้อดีตรงนี้เรียกได้ว่าเป็นสิ่งที่เหนือกว่า GUI แบบข้ามขั้น
    GUI ก็สามารถเรียกได้โดยใช้ Custom Script: call testmessage("JASS: I apologize for this, GUI. XD")

    5. การทำงานของ Function ในลักษณะต่างๆ

    5.1 การตั้ง Loop

    Structure ของ Loop จะเป็นอย่างนี้
    loop
    exitwhen <condition>
    action
    endloop
    จุดสำคัญครับ ถ้าลืม endloop ปิดไว้ตอนท้ายแล้วล่ะก็ WE จะ Crash ในทันทีทันใด

    ตัวอย่างการตั้ง loop
    loop
    exitwhen int1 > int2
    set int1 = int1 + 1
    endloop // don't for get this one, I WARNED YOU!
    Loop นี้จะจบเมื่อค่า int1 มากกว่า int2
    และถ้าเราไม่ตั้ง condition ให้ Loop มันจบได้ มันก็จะวิ่งไปจน Infinity
    แน่นอนว่าถ้า Infinity พอรันในเกมจะหยุดไปเลย

    5.2 การตั้ง if

    if condition then
    action then
    else
    action else
    endif // Again, don't for get this one.
    ในการตั้ง condition นั้นจะต้องเป็นการเปรียบเทียบ ถ้าหากว่าถูกต้อง
    action then จะทำงาน แต่ถ้าไม่ถูกต้อง action else จะทำงานแทน
    ถ้าไม่ใช้ else ก็ไม่ต้องเพิ่ม action นั้นๆ ครับ เช่น

    if udg_BloodSuckCount >= 20 then
    set udg_BloodSuckCount = 0
    endif
    กรณีที่ BloodSuckCount มากกว่าหรือ = 20 ก็จะมีการ set variable ตัวนี้ให้เป็น 0
    ห้ามลืม endif เพราะมันคือการทำให้ if จบกระบวนการ
    เหมือนกับ endfunction และ endloop ถ้าลืม WE จะ Crash ครับ

    *เราจะใส่ ( ) ไว้ตรง Condition หรือไม่ก็ได้ ให้ผลเหมือนกันครับ
    เพียงแต่ ถ้าใส่วงเล็บ if (udg_BloodSuckCount >= 20) then
    เราจะเห็น Condition ชัดขึ้นแค่นั้นเอง*

    ตัวอย่างการเปรียบเทียบ
    == equal to
    != not equal to
    < Less than
    <= Less than or equal
    >= Greater than or equal
    > Greater than
    กรณีที่เป็น and หรือ or ให้ใส่เอาไว้ระหว่าง Condition สองอัน
    เช่น
    function Trig_Lighting_Fury_Conditions takes nothing returns boolean
    return GetHeroLevel(GetLevelingUnit()) == 20 and GetUnitTypeId(GetLevelingUnit()) == 'Hant'
    endfunction
    นอกเหนือจากนี้ก็จะมี elseif เป็นอีกหนึ่ง Structure ที่น่าสนใจ
    การใช้ elseif จะมีลักษณะดังนี้

    function stream takes nothing returns nothing
    local integer i = 1
    if i=1 then
    call SetTimeOfDay( 12 )
    elseif i=2 then
    call SetTimeOfDay( 24 )
    endif
    endfunction
    การทำงานของ Function stream นี้จะมีการตั้ง i = 1
    ใน if นั้นถ้าหากว่า i=1 จะมีการตั้งเวลาของเกมให้เป็น 12 และทำให้ if จบกระบวนการ
    แต่ในกรณีที่ i ไม่ใช่ 1 นั้นก็จะมีการหา i ไปเรื่อยๆ จนกว่าจะจบ if
    ถ้าเราจะเพิ่มตัวเลขก็เช่น

    function stream takes nothing returns nothing
    if i=1 then
    call SetTimeOfDay( 12 )
    elseif i=2 then
    call SetTimeOfDay( 24 )
    elseif i=3 then
    call SetTimeOfDay(1)
    elseif i=4 then
    call SetTimeOfDay(9)
    endif
    endfunction
    6. การคืนค่าของ boolean ว่าถูกต้องตามเงื่อนไขหรือไม่
    ตรงนี้ผมจะเจาะลึกจากการสร้าง Trigger ด้านบนนะครับ
    JASS จะใช้ return ซึ่งจะต่างกับ GUI ตรงที่

    GUI:

    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A00J' ) ) then
    return false
    endif
    return true
    endfunction
    JASS:
    function Trig_Destructive_Force_Conditions takes nothing returns boolean
    return GetSpellAbilityId()=='A00J'
    endfunction
    'A00J' ก็คือ Raw Code ของ Ability
    นี่เป็นการเช็คอย่างง่ายๆ ว่า Ability ที่ยูนิตร่ายเป็น 'A00J'หรือไม่
    ถ้าใช่เมื่อไหร่ ในส่วนที่เป็น Action ก็จะทำงาน

    ในเรื่องของการ return จะมีบั๊กที่นำเอามาใช้ได้
    คำสั่ง return หรือ Skip Remaining Action ใน GUI
    เป็นการทำให้ Trigger หยุดทำงาน แต่ตามตัวอย่างด้านบนนั้น
    มีการ return false กับ true ซึ่งตรงกับ returns ในส่วนสุดท้ายของ function
    และใน Code ของ JASS นั้นมีการใช้ return Abiility โค้ดว่าใช้ 'A00J'
    และถ้าได้ค่าเป็น true ขึ้นมา Condition ก็จะถูกต้องซึ่งจะทำให้
    Action ใน Trigger ทำงาน

    ทีนี้ นอกจาก boolean (true,false) เราสามารถนำ return มาใช้งานกับอย่างอื่นได้เช่นกัน
    ให้ดู function อันนี้ครับ

    function GetAngle takes integer i returns real
    if i==1 or i==4 then
    return 45.00
    elseif i==2 or i==8 then
    return 315.00
    elseif i==3 or i==5 then
    return 135.00
    elseif i==6 or i==7 then
    return 225.00
    endif
    return 0.00
    endfunction

    function HeroSelection takes nothing returns nothing
    local real s = GetAngle(1)
    endfunction
    Function HeroSelection จะมีการหา real จาก Function GetAngle
    จะสังเกตุได้ว่า ไม่มีการ call เพราะถ้าเราต้องการค่าจาก return
    เราสามารถนำมาได้เลยโดยไม่ต้อง call
    ในกรณีนี้ผมอยากได้ real ที่เป็น 1 ว่าจะมีค่าเท่าไหร่
    พอเรียกใช้ GetAngle ก็จะมีการหาค่า real ตามที่ผมต้องการ
    ถ้าอ่านเรื่อง elseif ก็คงจะเข้าใจแล้วนะครับว่า ถ้า 1 นั้น
    เราจะได้ค่า real s ใน Function HeroSelection เป็นเท่าไหร่เอ่ย
    แน่นอนครับ ว่าต้องได้ 45.00 กรณีที่เป็นตัวเลขอื่นก็จะได้ค่าเป็นท่าไหร่บ้าง?
    ลองไปทำกันดูนะครับ

    7. อาการ Leak ซึ่งก่อให้เกิดผลกระทบใน Memory
    ในส่วนนี้จะบอกกล่าวถึงการแก้ไข Leak

    7.1 Variable Leak มีนะครับ ไม่ใช่ไม่มี แก้ไขได้โดยทำให้ Variable เป็น Null เช่น

    local unit u
    set u = GetSpelltargetUnit()
    ในตอนที่เราใช้ set u = GetSpellTargetUnit() จะถือว่ามีการบันทึกลงใน Memory เมื่อนั้น
    เมื่อจบกระบวนการใช้งานให้ใช้ null ซึ่งเป็นการเคลียร์หน่วยความจำออก
    ถ้าไม่ทำแบบนี้ มันก็จะยังบันทึกอยู่ใน Memory ทำได้โดยการ set u = null ในส่วนสุดท้ายของ Function
    เมื่อไหร่ก็ตามที่คุณ null variable ไปเรียบร้อยแล้วจะเรียกใช้งานไม่ได้อีกเลย เช่น

    function DesTrig takes nothing returns nothing
    local trigger t=CreateTrigger()
    set t=null
    call DestroyTrigger(t)
    endfunction

    7.2 เป็นเรื่องของสิ่งต่างๆ ที่มีผลอยู่ใน Memory แม้เราจะ Null ก็ตาม
    ดังนั้นเราจะใช้อีก Function หนึ่งในการทำลาย เช่น


    call DestroyTrigger() <<< ทำลายทริกเกอร์
    call DestroyTimer() <<< ทำลายตัวจับเวลา
    call DestroyLightning() <<< ทำลาย effect ประเภท Lightning
    call DestroyMultiboard() <<< ทำลายมัลติบอร์ด
    คำสั่ง Destroy และ Remove มี List อยู่ใน JassCraft นำไปใช้ได้ครับ เยอะมาก
    หรือจะเปิด native.j ออกมาดูเลยก็ได้

    7.3 Sound Leak

    อันนี้หลายๆ คนคงคาดไม่ถึง ไม่ว่ายังไงก็ตามเราอย่าลืมว่า Sound ก็เป็น Variable ด้วย
    ดังนั้นเมื่อมีการใช้ Sound ที่เสร็จแล้วและไม่คิดที่จะใช้อีกให้ใช้
    call KillSoundWhenDone() <<< ในวงเล็บให้ใส่ชื่อของ Sound เข้าไปครับ
    หรือ call KillSoundWhenDone(GetLastPlayedSound())

    8. การทำ Spell ใน JASS

    ย้อนกลับไปดูด้านบนครับ
    ตรงวิธีการทำงานของ Trigger ผมจะเปลี่ยนมาใช้ JASS Code ให้ดูเป็นตัวอย่างครับ

    function Cond takes nothing returns boolean
    return GetSpellAbilityId()=='A001'
    endfunction

    function Destructive takes nothing returns nothing
    local unit u=GetSpellAbilityUnit()
    local location p
    local integer int1=1
    local integer int2=3*GetUnitAbilityLevel(u,'A001')
    local real real1=360/int2
    set p=GetSpellTargetLoc()
    loop
    exitwhen int1 > int2
    call CreateNUnitsAtLoc( 1, 'hpea', GetOwningPlayer(u), p, ( real1 * I2R(int1 ) ) )
    set udg_TempPoint = GetUnitLoc(GetLastCreatedUnit())
    call IssuePointOrderLocBJ( GetLastCreatedUnit(), "impale", udg_TempPoint )
    call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
    call RemoveLocation(udg_TempPoint)
    set int1=int1+1
    endloop
    call RemoveLocation(p)
    set u= null
    set p=null
    endfunction
    function InitTrig_Destructive_Force takes nothing returns nothing
    local trigger t
    set t=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t,Condition(function Cond))
    call TriggerAddAction(t,function Destructive)
    endfunction
    เอาล่ะ มาเริ่มการอธิบายกัน
    Event ของ Trigger นี้จะเริ่มทำงานจาก
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    ซึ่งจะเป็นการตรวจสอบการใช้เวทย์มนตร์ Spell หรือ Unt - A unit Starts the effect of an ability
    จากนั้นจะมาในเรื่องของ Condition ซึ่ง Spell ที่ใช้คือ 'A001' หรือ
    Destructive Force ถ้าหากว่าเป็น Spell นี้ Trigger จะเริ่มในส่วนของ Action

    เมื่อเริ่มการทำงานของ Action จะมีการเรียกใช้ local Variable ของ Function
    ซึ่งจะมี unit, location, integer และ real
    integer จะเป็น Loop โดย int1 จะเป็นจุดเริ่มต้นและ int2 จะเป็นจุดสิ้นสุด Loop
    โดยคำนวนจาก 3 * เลเวลของ Destructive Force ของยูนิตตัวที่ร่ายเวทย์
    location จะเป็นจุดที่ร่ายเวทย์ โดยจะมี p ซึ่งเซ็ทค่าให้จดจำยูนิตที่ร่ายเวทย์เอาไว้
    real ก็คือการหันหน้าของ Dummy ซึ่งจะหารด้วย int2 (ส่งผลตามเลเวลยูนิตที่ร่าย เช่น เลเวล 1 = 360 / 3
    ก่อน Loop จะเริ่มขึ้นจะเป็นการตั้งว่าเมื่อ int1 > มากกว่า int2 เมื่อไหร่ Loop จะจบที่ตรงนั้น
    หลังจากที่ตั้งเงื่อนไขการจบ Loop เรียบร้อยแล้ว action จะเรียก Dummy มาใช้
    โดยที่ u คือเจ้าของ Dummy p คือ Point real คือตำแหน่งที่ Dummy หันหน้า
    เช่น เลเวล 1 Dummy จะหันจุดแรกที่ 120, จุดที่สอง 240, จุดที่สาม 360 (ตาม Loop ใน int2)
    จากนั้นจะเซ็ทตำแหน่งโดย set udg_TempPoint ซึ่งจะเป็นจุดที่ยูนิตอยู่ ใ
    น action ต่อมานั้นผมจะให้ Dummy ร่ายเวทย์ตรงจุดที่ยูนิตอยู่ (TempPoint)
    หลังจากนั้นก็จะมีการให้ Expiration Timer กับ Dummy เพื่อให้ Dummy ตายอัตโนมัติ
    และลบ Location ออกโดย call RemoveLocation()
    เพื่อป้องกันการ Leak และท้ายที่สุด ห้ามลืม set int1 = int 1 + 1 นะครับ
    ไม่งั้น Loop จะรันจน Infinity ส่งผลให้เกมหยุดในทันที และอย่าลืม endloop
    เมื่อจบ Loop แล้วผมจะทำให้ u และ p เป็น null เพื่อล้าง memory ที่เราใช้

    *สังเกตใช่ไหมครับว่า ทำไมผมต้องตั้ง u ให้หายูนิตที่ร่ายเวทย์ตั้งแต่ตอนแรก
    ไม่อย่างนั้น ตอนหา int2 ตรง u จะไม่รู้ว่าเป็นยูนิตตัวไหน และจะทำให้ int2 เป็น 0
    Loop ก็ไม่ต้องเริ่มครับ จบเห่กันเลย*


    Challenge!
    ในตัวอย่างด้านบนเป็นเพียงการใช้ local บางส่วนเท่านั้น
    ในที่นี้เราสามารถเพิ่ม local อะไรอีกได้บ้าง?

    9. การใช้ Unit Group แบบ JASS
    นี่คือการใช้ Loop ใน Unit Group ถามว่าต่างกับ GUI ยังไง? ก็ไม่ต่างกันมาก แค่พื้นฐานตอนเรียกใช้ต่างกันเล็กน้อย
    และนี่อาจเป็นคำตอบที่ว่าทำไม Spell บางอย่างถึงรันได้แต่ใน JASS เท่านั้น และ Smooth กว่า

    ผมใช้ Raw Code ต่อไปนี้

    Spell - 'A00B' <<< จาก Channel
    Dummy - 'uaco' <<< Acolyte
    mass entangling roots - 'Aenw' <<< Entangling Roots แบบ Netural Hostile

    เอาล่ะมาเริ่มกันที่ Script ของ Overgrowth กันเลย

    function Ovgrowth takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(GetTriggerUnit()))==true and GetUnitAbilityLevel(GetFilterUnit(),'Avul')!=1==true
    endfunction
    function Trig_Overgrowth_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A00B'
    endfunction
    function Trig_Overgrowth_Actions takes nothing returns nothing
    local unit u = GetTriggerUnit() // GetTriggerUnit() is faster, thanks to Vexorian.
    local unit e
    local unit d
    local group g = CreateGroup()
    call GroupEnumUnitsInRange(g,GetUnitX(u),GetUnitY(u),1200,Condition(function Ovgrowth))
    set e = FirstOfGroup(g)
    loop
    exitwhen e==null
    set e = FirstOfGroup(g)
    call GroupRemoveUnit(g,e)
    set d = CreateUnit(GetOwningPlayer(u),'uaco',GetUnitX(e),GetUnitY(e),0)
    call IssueTargetOrder(d,"entanglingroots",e)
    call UnitApplyTimedLife(d,'BTLF',1.5)
    set d = null
    endloop
    call DestroyGroup(g)
    set u = null
    set g = null
    endfunction
    function InitTrig_Overgrowth takes nothing returns nothing
    set gg_trg_Overgrowth = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Overgrowth, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Overgrowth, Condition( function Trig_Overgrowth_Conditions ) )
    call TriggerAddAction( gg_trg_Overgrowth, function Trig_Overgrowth_Actions )
    endfunction
    Trigger นี้จะเริ่มทำงานก็ต่อเมื่อมียูนิตใช้ Ability ขึ้นมาซึ่งในที่นี้ก็คือ 'A00B'
    ต่อจากนี้จะมีการเรียกใช้ Local Variable ของ unit และ group
    ในตอนที่เรียกใช้ group อย่าลืมใช้คำสั่ง CreateGroup() เพื่อเป็นการสร้าง Unit Group ครับ

    call GroupEnumUnitsInRange(g,GetUnitX(u),GetUnitY(u),1200,Condition(function Ovgrowth))

    ทีนี้ผมอยากให้ดูคำสั่งนี้กันก่อน นี่เป็นการหายูนิตที่อยู่ในระยะ 1200 ของ u ซึ่งก็คือ Caster นั่นเอง
    ทีนี้ GetUnitX กับ GetUnitY คืออะไร? จำตอนทำ Camera ได้ไหม หลักการเดียวกันครับ
    แกนที่เป็นแนวตั้งกับแนวนอนนั่นแหละ ไม่ใช่แกน Z นะ (อธิบายยากนิด)
    ทีนี้การกลั่นกรองยูนิตที่จะไม่ใช้ออกนั้น ให้ดูที่ Condition Function ด้านบนสุด
    ซึ่งจะเป็นการเช็คว่ายูนิตตัวนี้เป็นศัตรูและเลเวล Invulnerable ไม่เท่ากับ 1 ใช่ไหม
    ถ้าเงื่อนไขถูกต้อง ยูนิตตัวนี้ก็จะถูกใส่เข้าไปใน Unit Group นั่นเอง

    จากนั้น function ต่อมาจะเป็นการหายูนิตตัวแรกของ Group ขึ้นมาซึ่งจะใช้ Variable e เป็นตัวแรกของกลุ่ม
    และใน Loop จะมีเงื่อนไขอีกที่ว่า exitwhen e==null ซึ่ง Loop นี้จะจบเมื่อ e มีค่าเท่ากับ null (ไม่มียูนิตแล้ว)
    FirstOfGroup คือการตั้งยูนิตตัวแรกของ Group นั้นๆ ไม่ใช่ RandomUnitFromGroup นะครับ
    เพราะผมใช้คำสั่งนี้ลองมาหลายทีละ ไม่มีทางเลยที่จะได้ครบทุกตัวใน UnitGroup
    ทีนี้คุณผู้อ่านอาจจะสังเกตได้ว่าทำไมผมต้องตั้ง set e = FirstOfGroup(g) เพราะไม่อย่างนั้น Loop ก็จบสิ้นกันเลย
    แต่มันยังมีวิธีการที่ว่าตั้งเงื่อนไขการจบ Loop เอาไว้ข้างท้าย ตรงนี้ยังไม่ได้ลอง จะลองกันดูก็ได้นะ ^^

    เมื่อ e (ยูนิตตัวแรก) ถูกเรียกใช้แล้วนั้น function ต่อมาจะเป็นการถอนยูนิตออกจากกลุ่ม call GroupRemoveUnit(g,e)
    และต่อมาจเป็นการเรียก Dummy เพื่อร่าย entanglingroots ใส่ e และใช้ Apply Timed Life เพื่อให้ Dummy ออกไปจากเกม
    อย่าลืม set ให้ d = null ด้วยนะ เพราะตัวต่อไปก็ต้องใช้เหมือนกันเมื่อ Loop แรกจบ Loop ต่อๆ ไปก็จะหายูนิตตัวแรกออกมา
    เรื่อยๆ จนกว่าจะหมดกลุ่มและ Loop ก็จะจบ

    สุดท้ายที่สุดอย่าลืมสั่ง DestroyGroup และ ตั้ง Variable ให้เป็น null เพื่อป้องกันอาการ Leak

    Challenge!
    แน่นอน อย่าคิดว่าผมจะให้ตัวอย่างที่สมบูรณ์เสมอไป ในที่นี้ตอนเช็คยูนิตที่จะใส่เข้า Group นั้น
    มีจุดโหว่ที่ว่า ไม่ได้เช็คยูนิตว่าเป็น Structure ตรงนี้จะทำยังไงเอ่ย?

    10. การใช้ Timer
    หัวข้อนี้ผมขอบอกไว้เลยครับ ว่า "สั้นมาก" เพราะถ้าคุณผู้อ่านเคยใช้ GUI อ่านปุ๊ปก็จะรู้เลยว่า
    ทำไม๊ ทำไม JASS ถึงทำอะไรหลายๆ อย่างได้ Smooth นัก

    ก่อนที่จะเริ่มเนื้อหา ผมอยากจะบอกอะไรซักอย่างหนึ่งเกี่ยวกับการ Wait หรือ TriggerSleepAction
    ทำไมต้องพูดถึงหัวข้อนี้ก่อน ง่ายมากครับ เพราะว่า Wait เมื่อใดก็ตามที่ต่ำกว่า 0.10
    เช่น 0.01, 0.02 จะถูกปัดขึ้นเป็น 0.10 ทันที ตรงนี้เราจะใช้ Timer เข้ามาแก้ตรงนี้
    ที่สำคัญ ถ้าเราไป Wait ใน Loop ในบางทีจะรันไม่ครบ เช่นจาก 1-10 อาจจะได้ 1-7 บ้าง 1-9 บ้าง

    ดังนั้นเรามาดูปัญหากันก่อน

    function Trig_Epicenter_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A003'
    endfunction
    function Trig_Epicenter_Actions takes nothing returns nothing
    local integer i1 = 1
    local integer i2 = 10
    local unit cast
    local unit dummy
    set cast = GetSpellAbilityUnit()
    loop
    exitwhen i1 > i2
    call PolledWait( 0.05 ) // This is a problem that we are going to fixed it.
    set dummy = CreateUnitAtLoc(GetOwningPlayer(cast), 'hpea', GetUnitLoc(cast), 0)
    call IssueImmediateOrderBJ( dummy, "stomp" )
    call UnitApplyTimedLifeBJ(1.50, 'BTLF', dummy)
    set i1 = i1 + 1
    endloop
    endfunction
    function InitTrig_Epicenter takes nothing returns nothing
    set gg_trg_Epicenter=CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Epicenter,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_Epicenter, Condition( function Trig_Epicenter_Conditions))
    call TriggerAddAction(gg_trg_Epicenter, function Trig_Epicenter_Actions)
    endfunction
    Script ด้านบน ดูยังไงก็เป็นไปได้ยากครับ ดังนั้น

    function Trig_Epicenter_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A003'
    endfunction
    function EpicenterEffect takes nothing returns nothing
    local unit d
    local location p=GetUnitLoc(udg_cast)
    set d=CreateUnitAtLoc(GetOwningPlayer(udg_cast), 'hpea', p, 0)
    call IssueImmediateOrder( d, "creepthunderclap" )
    call UnitApplyTimedLife(d,'BTLF', 1.5)
    call RemoveLocation(p)
    set p=null
    set d=null
    endfunction
    function Trig_Epicenter_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    set udg_cast = GetTriggerUnit()
    call TimerStart(t,0.05,true,function EpicenterEffect)
    call TriggerSleepAction(0.53)
    call PauseTimer(t)
    call DestroyTimer(t)
    set udg_cast=null
    set t=null
    endfunction
    function InitTrig_Epicenter takes nothing returns nothing
    set gg_trg_Epicenter = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Epicenter, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Epicenter, Condition( function Trig_Epicenter_Conditions ) )
    call TriggerAddAction( gg_trg_Epicenter, function Trig_Epicenter_Actions )
    endfunction
    ผมจะเจาะเรื่อง Timer เลยนะ
    ตอนแรกสุดเราจะมีการเรียกใช้ local timer
    และตั้ง Global Variable เป็น udg_cast = GetTriggerUnit() (เจ้าของที่ร่าย)
    ทีนี้มาดูการทำงานของ Timer ครับ ซึ่งจะเป็น Timer แบบ Repeating ใน GUI
    ทำได้โดยเขียน true ลงไปใน function ที่จะให้ Timer ทำงาน
    ถ้าจะเรียกให้ง่ายขึ้นกว่าเดิม ก็คือ Time - Periodic Event ใน Event นั่นเอง
    และเมื่อ Timer ทำงานเมื่อไหร่จะมีการเรียก function EpicenterEffect ขึ้นมา
    ถ้าดูจากตอนนี้เราจะให้ Timer ทำงานทั้งหมด 10 ครั้ง (Start ทุกๆ 0.05 วินาที) โดยแต่ละครั้งจะเรียก
    Dummy เพื่อใช้ Slam (ทำให้ยูนิต Slow พร้อม Damage)
    เมื่อ TriggerSleepAction จบลง Timer ก็จะถูกทำลาย Function EpicenterEffect ก็จะหยุดทำงานไปด้วย
    ตรงนี้ผมว่าเฉลยการทำ Spell บางอย่างที่ Effect มันไหลลื่นได้แล้วล่ะนะ
    ถ้า Knockback ผมแนะนำว่าให้ start ที่ 0.02 แล้วเคลื่อนยูนิตทีละ +10 จะ Smooth ที่สุดละ


    แต่วิธีนี้ จะมีจุดบกพร่องอยู่ตรงที่ถ้าเราใช้ Global Variable แล้วเราห้ามใช้ใน Trigger อื่นอีก
    เจอบั๊กแน่นอนครับ แต่ถ้าจะหลีกไม่ให้ใช้ Global ต้องพูดต่อกันในคราวหน้ากับเรื่อง Handle
    เป็นอะไรที่ Advance ขึ้นไปอีกขึ้นแต่จะทำอะไรได้สะดวกขึ้น รวมทั้งจะนับจำนวนครั้งที่ใช้ Epic ได้แม่นเป๊ะ

    เรื่องของ Timer ในตอนสุดท้ายนั้น ต้อง Pause Timer ก่อนที่จะทำลาย
    ถ้าทำลายไปก่อนจะ Pause มันมีโอกาสที่จะเป็น Timer อันเดิมครับ
    ง่ายๆ ว่ามันจะ Expire ใน Trigger หรือ Function อื่นๆ ได้


    11. บทสรุป และ คำคอมเมนต์
    นี่เป็นการให้ดูลักษณะการทำงานของ JASS เพื่อจุดประกายแนวทางเล็กน้อย
    ผมไม่เคยเรียน C มาก่อน เพิ่งจะเห็นหน้าค่าตาแบบจำลองมันครั้งแรกก็จากใน WE นี่แหละ
    ตอนแรกก็ลองผิดถูกจน WE มัน Crash ไปร่วมยี่สิบกว่ารอบกว่าจะเข็น JASS ให้ออกมาให้ได้ซักอัน
    ยังไงมันก็โปรแกรมสำเร็จรูปนี่เนอะ ลองเขียนเองอาจบรรลัยได้ - -"

    เรื่องการทำงานโดยรวมของ JASS เร็วกว่า GUI มาก ไม่ต้องอะไรเลย พิมพ์ๆๆๆ ก๊อปๆๆๆ เสร็จละ
    GUI ตั้งโน่นนี่กว่าจะได้ Spell ซักหนึ่งอันแสนนาน แต่ถ้ากลับกันเป็น Quest, Camera จะลำบากกว่า

    สำหรับ Jass Script จริงๆ นั้นขึ้นอยู่กับว่าใครจะถนัดโค้ดแบบไหน
    *โปรดทำใจก่อนเปิดนะ*
    ถ้าอยากเห็นแนวทาง >>> My Warcraft III spell book

    มาถึงตรงนี้ ผมขอพูดคำเดียวว่า
    ผมไม่ใช่คนเขียนโปรแกรม ดังนั้น ขอร้องว่า อย่าถามภาษาอื่น
    ผมตอบไม่ได้ครับ

    Edit: Update Tutorial 1.9

  2. #2
    sirtan

    Re: Introduction to JASS

    สุดยอด !

  3. #3
    black rock

    Re: Introduction to JASS

    กระจ่างแจ้งแล้ว



    เมื่อก่อนข้าน้อยเดาเอา - -'

  4. #4
    Fenix

    Re: Introduction to JASS

    Quote Originally Posted by sirtan
    สุดยอด !
    แหม ไม่ถึงขนาดนั้นหรอกครับ ^^';
    เพราะผมเองก็ศึกษา WE นานเหมือนกัน

    Quote Originally Posted by Black rock ออคหินสีดำ
    กระจ่างแจ้งแล้ว

    เมื่อก่อนข้าน้อยเดาเอา - -'
    ขนาดนั้นเลยเหรอครับ ^^
    ดีใจครับที่เป็นแนวทางให้กับคุณได้

  5. #5
    Niagin

    Re: Introduction to JASS

    เข้าใจขึ้นเยอะ ถ้ามีตัวอย่าง Spell ของ GUI เทียบกับ Jass นี่แจ๋วเลย

  6. #6
    Fenix

    Re: Introduction to JASS

    แนะนำคุณ Niagin ครับ
    ถ้าอยากศึกษา JASS เพิ่มลองใช้ Covert to Custom Text ใน Trigger
    ง่ายๆ ว่าเปลี่ยนไปเปลี่ยนกลับ จาก GUI >>> JASS แล้ว Undo จาก JASS >>> GUI
    ลองไปซักระยะ เดี๋ยวมันก็เก็ทเอง

    ประเด็นอยู่ที่ต้องจำคำสั่งให้หมดน่ะ หลักๆ อยู่ตรงนี้เอง
    อ้อ อย่าลืม Back Up Map เอาไว้ด้วยนะ ^^

  7. #7
    NongNoobJung

    Re: JASS Tutorial 1.7

    จะว่ารู้เรื่องก็รู้ จะว่าไม่รู้ก็ไม่รู้ :015:

  8. #8
    Fenix

    Re: JASS Tutorial 1.7

    Quote Originally Posted by NongNoobJung
    จะว่ารู้เรื่องก็รู้ จะว่าไม่รู้ก็ไม่รู้ :015:
    ฟีลลิ่งในทางนี้ดีแล้วล่ะ ' '/
    ลองเขียนซักแป๊ปก็เป็นเอง - -+

  9. #9
    [SIN]_BlooD

    Re: JASS Tutorial 1.7

    ขอการกำหนดตัวแปรหน่อยได้ไหมคับ

    แบบว่า local .... a อารายแบบเนี้ย

    เขียนไม่ค่อยถูกเลย

  10. #10
    Fenix

    Re: JASS Tutorial 1.7

    ขอมาก็จัดให้

    http://www.hiveworkshop.com/forums/f...riables-34049/

    มันยาวน่ะ เลยขี้เกียจเอามาลง ^^'
    แนะนำว่าลองเปิด Source Code ตามกระทู้นี้ออกมาเลยดีกว่านะ มี List อยู่

    http://www.thaicybergames.com/webboa...c,80230.0.html

    อาจจะได้เห็นฟังก์ชั่นประหลาดๆ หรือการใช้ WE แบบแปลกๆ ที่ไม่เคยเห็นใน GUI เลยก็ได้

Facebook Comments


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •