[Tutorial] GUI to JASS

Thảo luận trong 'World Editor' bắt đầu bởi Tom_Kazansky, 2/8/08.

  1. Tom_Kazansky

    Tom_Kazansky

    Tham gia ngày:
    28/12/06
    Bài viết:
    3,454
    Nơi ở:
    Hà Nội
    I.Giới thiệu

    Hầu hết mọi người đều nghe về JASS và đó là một cách hiệu quả để “phát triển” spell (develop, hay cứ hiểu là “tạo” spell đi :D). Nhưng câu hỏi thực sự là JASS là gì và làm thế nào để học JASS ? Giống như những ngôn ngữ lập trình (NNLT) khác, JASS là một ngôn ngữ đc phát triển bởi Blizzard. Nếu bạn có hiểu biến cơ bản về C++, Pascal hay những NNLT “ko hướng đối tượng” khác (thật ra tôi cũng chả hiểu gì về “ko hướng đối tượng” nhg chắc chắn Java là một NNLT “hướng đối tượng”, tôi đã đọc qua sách Java nên biết vậy) thì tutorial này dễ như ăn bánh vậy. Nếu bạn ko biết gì thì bạn sẽ gặp một chút khó khăn nhưng đến lúc hết tutorial này thì bạn sẽ hiểu. Trước khi tiếp tục đọc tutorial này, tôi nghĩ các bạn trước hết nên học qua trigger. Tutorial này sẽ dựa vào hiểu biết về trigger, vì vậy nếu ko biết về trigger, bạn sẽ ko hiểu nhiều đâu.
    Note: Custom Script (trong GUI) là một tên khác của JASS
    Note2: Trước khi tiếp tục, các bạn hãy download tool Jass Craft này. (hay http://forum.gamevn.com/attachment.php?attachmentid=114891&d=1261033125 )

    II.Tại sao lại là JASS ?

    Đơn giản là, bởi vì trước hết, JASS có thể giúp bạn “multi-instanceable spells” (MUI spell ấy, dịch khó quá, hic :| ). “Multi-instanceable” là cách duy nhất mà có thể giúp bạn trong trường hợp bạn muốn tạo spell bình thường và bạn ko thể làm nó “Multi-instanceable” với GUI. “Multi-instanceable” (đc giải thích) là khả năng của một spell có thể đc “cast” nhiều hơn một unit của cùng một player trong cùng một thời gian. Một ích lợi khác của JASS là tốc độ. Một khi đã hiểu JASS, bạn có thể tạo spell nhanh hơn, và nói thật là, chúng sẽ “hiệu quả” cao hơn (efficent trong câu này thật khó dịch ghê). Và một thứ khác tôi nhận ra về JASS là làm sao để tạo đc những effect rất hay (awesome = ?) như smooth Knockback (knockback “mượt” chăng ? theo tôi là knockback mà ko thấy “giật” ). “Tin tôi đi, tôi đã thử với GUI nhưng bạn sẽ ko bao giờ có đc những “awesome effect” bạn sẽ có với JASS” <= câu này của Daelin, nhg quả thực là như vậy.
    Có lúc bạn ko nên dùng JASS bởi vì bạn ko cần phải dùng JASS với mọi thứ. Tạo campaign khi mà bạn tạo quest(các nhiệm vụ), cinematic ( chiếu “phim”) và các thứ khác mà có thể tạo dễ dàng bằng GUI. Bạn không cần thiết phải dùng JASS vì nhiêu khi nó có thể có “lỗi”.
    Vậy lúc nào thì dùng JASS ? tôi hoàn toàn khuyên bạn dùng nó để tạo spell. Nếu bạn muốn tạo các “cool minigame” trong map và tất nhiên là các “cool effect”. Bạn sẽ thoải mái hơn và ko còn bị hạn chế bởi những thao tác lặp đi lặp lại. Nếu chưa đủ thì cứ thử, các bạn sẽ ko tiếc đâu.

    III.Function – Hàm

    Function (hàm) là một phần của chương trình (script = ?) mà thực hiện một số thao tác, một thao tác sau một thao tác khác (các hàm này có thể đc tạo để thực hiện một số lượng vô hạn các thao tác nêu bạn muốn, nhưng như vậy script của bạn sẽ ko đúng nữa). Tưởng tượng như các thao tác của một hàm, các action của lệnh “Pick Up Every Unit In Group and do Actions”,...
    Để tạo một hàm, bạn phải theo cấu trúc sau:

    Mã:
    function name takes nothing returns nothing
    //các thao tác đc thực hiện bởi hàm này
    endfunction
    Giải thích: các từ function và endfunction mở hàm và đóng hàm. Bạn có thể đặt tên của hàm thay cho name. Tên hàm có thể là các kí tự như chữ, số, gạch nối nhưng ko đc trùng với từ khóa, ko đc có khoảng trống (“cách” – space). Ví dụ “Oz02” là tên đúng, “Oz 02” là sai. Tuy nhiên, tất cả các hàm phải có một tên riêng. Nếu hai hàm có cùng tên thì bạn sẽ gặp lỗi.
    Note: trong JASS, phần chú thích đc viết sau “//”.
    Giữa function và endfunction bạn có thể để các thao tác bạn muốn hàm đó thực hiện. Hãy nghĩ đó là các lệnh trong phần action của trigger.
    Note: Bạn có thể tạo bao nhiêu hàm cũng đc nhg hãy nhớ là ko đc tạo hàm trong hàm khác. Ví dụ:

    Mã:
    function sleep takes nothing returns nothing
        function entangle takes nothing returns nothing
        endfunction
    endfunction
    script đó sai, sript đúng phải như sau:

    Mã:
    function sleep takes nothing returns nothing
    endfunction
    
    function entangle takes nothing returns nothing
    endfunction
    IV. Executing a function – thực hiện một hàm

    Các hàm có thể đc thực hiện như trigger. Nhưng ko như trigger, chúng phải đc gọi trực tiếp và sẽ ko thực hiện nếu một “event” nào đó xảy ra. Coi như chúng là một trigger đc tắt đi sẵn và chỉ có thể đc thực hiện qua lệnh:
    Run Trigger Ignoring Conditions
    Có vài hàm như trên nhưng tôi sẽ đề cập sau.
    Để gọi một function, bạn phải dùng cấu trúc sau:

    Mã:
    call FUNCTION_NAME
    FUNCTION_NAME là tên của hàm.
    Một hàm có thể gọi hàm khác nhưng hàm đc gọi phải “xuất hiện” trước hàm mà gọi function đó.
    Script sau sai:

    Mã:
    function sleep takes nothing returns nothing
        call entangle
    endfunction
    function entangle takes nothing returns nothing
    endfunction
    script sau đúng:

    Mã:
    function entangle takes nothing returns nothing
    endfunction
    function sleep takes nothing returns nothing
        call entangle
    endfunction
    Các lệnh mà bạn thực hiện trong trigger cũng là các hàm. Chúng đã đc tạo từ trước và có thể đc gọi trực tiếp.
    Ví dụ: hàm “TriggerSleepAction” là hàm đợi nhg nó sẽ đc giải thích sau.
    Note: tên hàm của bạn cũng ko đc trùng với tên của các hàm này. Tất cả các hàm đc tạo sẵn này đều có trong Jass Craft.

    V. Variables – Biến

    Biến là “labeled place” trong bộ nhớ mà lưu một giá trị nào đó. Vì có “label” nên chúng có tên như hàm. Như trong GUI, chúng có nhiều kiểu. Những kiểu quan trọng nhất là:
    Mã:
    lightning – lightning in gui 
    location – point in gui
    unit – unit in gui
    effect – special effect in gui
    item – item in gui
    doodad – destructable in gui
    timer – timer in gui
    group – unit group in gui
    trigger – triggers themselves in gui (cái này sẽ nói sau)
    integer – integer in gui
    real – real in gui
    boolean – boolean in gui
    force – player groups in gui
    Có hai kiểu biến:
    a) Local variables (biến cục bộ) là các biến đc khai báo trong hàm mà chỉ đc sử dụng trong hàm đó. Để khai báo thì ta phải theo cú pháp sau:

    Mã:
    Local <VARIABLE_TYPE> <VARIABLE_NAME>
    Type là kiểu của biến và Name là tên biến. Trong cùng một hàm ko thể có nhiều hơn một biến local có cùng tên. Có thể có hai hay nhiều biến local có cùng tên, nhưng chúng phải ở các function khác nhau :P
    Ví dụ

    Mã:
    function sleep takes nothing returns nothing
        Local unit cast
    endfunction
    function entangle takes nothing returns nothing
        Local timer cast
    endfunction
    function arrow takes nothing returns nothing
        Local unit cast
    endfunction
    Để chỉ (refer – tôi dịch là “chỉ” mà có thể là... ?) các biến local, chỉ cần dùng tên của chúng.
    Warning: các biến local phải đc khai báo ở đầu các hàm trước mọi thao tác khác đc thực hiện. Ví dụ về script sai:

    Mã:
    function entangle takes nothing returns nothing
    endfunction
    function sleep takes nothing returns nothing
         local trigger t
         call entangle()
         local unit cast
    endfunction
    b) Global variables (biền toàn cục) – GUI variable, tạo như bạn tạo khi sử dụng GUI. Để chỉ biến global (trong JASS) dùng cấu trúc “udg_name” trong đó name là tên của biến global.
    Note: biến local và global có thể có cùng tên. Chúng sẽ ko bị nhầm bởi vì global có “udg_” đằng trước và local thì ko.
    Có một kiểu biến đặc biệt gọi là handlers. Kiểu này tồn tại và chỉ có thể đc sử dụng như local. Nó có thể chứa mọi kiểu biến. (nhưng thôi, đừng quan tâm, Tom cũng ít dùng :P )

    VI. Decisional and Repetitive structure – cấu trúc điều kiện và lặp

    a) Decisional structure - Cấu trúc điều kiện
    Cấu trúc điều kiện trong JASS khá đơn giản. Nó theo cấu trúc sau:
    Mã:
      if <điều kiện> then
         //các thao tác của then
      else
         //các thao tác của else
      endif
    Note: điều kiện đc đặt trong <> để nhìn dễ hơn. Script thì ko nên có <>
    Ok, điều kiện ở đây phải là điều kiện so sánh. Nếu sự so sánh này mà đúng thì “thao tác của then” sẽ đc thực hiện. Nếu ko thì “thao tác của else” sẽ đc thực hiện. Tuy nhiên, else ko phỉa là lệnh. Nếu bạn ko đặt else thì cũng ko nên đặt “thao tác của else”. Và đừng quên endif bởi vì nó đóng if.

    b) Repetitive structure – Cấu trúc lặp
    Trong JASS, ta chỉ có cấu trúc while trong C++ và Pascal, nó đc gọi là loop. Nó có cấu trúc như sau:
    Mã:
    loop
        exitwhen <condition>
        //các thao tác
    endloop
    Warning: nếu bạn quên cái endloop và bạn ko đóng loop này, WE sẽ “crash” và bạn sẽ mất hết các dữ liệu chưa save trước khi làm việc với loop. Vậy hãy chắc chắn là bạn đóng mỗi cái loop mà bạn mở.
    Và bạn nên dùng NewGen(http://tools.assembla.com/svn/war3tools/luadriven/releases/jassnewgenpack5a.7z) vì nếu bạn quên đóng loop, WE sẽ ko crash.
    Cũng ko có gì để nói về loop. Loop này sẽ làm cho các thao tác lặp lại đến khi điều kiện so sánh sau exitwhen cho giá trị đúng (true). Cho tới lúc đó, nó sẽ thực hiện các thao tác lặp đi lặp lại. (và nếu điều kiện kia ko bao giờ cho giá trị đùng, loop này sẽ lặp đi lặp lại đến vô cùng và trong hầu hết các trường hợp thì War3 sẽ... crash, ko báo lỗi)

    VII. Functions which take and/or return a value – các hàm lấy và/hoặc trả giá trị

    Tới lúc này ta mới nói về các hàm ko lấy và trả về giá trị nào. Những hàm như vậy tồn tại ít và thường đc dùng trong trigger (đúng là ta sẽ dùng trigger trong JASS nhưng “phương pháp trigger” sẽ đc đề cập sau, vì vậy bây giờ các bạn cứ hiểu là trigger trong GUI)
    Ở phần trước, tôi đã nói với các bạn là cấu trúc của một hàm bắt đầu bằng “function name takes nothing returns nothing”. Thật ra, hai cái nothing có thể đặt một hoặc nhiều kiểu biến (và sau đó là là tên biến). Biến sau “takes” là giá trị mà đc hàm đó lấy đi khi ta gọi nó, trong khi đó thì giá trị sau “returns” là giá trị mà hàm trả về.

    Note: hàm chỉ có thể trả một giá trị (nó có thể takes nhiều, max là 32, nhưng chỉ trả 1 thôi)

    Hãy lấy một ví dụ về hàm mà có “takes”. Trong hàm này, ta sẽ làm việc với vài biến. Tôi sẽ đưa ra một hàm đc tạo từ trước( những hàm này là các action trong GUI) và các bạn sẽ thấy cách gọi một hàm mà takes giá trị.
    Mã:
    function remove takes unit cast, real wait returns nothing
        call TriggerSleepAction(wait)
        call RemoveUnit(cast)
    endfunction
    
    function start takes nothing returns nothing
        call remove(udg_caster, 1.00)
    endfunction
    
    Hãy phân tích script này một chút. Hàm start gọi hàm remove và nó bao gồm hai tham số. Như bạn thấy, các tham số này đc đặt trong ngoặc đơn và đc tách ra bởi một dấu phẩy. Tham số thứ nhất là một biến global và tham số kia là một hằng số thực.

    Note: hằng số là giá trị mà giá trị của chúng ko bao giờ đổi (cho dù trời có sập :D )

    Hãy xem hàm thứ 2(hàm remove). Bạn thấy rằng cast và wait ko còn cần đc đề cập như là biến local nữa. Bởi vì tất cả các giá trị đc “takes” đã đc coi như là local. Và hãy xem hàm đc tạo sẵn.
    Như tôi đã đề cập từ trước, call TriggerSleepAction là đợi (wait), và nó làm cho hàm này đợi một thời gian (tính bằng giây) trước khi tiếp tục các thao tác khác. Giá trị trong ngoặc đơn (một tham số) là số giây mà bạn cần TriggerSleepAction đợi.

    Note: tham số tối thiểu cho việc đợi này là 0.10. Nếu bạn đặt thấp hơn, nó sẽ tự động đc đặt là 0.10 khi hàm đc thực hiện.

    Hàm thứ 2 “RemoveUnit” là lệnh “Unit – Remove Unit” trong GUI và như trên, nó remove (dịch là gì bây giờ ) tham số unit. Trong trường hợp này, nó remove unit đc chứa trong biến local “cast”

    Warning: khi bạn gọi một hàm mà hàm đó lấy một giá trị, bạn phải đề cập đến tất cả các tham số và các tham số này phải cùng kiểu với giá trị đc đưa vào khi hàm đc gọi (giá trị mà hàm “takes”, mình đưa cho nó ấy :’>)

    Trong trường hợp trả lại một giá trị thì giá trị đc trả (return) sẽ đc đặt trong biểu thức logic hoặc đc lưu vào một biến (nguyên văn là “stored into an integer” nhg Tom thấy hơi lạ, sao lại “lưu vào một số nguyên” có thế Daelin viết nhầm hoặc, thật ra các giá trị đều có thể quy ra integer hêt -> nói chung là Tom ko hiểu câu này lắm :|)
    Mã:
    function condition takes unit cast returns boolean
    return IsUnitDeadBJ(cast)
    endfunction
    
    function start takes nothing returns nothing
    call TriggerSleepAction(0.01)
    if condition(udg_caster)==true then
    call RemoveUnit(udg_caster)
    endif
    endfunction
    
    Khi hàm start chạy, nó đợi 0.10 giây (bởi vì giá trị dưới 0.10 trong TriggerSleepAction sẽ lấy 0.10) và sau đó nó gọi hàm condition. Khi mà hàm lấy một giá trị, “call” cũng có một tham số.

    Hàm thứ 2 trả kiểu boolean. Điều này có nghĩa là nó trả một giá trị boolean (đúng hoặc sai). Nó sử dụng hàm IsUnitDeadBJ(cast) để kiểm tra một unit nào đó chết hay ko. Nếu unit đó chết thì hàm này sẽ trả “true”, nếu ko thì trả “false”. Hơn nữa, hàm trả giá trị mà gía trị này đc trả bởi chính hàm IsUnitDeadBJ. Nghe có vẻ phức tạp, hãy tưởng tượng thay vì hàm đó, có một giá trị boolean, giá trị này thật ra đc trả bởi hàm đó.

    Và ta có giá trị trả lại sau “return”. Cái này bắt hàm trả một giá trị. Hãy nhớ là giá trị sau “return” phải cùng loại với giá trị mà hàm sẽ trả (returns… )

    Chú ý: khi một hàm đã trả một giá trị, nó sẽ dừng tất cả các thao tác sau đó. (Skip remaining actions)

    Note: Bạn ko nên dùng call trước hàm mà bạn trực tiếp sử dụng giá trị đc trả lại từ hàm đó. Nếu bạn ko muốn sử dụng giá trị đc trả lại bởi function thì bạn có thể dùng call.

    Warning: khi một hàm phải trả lại một giá trị, bạn phải chắc chắn là nó sẽ trả giá trị đó. Nếu ko, điều này sẽ làm WE crash (lại một lí do nữa để dùng NewGen ). Có vài trường hợp lạ mà trong đó, cho dù hàm đó luôn trả một giá trị, bạn vẫn gặp lỗi. Hãy xem ví dụ sau:

    Mã:
    function bool takes nothing returns boolean
    if udg_a==true then
    return true
    else
    return false
    endif
    endfunction
    Thật lạ là WE ko cho rằng giá trị đó lúc nào cũng đc trả. Nó sẽ crash. Vậy bạn phải chắc chắn rằng bạn có một giá trị trả lại ở ngoài FORs, Ifs và vài cấu trúc khác. Điều này sẽ ngăn crash. Cái bẫy IF rất hay gặp và rất… khó chịu. Vậy hãy cẩn thận.

    VIII. Working with variables – làm việc với biến

    Chương này cũng rất quan trọng bởi vì ta sẽ học làm sao để cho biến một giá trị. Có ví dụ sau:

    Mã:
    function entangle takes nothing returns nothing
        local unit cast
        set cast = GetTriggerUnit()
        call RemoveUnit(cast)
    endfunction
    Hàm này thực hiện công việc gì ? ta lấy một biến local có tênlà cast. Bây giờ ta cho biến đó một giá trị bằng cách gõ “set”, tạo ra một khoảng trống, sau đó là tên của biến. Sau đó ta có một dấu “=”, và sau đó là cái mà ta cho biến. Trong trường hợp này, ta cho nó giá trị của hàm GetTriggerUnit(). Hàm này cũng là một hàm đc tạo từ trước và nó trả Trigger Unit (GUI). Sau đó ta remove unit cast.

    Tôi viết vài hàm đc tạo sẵn, hoạt động tốt cho biến unit.

    Mã:
    GetTriggerUnit() = Triggering Unit
    GetEventDamageSource() = Damage Source
    GetLastCreatedUnit() = Last Created Unit
    GetAttackedUnitBJ() = Attacked Unit
    GetAttacker() = Attacking Unit
    GetSpellAbilityUnit() = Casting Unit
    GetDyingUnit() = Dying Unit
    GetEnteringUnit() = Entering Unit
    GetManipulatingUnit() = Hero Manipulating Unit
    GetKillingUnitBJ() = Killing Unit
    GetOrderedUnit() = Ordered Unit
    GetSummonedUnit() = Summoned Unit
    GetSummoningUnit() = Summoning Unit
    GetSpellTargetUnit() = Target Unit of Ability Being Cast
    GetOrderTargetUnit() = Target Unit of Issued Order
    Note: GetSpellTargetUnit(), GetOrderTargetUnit() và các hàm như trên sẽ ko trả giá trị sau một TriggerSleepAction cho dù wait này có nhỏ. Ngoại trừ GetTriggerUnit(). Đó là lý do vì sao tôi khuyên bạn nên lưu tất cả unit vào biến local.

    IX. Trigger
    – (là cái gì chắc các bạn cũng biết rồi :)) )

    Ok, cho tới bây giờ, ta chưa làm việc với JASS script thật nào, mới chỉ làm việc với các phần nhỏ. Nếu bạn muốn thử các script này, chắc chắn bạn sẽ gặp một “sê ri” lỗi hoặc ít nhất là nó sẽ ko hoạt động. Đó là bởi vì các hàm của bạn phải đc “trigger” (dịch là gì đây :(( ) bởi một thứ nào đó. Và mhư vậy, “trigger” tồn tại trong JASS như tôi đã nói. Có 2 loại trigger: Global trigger (cái mà ta thấy trong GUI) và Local trigger. Vậy trước tiên, ta sẽ nói về và tạo JASS script đầu tiên của bạn.

    a) Global Triggers

    Đầu tiên (của cái đầu tiên). Tạo một GUI trigger. Cho nó một event, “An Unit Starts the Effect of an Ability” và điều kiện là “Ability Being Cast Equal to Sleep”.

    Bây giờ, bạn sẽ thấy JASS có thể đơn giản thế nào. Vào Edit – Convert to Custom Text và click “Ok”. Custom Script sẽ hiện ra ở bên phải (phần bạn hay viết các lệnh ấy). Hãy xem:

    Mã:
    function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
        if ( not ( GetSpellAbilityId() == 'AUsl' ) ) then
            return false
        endif
        return true
    endfunction
    
    function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
    endfunction
    
    //===========================================================================
    function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
        set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) )
        call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
    endfunction
    Nó nhìn thật kinh khủng và có thể bạn sẽ ko hiểu nhiều thứ. Vậy hãy cùng xem nó. Nó bắt đầu với một hàm chứa điều kiện, hàm này trả một boolean. Nó dùng một hàm GetSpellAbilityId(), hàm này dùng để lấy “raw code” của ability đang đc cast. Hàm này trả lại một “raw code” (là integer) vì thế hình dung nó là một “raw code”. Bây giờ nếu rawcode đc trả bằng ‘AUsl’ vậy hàm này (hàm điều kiện) sẽ trả một giá trị false và sẽ ngừng tất cả các thao tác còn lại. Nhưng nghe có vẻ ko đúng, phải vậy ko ? Nhưng nhìn vào “not” ở trước phép bằng. Thật ra nó đã phủ nhận giá trị của phép bằng này. Vậy nếu phép bằng này là đúng, nó sẽ trả sai. Ko hiệu quả lắm nhưng tôi sẽ cho bạn biết cách làm nó dễ hơn. Blizzard thích làm mọi thứ phức tạp (đúng ko nhỉ ?)

    Quay lại với hàm, nếu nó ko trả giá trị false, hàm sẽ tiếp tục, nó sẽ ra khỏi if và trả giá trị true.

    Hàm thứ 2 đại diện cho action của trigger. Bây giờ nó ko có gì vì bạn chưa cho một thao tác nào vào cả. Ta sẽ nói về phần này sau.

    Và tiếp theo là một phần chú thích lạ. Nó chia các hàm với global trigger. Hàm ở phía dưới giống như một “trigger”. Nó đc kích hoạt ở “map initialization”. Nó phải có cùng tên với trigger. Trước đây, tôi chưa bao giờ “đụng” vào những hàm này vậy bạn ko phải lo lắng về chúng bây giờ.

    Phía dưới bạn sẽ thấy một global trigger đc tạo. Chú ý là tất cả trigger phải đc tạo ra trước khi chúng có thể đc sử dụng. Trước đó chúng tồn tại dưới dạng biến nhưng ko đc sử dụng. Timer cũng vậy.

    Bây giờ ta có 3 hàm đáng chú ý, những hàm này sẽ đc dùng nhiều. Đầu tiên là “đăng kí” (hay tạo) event. Hàm thứ 2 là tạo điều kiện. Thứ 3 là tạo action. Chúng sẽ hơi phức tạp nên ta sẽ đi từng cái. Nào, hãy mở Jass Craft ra.

    1.Function registering events – hàm tạo event

    Đúng là rất nhiều event nhưng khi bạn đã học cách làm việc với chúng, chúng sẽ rất đơn giản. Tất cả các event đều bắt đầu với cú pháp:

    Mã:
    TriggerRegister
    Tiếp theo cú pháp là các từ khóa khác nhau. Để xem các event tồn tại, gõ vào Jass Craft “TriggerRegister” (cái thanh công cụ bên tay phải ấy, có cái dòng search). Để dễ dàng hơn, nếu bạn vẫn chưa hiểu event nào là event nào, hãy tạo 1 GUI trigger, đặt event bạn muốn và sau đó đổi GUI trigger này sang JASS. Vậy bạn sẽ thấy event này trong JASS.

    Note: bạn có thể register nhiều event nếu bạn muốn. Ví dụ:

    Mã:
    function maim takes nothing returns nothing
    set gg_trg_t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( gg_trg_t, EVENT_PLAYER_UNIT_DEATH )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    Trong trigger trên, ta có 2 event: một là khi một unit chết và hai là khi một unit start effect của 1 ability. Thường thì tất cả hàm khởi tạo event có tham số đầu tiên là tên của trigger. Sau đó nó phụ thuộc vào event. Tôi thực sự ko thể nói nhiều về chương này (Tom cũng thế ). Bạn cứ thử bằng cách tạo một trigger trong GUI rồi đổi ra JASS.

    2.Functions registering condition – Hàm tạo điều kiện (cho trigger)

    Những hàm này khá đơn giản hơn và dễ giải thích hơn. Ta đã nói về “những hàm lấy và/hoặc trả giá trị” (nếu chưa rõ thì bạn hãy xem lại)

    Bây giờ, cấu trúc để thêm một điều kiện chỉ có một:

    Mã:
    call TriggerAddCondition(trigger_name, Condition (function name))
    Thay trigger_name bằng tên trigger dùng condition này. Thay name bằng tên của hàm mà kiểm tra vài điều kiện.

    Warning: trong trường hợp này, hàm mà dùng để làm điều kiện ko nên lấy bất kì giá trị (tham số) nào. Đó là lý do vì sao bạn ko thể chuyển biến local từ trigger này sang trigger khác, ít nhất là bây giờ chưa đc.
    Note: hàm điều kiện PHẢI trả một giá trị boolean. Nếu ko thì bạn sẽ gặp lỗi.

    Và bây giờ tôi sẽ cho bạn thấy làm thế nào để có đc hàm điều kiện thật hiệu quả, ko như hàm mà Blizzard đã dùng. Ta sẽ lấy một trigger, hoạt động khi một unit cast một ability.

    Mã:
    function Cond takes nothing returns boolean
    return GetSpellAbilityId() == ‘A000’
    endfunction
    
    function trigger takes nothing returns nothing
    set gg_trg_t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( gg_trg_t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_t, Condition(function Cond))
    endfunction
    
    Giải thích ra thì đơn giản. Hàm đầu tiên trả giá trị từ phép so sánh bằng GetSpellAbilityId() == ‘A000’. Hàm thứ 2 tạo một global trigger và thêm event, condition.

    Warning: bạn chỉ có thể “add” một điều kiện. Nhưng bạn có thể “thao tác” nó thành các hàm tốt hơn.

    3.Function registering Actions – hàm tạo action

    Đây là chương ngắn nhất và dễ nhất. Nó hoạt động như điều kiện nhưng khác một chút về cú pháp. 

    Mã:
    call TriggerAddAction(trigger_name, function name)
    Vậy là bạn ko cần Condition trước function name nữa. Đó là tất cả. Và bây giờ tôi sẽ lấy một trigger phức tạp một chút, đơn giản là giết mục tiêu khi cast spell.

    Mã:
    function Cond takes nothing returns boolean
    return GetSpellAbilityId() == ‘A000’
    endfunction
    
    function Act takes nothing returns nothing
    local unit targ
    set targ = GetSpellTargetUnit()
    call KillUnit(targ)
    endfunction
    
    function main takes nothing returns nothing
    set gg_trg_t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_t, Condition(function Cond))
    call TriggerAddAction(gg_trg_t, function Act)
    endfunction
    Note: bạn chỉ có thể add một action. Hơn nữa, hàm này ko đc take hay return giá trị nào.

    b) Local Triggers

    Có những trigger mà có thể đc tạo vàkhai báo trong hàm. Theo cách này, chúng có thể chiếm chỗ đối với vài unit và bạn có thể tạo multi-instance bằng cách dùng locals. Local trigger họat động như globals, nhưng cũng có vài khác biệt nhỏ.

    Mã:
    function Remove takes nothing returns nothing
    call RemoveUnit(GetTriggerUnit())
    endfunction
    
    function act takes nothing returns nothing
    local trigger t
    set t = CreateTrigger()
    call TriggerRegisterUnitEvent(t, GetTriggerUnit(), EVENT_UNIT_SPELL_ENDCAST)
    call TriggerAddAction(t, function Remove)
    endfunction
    Ko quá phức tạp. Hàm thứ 2 có một local trigger. Nó hoạt động khi Trigger Unit của hàm đó dừng cast một ability. Action của trigger là hàm Remove mà nó remove Trigger Unit của hàm đó mà unit này là unit ở hàm thứ 2 (act) mà stop cast ability.

    X. Variable Leaking – leak biến /:)

    Một bất lợi của biến (unit, doodads, items, trigger hay bất kì các loại khác trừ giá trị số như integer, real) là chúng sẽ “leak”. Điều này có nghĩa là gì ? Đó là sau khi đc dùng, nếu nó ko đc xóa khỏi bộ nhớ một cách chính xác, nó sẽ ở đó và sẽ làm tốn tài nguyên của bạn (chính xác của PC). Quá nhiều JASS trigger có thể làm map lag kinh khủng, điều mà các “lập trình viên” đều ko muốn. Vậy làm thế nào để ngăn ngừa “leak” ?

    a)Hủy biến sau khi đã sử dụng chúng.

    Tiêu đề ko đc… (suggestive title là gì ::( ) Ok, nói chung là lấy biến ra khỏi bộ nhớ bằng cách đặt giá trị của nó thành giá trị mà nó đã đc khởi tạo trước khi trigger đc thực hiện. Giá trị đó đc gọi là “null” và đơn giản nó có nghĩa là biến đó trống, ko chứa gì cả. Ví dụ:

    Mã:
    function Trap takes nothing returns nothing
    local unit cast
    set cast = GetTriggerUnit()
    call TriggerSleepAction(1.50)
    call RemoveUnit(cast)
    set cast = null
    endfunction
    Ở đầu hàm nơi ta khai báo biến qua cú pháp “local unit cast”, biến cast sẽ nhận giá trị “null”. Điều này có nghĩa là nó ko lấy một chút bộ nhớ nào. Tuy nhiên, ta cho nó một giá trị (TriggeringUnit), ta đã lưu một unit nào đó vào bộ nhớ dưới một “tiêu đề” là tên của biến đó. Sau khi đợi, ta remove unit đó. Tuy nhiên, trong bộ nhớ vẫn chứa gì đó, mặc dù unit đã bị remove. Và vậy, ta cần phải đặt giá trị của biến thành giá trị khởi tạo null

    Tôi ko chắc là nếu bạn remove unit, nó vẫn leak nhưng một cái rõ ràng là: nếu bạn muốn sử dụng “Target Unit of Ability Being Cast”, và dùng qua waits, lựa chọn duy nhất của bạn là local. Nếu bạn ko thực sự remove Target Unit of Ability Being Cast, một điều rõ ràng là nó sẽ còn ở trong bộ nhớ, đc lưu qua một biến. Và cách duy nhất để xóa nó khỏi bộ nhớ là hủy biến

    Note: bạn ko cần phải hủy biến integer và real, mặc dù bạn muôn. Bởi vì chúng ko leak.

    b)Destroying timer, effects, lightning and trigger

    Trong nhiều trường hợp, chỉ đơn giản hủy biến thì ko đủ. Đây là trường hợpk của timer, special effect, lightning và trigger. Trong trường hợp này, bạn sẽ phải “destroy” chúng trước khi bạn có thể remove chúng. Tại sao lại như vậy ? Bởi vì, ví dụ, trigger còn hơn là một đối tượng (object). Chúng hoạt động dựa vào các thao tác và chúng ko hoạt động dựa vào chính chúng như các đối tượng khác. (Đoạn này khó hiểu nhỉ :| ) Vậy ta phải destroy những cái này thế nào ?

    Mã:
    Timers – call DestroyTimer(name)
    Effects – call DestroyEffect(name)
    Lightning – call DestroyLightning(name)
    Triggers – call DestroyTrigger(name)
    Tôi có thể nói trong trường hợp timer, nếu chúng lặp lại và chúng sẽ gọi các thao tác sau mỗi lần expire (thời gian = 0) chúng sẽ tiếp tục lăp lại và gọi các thao tác đó cho dù bạn đã hủy biến mà trong đó nó đc lưu. Đối với trigger, chúng sẽ chạy cho dù bạn đã hủy biến. Special Effect và Lighining sẽ gây ra lag nếu chúng ko bị destroy sau khi đã đc sử dụng. Thử tạo một spell với nhiều special effect. Cast spell đó nhiều lần, nếu effect đó chưa đc remove mà spell đã đc cast thêm, bạn sẽ thấy sau vài lần, game sẽ bắt đầu lag.
    Note: bạn KHÔNG NÊN hủy biến trước khi destroy cái mà nó chứa. Nếu bạn làm vậy, bạn sẽ ko thể destroy cái đó nữa (thứ đc chứa trong biến). Ví dụ:

    Mã:
    function Remove takes nothing returns nothing
    call RemoveUnit(GetTriggerUnit())
    endfunction
    
    function triggy takes nothing returns nothing
    local trigger t
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t, function Remove)
    call TriggerSleepAction(10.00)
    set t = null
    call DestroyTrigger(t)
    endfunction
    Trigger này sẽ remove bất kì unit nào “start effect of an ability”. Tuy nhiên nó nên đc destroy sau 10 giây, làm cho nó ko tồn tại nữa. Nhưng trong trường hợp này, bạn đã hủy biến trước rồi SAU ĐÓ bạn mới destroy trigger. Đó là lí do vì sao trigger sẽ ko bị destroy bởi vì bạn đã destroy cái gì đó null
    Về memory leak, hãy xem thêm post này: http://forum.gamevn.com/showthread.php?t=475215

    XI. Timers

    Timer hoạt động, theo một cách nào đó, giống trigger nhưng cái khác là nó sẽ chạy các action sau một khoảng thời gian, bất chấp hoàn cảnh nào (tức là ko có điều kiện). Hơn nữa, nó sẽ thực hiện các thao tác nhiều lần nếu nó là timer lặp. Và có một thứ hay hơn là timer ko cho rằng 0.10 giây limit, mà cái này đc đặt cho TriggerSleepAction(). Chúng có thể đc đẩy xuống tới 0.01 và đó là lí do vì sao bạn có thể nhận đc các “smooth effect” như knockback. Hãy cùng xét một đoạn script sau. Có thể nó nhìn rất phức tạp nhưng trong thực tế, bạn sẽ biết đc điều gì xảy ra.

    Mã:
    function Cond takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
    endfunction
    
    function Knockback takes nothing returns nothing
    call SetUnitPositionLoc(udg_targ, PolarProjectionBJ(GetUnitLoc(udg_cast), DistanceBetweenPoints(GetUnitLoc(udg_cast), GetUnitLoc(udg_targ))+10, AngleBetweenPoints(GetUnitLoc(udg_cast), GetUnitLoc(udg_targ))))
    endfunction
    
    function Start takes nothing returns nothing
    local timer t
    set udg_cast = GetTriggerUnit()
    set udg_targ = GetSpellTargetUnit()
    set t = CreateTimer()
    call TimerStart(t,0.03,true,function Knockback)
    call TriggerSleepAction(2.00)
    call DestroyTimer(t)
    set udg_cast=null
    set udg_targ=null
    set t = null
    endfunction
    
    function InitTrig_trigger 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 Start)
    endfunction
    Warning: Để spell này hoạt động tốt, bạn nên theo các chỉ dẫn sau:
    - Spell của bạn phải dựa vào Cripple và nó là spell đầu tiên đc tạo ra trong map của bạn để nó có rawcode là ‘A000’
    - Chắc chắn rằng tên trigger của bạn là trigger. Tên có phân biệt viết hoa, viết thường nên hãy cẩn thận
    - Hãy chắc rằng bạn có 2 biến global với tên sau: (cũng phân biệt viết hoa, viết thường) cast và targ
    Ở đây, phân tích là cần thiết. Chắc chắn bạn hiểu hàm điều kiện và hàm cuối nên ta sẽ đến hàm Start luôn. Và, ta lấy 2 biến global và cho chúng giá trị của mục tiêu và trigger unit (là unit cast spell ấy). Để nó multi-instance (locals), ta cần học chương về handlers mà bây giờ thì hơi... quá cao. (Tom sẽ viết một tutorial khác về vấn đề này, sau khi mọi người đã hiểu tutorial này.)
    Bây giờ ta có timer mà ta tạo là 0.03s , lặp lại (cái true ấy), và một khi nó hết nó sẽ chạy hàm KnockBack. Ta có thể nói nó hoạt động như trigger có thời gian nhưng nó tốt hơn vì nó... lặp. Sau đó ta có 2 giây đợi, vậy là timer sẽ chạy 66 lần. Sau đó ta destroy timer để chắc rằng nó sẽ ko chạy nữa và ta đặt cả biến global và local là null để save bộ nhớ.
    Trong hàm KnockBack, nó có thể nhìn lạ nhưng nó lại rất logic. Mỗi khi timer chạy, nó di chuyển mục tiêu ngay lập tức (hàm SetUnitPosition) sử dụng Polar Offset (PolarProjectionBJ trong JASS). Hàm Polar Offset có các tham số sau: điểm giữa ( position of the casting unit), khoảng cách giữa điểm thứ 1 và thứ 2 (mà trong này là khoảng cách giữa 2 unit + 20) và góc giữa 2 điểm (thật ra nó như nhau vì unit này chỉ bị đẩy lùi lại với một góc ko đổi). Tôi hi vọng các bạn hiểu.

    XII. Pick Up Every Unit

    (Daelin thanks Vexorian về việc làm rõ điều gì đó, hic, Tom ko hiểu :( )
    Lần này, ta sẽ dùng biến group. Script sau đây là một AoE sleep, nó làm các unit trong một “vùng ảnh hưởng” (AoE) bị tác dụng bởi sleep. Trước khi ta bắt đầu, tôi nghĩ bạn nên có một dummy unit, dummy spell tạo từ Silence với 0.01s duration và một spell sleep cho dummy unit. Hãy xem xét rawcode sau:
    Mã:
    dummy – ‘h000’
    sleep - ‘ A001’
    mass sleep (silence) – ‘A000’
    Và bây giờ hãy tạo script này. Trigger của bạn sẽ có tên là masssleep (nhớ là phân biệt chữ viết hoa, viết thường)

    Mã:
    function Cond takes nothing returns boolean
    return GetSpellAbilityId()==’A000’
    endfunction
    
    function Mass_Sleep takes nothing returns nothing
    local group g
    local unit u
    local unit cast
    local unit dumb
    local location p
    
    set cast = GetTriggerUnit()
    set p = GetSpellTargetLoc()
    set g = GetUnitsInRangeOfLocAll(800.00, p)
    loop
          set u = FirstOfGroup(g)
          exitwhen u==null
          if  IsUnitEnemy(u, GetOwningPlayer(cast))==true then
          call GroupRemoveUnit(g,u)
          set dumb = CreateUnitAtLoc(GetOwningPlayer(cast), ‘h000’, GetUnitLoc(u), 0.00)
          call IssueTargetOrderBJ(dumb, “sleep”, u)
          call UnitApplyTimedLifeBJ (1.50, ‘BTLF’, dumb)
          set dumb = null
          endif
    endloop 
    
    set g = null
    set u = null
    set cast = null
    set p = null
    endfunction
    
    function InitTrig_masssleep 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 Mass_Sleep)
    endfunction
    Nhiều thứ cần đc giải thích nhưng tôi hi vọng các thông tin này sẽ giúp các bạn. Tôi sẽ đi thẳng vào action. Hàm GetUnitsInRangeOfLocAll lấy tất cả các unit trong range của một điểm. Nếu bạn muốn “select” địch ko thôi thì bạn phải dùng hàm khác.

    Hàm FirstOfGroup sẽ lấy unit đầu tiên trong một unit group (thật ra Tom cũng ko biết unit đầu tiên là thế nào :|) Điều này chả ảnh hưởng gì vì từ unit đầu tiên đến hết, ta sẽ pick hết các unit này. Bây giờ, bạn sẽ tự hỏi tại sao tôi lại cho biến u giá trị của FirstOfGroup(g) cả trong và ngoài loop. Nếu bạn chỉ cho nó giá trị trong loop, nó sẽ bị null khi ta bắt đầu loop. Và điều kiện là u==null sẽ là đúng và loop sẽ bị bỏ qua.
    IsUnitEnemy là hàm kiểm tra unit nào đó có là địch của một player hay ko. GetOwningPlayer là owner của một unit (là player sở hữu unit đó), trong trường hợp này Triggering Unit đc lưu trong một biến. Hàm GroupRemoveUnit bỏ một unit ra khỏi một group. Nếu bạn ko làm vậy, mỗi lần lặp lại của loop, first unit trong group đã đc lưu trong u sẽ lại đc pick và nó cứ pick u như vậy. Nếu bỏ unit đó ra khỏi group, thì first unit khác sẽ xuất hiện trong group. Nhớ là nếu ko còn unit nào trong group thì u sẽ nhận giá trị null và loop sẽ dừng.
    Bây giờ tạo dummy unit, hàm CreateUnitAtLoc ko cho phép bạn dùng GetLastCreatedUnit(), vậy bạn sẽ nhận đc nó sau khi tạo. (btw, chả hiểu ông Daelin viết gì, ko cho phép dùng biến kia mà mình lại “get” unit đó, hic) Hàm này, chính nó trả lại một giá trị nhưng sẽ ko có vấn đề gì nếu ta cứ dùng nó theo: “call CreateUnitAtLoc” nhưng trong cách này ta sẽ ko theo dõi nó đc nữa (track ở đây dịch là ? ). Cái ExpirationTimer (UnitApplyTimedLifeBJ) để chắc chắn unit đó sẽ biến mất sau một thời gian. Nhớ là bạn sẽ ko thể biết đc dummy unit nào với dummy unit nào vì loop này lặp đi lặp lại, tạo một unit xong, lưu vào biến, lần sau lại như vậy (unit ở lần loop trước sẽ ko đc lưu trong biến nữa) -> nên ta dùng cái “expiration timer” này.

    XIII. Arrays

    Mảng rất gần với các NNLT khác. Vấn đề duy nhất trong JASS là bạn ko thể có mảng nhiều chiều (unidimensional array). Vậy bạn ko thể có “ma trận”, ít nhất thì đây là cách “cũ mà hay”. Hãy xem biến đc khai báo và sử dụng thế nào:

    Mã:
    local unit array a
    set a[0]=GetTriggerUnit()
    set a[1]=GetDamageSource()
    Vậy bạn khai báo với cú pháp: local, kiểu biến, array, tên biến. Và để dùng chúng thì bạn dùng tên biến và sau đó là thêm tham số nơi mà bạn lưu giá trị. Bạn chỉ cần biết như vậy về mảng thôi :D
    Note: mảng trong JASS ko thể dung như là tham số cho các function.
    Advice: Unit Group tốt hơn unit array bởi vì bạn đã có các hàm native đc thiết kế cho chúng.

    XIV. Conclusion

    Tôi khuyên bạn nên thử các hàm đc tạo sẵn và JASS khi điều này giúp bạn hiểu đc ý nghĩa của Custom Script. Đó là tùy vào bạn có muốn thử các action khác hay ko. Khi bạn ko hiểu action nào, kết hợp Jass Craft và đổi từ GUI ra JASS. Thỉnh thoảng tôi cũng dùng cách này vì bao nhiêu hàm, thật khó nhớ.

    XV. Credit
    Thanks Daelin for making this tutorial, his original post: http://www.thehelper.net/forums/showthread.php?t=28292
     
  2. Vua_Do_Hoa

    Vua_Do_Hoa Mr & Ms Pac-Man

    Tham gia ngày:
    30/3/08
    Bài viết:
    217
    Nơi ở:
    Tp Hồ Chí Minh
    Quả là hay, chút anh thành công với những bài Tut tiếp theo , nếu mình ko tự viết thì dịch anh nhỉ :D
    Thanks nhiều ;;)
    .
    ___________Auto Merge________________

    .
    À, còn một bài lớn lớn về mấy cái như là Timer trong World-editor-tutorials.thehelper.net
    một số bài về Struct và một số về Globals anh làm nhà phiên dịch cho tụi em hoc hỏi đi, nha anh ...:D
     
  3. Vua_Do_Hoa

    Vua_Do_Hoa Mr & Ms Pac-Man

    Tham gia ngày:
    30/3/08
    Bài viết:
    217
    Nơi ở:
    Tp Hồ Chí Minh
    Em có một số ý kiến về bài viết trên như sau

    ++ Phần giới thiệu sơ sài
    +++ Hướng dẫn chưa cụ thể về Function
    +++ Hướng dẫn chưa cụ thể về biến Globals và local
    ++ Phần quan trong về Timer vẫn nói quá sơ sài
    :hug:
     
  4. kingkod

    kingkod Mr & Ms Pac-Man

    Tham gia ngày:
    16/9/07
    Bài viết:
    127
    Bác tom hướng dẫn thêm đi, vẫn chưa đủ mừ
     
  5. BinhDotA

    BinhDotA Youtube Master Race

    Tham gia ngày:
    27/5/08
    Bài viết:
    15
    Hờ hờ hay nhở...chắc mình phải tập xài JASS từ bây h thôi :D

    Chỉ sợ đến lúc học xong JASS thì thế giới chuyển qua Starcraft Editor hết ... :))
     
  6. Exp1111

    Exp1111 Donkey Kong

    Tham gia ngày:
    19/9/08
    Bài viết:
    486
    Nơi ở:
    HN123
    Chả biết Starcraft 2 Editor có khá hơn bản 1 ko, bản 1 hạn hẹp lắm.
    Và chắc còn lâu mới sang Star2 cứ học đi
    Ai cho tui 1 vài ví dụ vê cái handle của KattAna đi, trên mấy cái kia ví dụ khó hiểu quá
     
  7. rongdoVN

    rongdoVN Space Marine Doomguy Lão Làng GVN

    Tham gia ngày:
    6/9/05
    Bài viết:
    5,674
    Nơi ở:
    TS-Pri GameVN
    hehehe đã hiểu đc phần nào rồi
    từ từ học nữa =))
     
  8. dante cvn

    dante cvn Donkey Kong

    Tham gia ngày:
    30/4/07
    Bài viết:
    311
    Nơi ở:
    VietNam
    Cho hỏi là làm sau trong Trigger có chỗ để viết Jass?
     
  9. Tom_Kazansky

    Tom_Kazansky

    Tham gia ngày:
    28/12/06
    Bài viết:
    3,454
    Nơi ở:
    Hà Nội
    click vào một trigger: Edit \ Convert to Custom Text
     
  10. letuandi1988

    letuandi1988 Youtube Master Race

    Tham gia ngày:
    15/4/09
    Bài viết:
    10
    ông bạn chỉ tui mở file w3x để xem cấu trúc maps với
    xin ông cho tui 1 đoạn code add AI vào maps.:(:D
     
  11. babymoon12345

    babymoon12345 Youtube Master Race

    Tham gia ngày:
    22/1/09
    Bài viết:
    29
    Anh ơi. Link đó không có file download
     
  12. Tom_Kazansky

    Tom_Kazansky

    Tham gia ngày:
    28/12/06
    Bài viết:
    3,454
    Nơi ở:
    Hà Nội
    đã sửa link

    ngoài ra tôi đã gửi kèm ở dưới, down luôn cho tiện :@)
     

    Các file đính kèm:

    • jc113.zip
      Kích thước:
      799.8 KB
      Đọc:
      529
  13. Kenmj

    Kenmj Youtube Master Race

    Tham gia ngày:
    26/5/09
    Bài viết:
    95
    Tìm hiễu về JASS này mệt thật..Nhưng hình như nó đơn giản hơn khi làm với Trigger thì phải.Đơn giản mà chưa hiễu gì ráo.Ai đó tạo 1 số JASS trong 1 map nào đó rồi post lên để mọi người tham khảo học tập thôi.
     
  14. conga12

    conga12 Mr & Ms Pac-Man

    Tham gia ngày:
    4/7/10
    Bài viết:
    164
    tạo 1 cái đơn giản như lính xuất hiện ấy nhỉ
     
  15. King War

    King War

    Tham gia ngày:
    23/7/10
    Bài viết:
    2,136
    Nơi ở:
    kw_corp@yh
    thì làm 1 cái trigger linh xuất hiện
    Edit - Convert ...
    thành jass lun :D
    nhanh - gọn - lẹ - chính xác
     
  16. Tom_Kazansky

    Tom_Kazansky

    Tham gia ngày:
    28/12/06
    Bài viết:
    3,454
    Nơi ở:
    Hà Nội
    • nhanh: ok,
    • gọn: gọn gì cơ?
    • lẹ: lẹ á?
    • chính xác: chính xác gì thế?

    convert từ GUI ra JASS xong mà bảo đó là JASS á? thế đừng học JASS nữa =))
     
  17. King War

    King War

    Tham gia ngày:
    23/7/10
    Bài viết:
    2,136
    Nơi ở:
    kw_corp@yh
    với em thì chĩ edit ra jass rùi thêm vài cái lệnh mà GUI ko có :)
     
  18. lucifekit

    lucifekit The Warrior of Light

    Tham gia ngày:
    25/2/06
    Bài viết:
    2,344
    Học Jass tốt nhất là xem các map demo,map system trên mạng,map chưa protect xem họ làm thế nào để bắt chước,thử 1 lần vừa nhìn mẫu vừa làm,thử 1 lần ko cần nhìn mẫu mà làm là nó biến thành của mình lúc nào không hay :D Chỗ nào bí thì đợi siêu nhân siêu cao thủ siêu pro Tom_Kazansky lên mạng buzz nhiệt tình hỏi thôi :D ( đùa chứ tốt nhất là ko buzz,đầu tiên hỏi từ tốn vâng dạ,nếu đại ca Tom rảnh thì a í sẽ reply,ko thì cứ lẳng lặng mà ngồi mò típ,thế nào cũng ra )
     
    Chỉnh sửa cuối: 23/7/10
  19. chuthanhtunghp

    chuthanhtunghp Youtube Master Race

    Tham gia ngày:
    11/9/11
    Bài viết:
    1
    Anh Ai Up HÌnh ảnh gi đi cha? hiểu gì
     
  20. Tom_Kazansky

    Tom_Kazansky

    Tham gia ngày:
    28/12/06
    Bài viết:
    3,454
    Nơi ở:
    Hà Nội
    có ảnh thì cũng toàn chữ, post làm gì? =))


    không biết thì không hiểu >:D<
     

Chia sẻ trang này