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 ). 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 ) 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 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
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ỉ 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 ...
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:
Hờ hờ hay nhở...chắc mình phải tập xài JASS từ bây h thôi Chỉ sợ đến lúc học xong JASS thì thế giới chuyển qua Starcraft Editor hết ...
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á
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.
thì làm 1 cái trigger linh xuất hiện Edit - Convert ... thành jass lun nhanh - gọn - lẹ - chính xác
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
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 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 ( đù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 )