======Lấy biến Trigger bằng cách sử dụng script===== =====Danh sách thuộc tính hành động (ActionAttr)===== | **Tên (Name)** | **Giá trị (Value)** | **Mô tả (Description)** | | Position | 1 | Vị trí | | AreaIns | 2 | Khu vực | | Number | 3 | Giá trị | | String | 4 | Chuỗi | | Boolean | 5 | Đúng/Sai (Boolean) | | Player | 6 | Người chơi | | PlayerGrout | 7 | Nhóm người chơi | | BlockType | 8 | Loại khối | | ItemType | 9 | Loại vật phẩm | | Creature | 10 | Sinh vật | | ActorID | 11 | Loại nhân vật | | CreatureGroup | 12 | Nhóm sinh vật | | Timer | 13 | Hẹn giờ | | EffectType | 14 | Loại hiệu ứng đặc biệt | | SiteGroup | 15 | Nhóm vị trí | | AreaGroup | 16 | Nhóm khu vực | | NumberGroup | 17 | Nhóm giá trị | | StringGroup | 18 | Nhóm chuỗi | | BooleanGroup | 19 | Nhóm đúng/sai | | BlockTypeGroup | 20 | Nhóm loại khối | | ItemTypeGroup | 21 | Nhóm loại vật phẩm | | CreatureTypeGroup | 22 | Nhóm loại sinh vật | | TimerGroup | 23 | Nhóm hẹn giờ | | EffectTypeGroup | 24 | Nhóm loại hiệu ứng | ---- =====Tại sao bạn lại muốn lấy các biến trigger?===== Nếu bạn đang theo dõi hướng dẫn này, rất có thể bạn muốn tạo game bằng cách kết hợp cả script và trigger – tận dụng ưu điểm của cả hai. Và thực tế, có những phương pháp giúp chúng ta làm được điều đó.\\ Việc lấy biến trigger cũng rất hữu ích vì bạn có thể sử dụng chúng như biến dùng chung giữa các script. Mini World không hỗ trợ phương thức **[[https://www.lua.org/pil/8.1.html|require]]**, vì vậy đây là một giải pháp thay thế tuyệt vời, cho phép bạn truy cập và sử dụng các biến có thể dùng chung trong mọi script.\\ =====Cách lấy biến toàn cục (global variables)===== Để lấy các biến toàn cục thì rất dễ vì chúng ta có các phương thức để làm điều đó.\\ >__getGlobalVarByName:__ như bạn có thể đoán từ tên gọi, cho phép bạn lấy giá trị của một biến toàn cục.\\ >__setGlobalVarByName:__ cho phép bạn thay đổi giá trị của một biến toàn cục.\\ ====getGlobalVarByName==== Hãy nói về phương thức này trước. Phương thức này nhận vào hai tham số:\\ * actionattr: số (Kiểu của biến) * msg: chuỗi (Tên của biến) Và trả về:\\ * **[[script:bai14| Mã lỗi (Errorcode)]]** * Giá trị (Value – tùy thuộc vào loại biến) Chúng ta sẽ làm một ví dụ sử dụng sau khi giải thích phương thức tiếp theo.\\ ====setGlobalVarByName==== Phương thức này thay đổi giá trị của biến trigger mà chúng ta đã cung cấp. Nó nhận vào ba tham số:\\ * actionattr: số (Kiểu của biến) * msg: chuỗi (Tên của biến) * val: (phụ thuộc vào actionattr) Và trả về:\\ * **[[script:bai14| Mã lỗi (Errorcode)]]** ===Ví dụ sử dụng=== Giả sử chúng ta muốn rằng khi một sinh vật bị đánh bại, một biến sẽ tăng lên 1. Chúng ta sẽ làm phần đó bằng trigger. Và khi biến đó lớn hơn hoặc bằng năm, chúng ta sẽ gửi một tin nhắn đến tất cả người chơi với nội dung “Bạn đã thắng!”, sau đó đặt lại biến đó về 0. Chúng ta sẽ làm phần đó bằng script. ===Trigger=== {{:script:feature:mini_world_api:devplustriggernew.png?direct&600|}}\\ ===Script=== Chúng ta bắt đầu bằng cách tạo một hàm, trong hàm này không cần tham số, rồi thêm [[https://en.wikipedia.org/wiki/Event_(computing)#Event_handler|Listener]] với sự kiện **Actor.Die** (Sinh vật chết) và hàm mà chúng ta vừa tạo. Mình sẽ không đi sâu chi tiết phần này vì nó đã được giải thích trong hướng dẫn về sự kiện **[[script:feature:Mini World API Events Usage|Sử dụng sự kiện Mini World API]]**. ===Đoạn mã sẽ trông như thế này:=== function CreatureDefeated() end ScriptSupportEvent:registerEvent([=[Actor.Die]=], CreatureDefeated) Sau đó, chúng ta thêm vào hàm phương thức **getGlobalVarByName(actionattr, msg)**, như đã giải thích ở đầu hướng dẫn này.Tham số **actionattr** là kiểu của biến mà chúng ta muốn lấy. Có thể bạn sẽ thắc mắc làm sao biết được kiểu đó, và câu trả lời là bảng này chứa tất cả các kiểu biến có thể có cùng với giá trị của chúng.\\ ===Bảng=== | **Tên** | **Giá trị** | **Mô tả** | | Position | 1 | Vị trí | | AreaIns | 2 | Khu vực | | Number | 3 | Giá trị | | String | 4 | Chuỗi | | Boolean | 5 | Giá trị đúng/sai (Boolean) | | Player | 6 | Người chơi | | PlayerGrout | 7 | Tập hợp người chơi | | BlockType | 8 | Loại khối | | ItemType | 9 | Loại vật phẩm | | Creature | 10 | Sinh vật | | ActorID | 11 | Loại nhân vật | | CreatureGroup | 12 | Tập hợp sinh vật | | Timer | 13 | Bộ hẹn giờ | | EffectType | 14 | Loại hiệu ứng đặc biệt | | SiteGroup | 15 | Nhóm vị trí | | AreaGroup | 16 | Nhóm khu vực | | NumberGroup | 17 | Nhóm giá trị | | StringGroup | 18 | Nhóm chuỗi | | BooleanGroup | 19 | Nhóm giá trị đúng/sai | | BlockTypeGroup | 20 | Nhóm loại khối | | ItemTypeGroup | 21 | Nhóm loại vật phẩm | | CreatureTypeGroup | 22 | Nhóm loại sinh vật | | TimerGroup | 23 | Nhóm bộ hẹn giờ | | EffectTypeGroup | 24 | Nhóm loại hiệu ứng | ---- Biến của tôi là kiểu số, nên actionattr là **3**. Nhưng sau khi xem bảng, có thể bạn sẽ thắc mắc, còn tham số **msg** thì sao? Nó chính là tên của biến mà chúng ta muốn lấy. Biến mà tôi tạo có tên là “MyTestVal”, nên **msg** tôi phải dùng là “MyTestVal”. Nhưng nếu tên biến của bạn khác, thì bạn phải dùng tên biến của bạn làm **msg**. Các lỗi thường gặp: function CreatureDefeated() local result = VarLib2:getGlobalVarByName(3,"MyTestVal") end ScriptSupportEvent:registerEvent([=[Actor.Die]=], CreatureDefeated) Lỗi này khá phổ biến, vì getGlobalVarByName trả về **2** giá trị (mã lỗi và giá trị) thay vì chỉ trả về giá trị. Nếu bạn cố gắng dùng biến kết quả **result** thì sẽ nhận được hoặc là 0 (ErrorCode.OK – không có lỗi), hoặc là 1001 (ErrorCode.FAILED – có lỗi xảy ra). Cách sửa là bạn phải khai báo thêm một biến nữa, ví dụ: **local result, value = VarLib2:getGlobalVarByName(3, “MyTestVal”)** Mẹo: Nếu bạn không dùng một biến nào đó được trả về bởi bất kỳ phương thức nào, bạn có thể dùng dấu gạch dưới _ thay cho tên biến đó. Dấu "_" báo cho Lua biết biến đó sẽ không được sử dụng. Bạn có thể dùng "_" nhiều lần, ví dụ:\\ _, _, PlayerList = World:getAllPlayers(-1) Còn nếu bạn không dùng "_" thì phải khai báo như thế này: result, PlayerNum, PlayerList = World:getAllPlayers(-1) ===Đoạn mã sẽ trông như thế này:=== function CreatureDefeated() local result, value = VarLib2:getGlobalVarByName(3,"MyTestVal") end ScriptSupportEvent:registerEvent([=[Actor.Die]=], CreatureDefeated) ===Hoặc=== function CreatureDefeated() local _, value = VarLib2:getGlobalVarByName(3,"MyTestVal") end ScriptSupportEvent:registerEvent([=[Actor.Die]=], CreatureDefeated) Cả hai cách đều giống nhau, nhưng cách trước tốt hơn nếu bạn muốn xử lý các lỗi có thể xảy ra. Sau đó, chúng ta thêm một câu lệnh if kiểm tra xem value có lớn hơn hoặc bằng 5 không, nếu đúng thì gửi tin nhắn cho người chơi với nội dung “You won!” và đồng thời đặt lại biến của chúng ta về 0. Phương thức setGlobalVarByName cũng có actionattr và msg, nên ta có thể sao chép từ getGlobalVarByName, nhưng tham số cuối cùng (val) là giá trị mà ta muốn biến đó có, trong trường hợp này là 0.\\ ===Đoạn mã sẽ trông như thế này:=== function CreatureDefeated() local result, value = VarLib2:getGlobalVarByName(3,"MyTestVal") if value >= 5 then Chat:sendSystemMsg("You won!", 0) VarLib2:setGlobalVarByName(3,"MyTestVal",0) end end ScriptSupportEvent:registerEvent([=[Actor.Die]=], CreatureDefeated) ===Hoặc=== function CreatureDefeated() local _, value = VarLib2:getGlobalVarByName(3,"MyTestVal") if value >= 5 then Chat:sendSystemMsg("You won!", 0) VarLib2:setGlobalVarByName(3,"MyTestVal",0) end end ScriptSupportEvent:registerEvent([=[Actor.Die]=], CreatureDefeated) ---- =====Cách lấy biến (biến riêng) của người chơi===== Giống như với biến toàn cục, với biến riêng của người chơi cũng có các phương thức cho phép ta lấy giá trị được lưu trong đó. Các phương thức này đều theo cùng một cấu trúc như phương thức với biến toàn cục, chỉ có khác là ta phải chỉ định trước id của người chơi mà ta muốn lấy biến riêng của họ.\\ ====getPlayerVarByName==== Lần này chúng ta sẽ nói về phương thức tương tự của getGlobalVarByName nhưng dành cho biến riêng. Phương thức này nhận ba tham số:\\ * objid: number (UID của người chơi mà ta sẽ lấy giá trị biến) * actionattr: number (Kiểu của biến) * msg: string (Tên của biến) Và trả về:\\ * **[[script:bai14| Mã lỗi (Errorcode)]]** * Giá trị (Tùy thuộc) Chúng ta sẽ làm ví dụ sử dụng sau khi giải thích phương thức tiếp theo.\\ ====setPlayerVarByName==== Phương thức này thay đổi giá trị của biến riêng mà ta đã chỉ định cho người chơi có UID mà ta cung cấp. Nó nhận bốn tham số:\\ * objid: number (UID của người chơi mà ta sẽ thay đổi giá trị biến) * actionattr: number (Kiểu của biến) * msg: string (Tên của biến) * val (Tùy thuộc vào actionattr) Và trả về:\\ * **[[script:bai14| Mã lỗi (Errorcode)]]** ===Ví dụ sử dụng=== Lần này chúng ta sẽ làm một điều khác, với các script ta sẽ làm tương tự, khi một người chơi đánh bại một con quái vật, ta sẽ cộng thêm 1 vào số quái vật mà người chơi đó đã đánh bại, và khi người chơi đánh bại được một số lượng quái vật, giả sử là 5, ta sẽ hiển thị thông báo “{PlayerNickname} đã lên cấp” trong đó **{PlayerNickname}** là chỗ dành sẵn cho tên của người chơi. ===Trigger=== {{:script:feature:mini_world_api:devplustriggernewprivate.png?direct&600|}}\\ ===Script=== Bây giờ, để làm điều này, chúng ta sẽ cần hiểu rõ hơn một chút về các kiến thức cơ bản vì chúng ta sẽ sử dụng một thứ gọi là **vòng lặp for dạng số** (thêm thông tin tại trang [[https://www.lua.org/pil/4.3.4.html|Lua numeric for page]]) và vòng lặp while (thêm thông tin tại trang [[https://www.lua.org/pil/4.3.2.html|while loop]]). Chúng ta bắt đầu giống như ví dụ trước, tạo một hàm không cần đối số, thêm [[https://en.wikipedia.org/wiki/Event_(computing)#Event_handler|Listener]] và thêm một vòng lặp while true bên trong hàm đó. Mã sẽ trông giống như sau: function CreatureDefeated() while true do threadpool:wait(1) end end ScriptSupportEvent:registerEvent([=[Game.Start]=], CreatureDefeated) Hàm **threadpool:wait(seconds)** rất quan trọng vì nếu chúng ta không thêm nó, trò chơi có thể bị treo. Sau đó, chúng ta thêm vào trong vòng lặp while hàm **World:getAllPlayers(-1)** — -1 có nghĩa là tất cả người chơi — và chúng ta thêm ba biến vì phương thức này trả về:\\ * **[[script:bai14| Mã lỗi (Errorcode)]]** * Số lượng người chơi * Một danh sách chứa tất cả ID của người chơi Nhưng chúng ta chỉ cần danh sách ID. Sau đó, chúng ta thêm một vòng lặp for dạng số với i = 1, PlayerListLength và bên trong vòng lặp đó, chúng ta thêm phương thức getPlayerVarByName. Chúng ta đã biết cách lấy actionattr và msg, vì vậy sau khi hiện đoạn mã, chúng ta sẽ truyền objid.\\ Mã sẽ trông giống như sau:\\ function CreatureDefeated() while true do local _,_,PlayerList = World:getAllPlayers(-1) for i=1, #PlayerList do local result, value = VarLib2:setPlayerVarByName(PlayerList[i],3,"MyPrivateTestVar") Chat:sendSystemMsg(value) end Trigger:wait(1) end end ScriptSupportEvent:registerEvent([=[Game.Start]=], CreatureDefeated) Sau khi xem đoạn mã, bạn có thể thắc mắc, tại sao chúng ta lại dùng **PlayerList[i]**, câu trả lời nằm ở cách vòng lặp **for** hoạt động. Vòng lặp **for** hoạt động như sau: **for initialvalue, endvalue, increment**, trong đó:\\ * __initialvalue__ là giá trị ban đầu mà chúng ta gán cho **i** * __endvalue__ là giá trị mà **i** sẽ tăng đến, và khi chạm đến giá trị này thì vòng lặp sẽ dừng * __increment__ là phần tùy chọn, nếu không khai báo thì mặc định sẽ là 1 Giá trị **#** tương đương với độ dài của một bảng (table), vì vậy chúng ta đang nói rằng giá trị bắt đầu của **i** là 1 và giá trị kết thúc là độ dài của **PlayerList** (số lượng người chơi hiện tại). Vậy nếu có 4 người chơi, thì độ dài của **PlayerList** sẽ là 4, do đó, vòng lặp sẽ chạy cho đến khi **i** bằng giá trị kết thúc (chạy 4 lần). Vậy **[i]** nghĩa là gọi phần tử ở vị trí **i** trong bảng.\\ Hãy xem một ví dụ nhỏ:\\ | Người chơi | Vị trí trong bảng | | NotSoPr17 | 1 | | PvpGamesAreGreat | 2 | | MiniWorldPlayer | 3 | | ILoveMiniWorld | 4 | Vậy ta biết có 4 người chơi nên độ dài **playerlist** sẽ là 4, sau đó ta thực hiện câu lệnh **for**.\\ for i=1, #playerlist (we know playerlist is 4 so it will be executed and i will increment by 1 until i is equal to 4) i -> 1 (NotSoPr17) i -> 2 (PvpGamesAreGreat) i -> 3 (MiniWorldPlayer) i -> 4 (ILoveMiniWorld) Vậy nói đơn giản thì **PlayerList[i]** chính là ID của người chơi ở vị trí thứ **i** trong bảng. Sau đó, chúng ta thêm một câu lệnh **if** để kiểm tra xem giá trị biến của người chơi có lớn hơn hoặc bằng 5 hay không. Đoạn mã lúc này sẽ trông giống như sau: function CreatureDefeated() while true do local _,_,PlayerList = World:getAllPlayers(-1) for i=1, #PlayerList do local result, value = VarLib2:setPlayerVarByName(PlayerList[i],3,"MyPrivateTestVar") if value >= 5 then end end threadpool:wait(1) end end ScriptSupportEvent:registerEvent([=[Game.Start]=], CreatureDefeated) Sau đó, khi một người chơi đánh bại hơn 5 quái vật hoặc người chơi khác, đoạn mã bên trong câu lệnh **if** sẽ được thực thi. Tiếp theo, ta thêm dòng **local _, playerNick = Player:getNickname(PlayerList[i])** để lấy tên hiển thị (nickname) của người chơi hiện tại, và chúng ta tạo một hàm nhận vào nội dung (**content**) và danh sách người chơi (**playerlist**) để hiển thị một thông báo cho họ. Hàm này sẽ sử dụng vòng lặp **for** để làm việc đó. ===Hàm sẽ trông giống như thế này:=== function SendToAllPlayers(content,funcplayerlist) for i=1, #funcplayerlist do Chat:sendSystemMsg(content, funcplayerlist[i]) end end Sau đó, ta thêm vào trong câu lệnh **if** phần kiểm tra xem giá trị có lớn hơn hoặc bằng 5 hay không, và sử dụng hàm **setPlayerVarByName** với **objid** là **PlayerList[i]**. ===Nếu bạn đang cảm thấy bối rối — điều này hoàn toàn bình thường khi học một cái gì đó mới — thì dưới đây là đoạn mã ví dụ để bạn tham khảo:=== function SendToAllPlayers(content,funcplayerlist) for i=1, #funcplayerlist do Chat:sendSystemMsg(content, funcplayerlist[i]) end end function CreatureDefeated() while true do local _,_,PlayerList = World:getAllPlayers(-1) for i=1, #PlayerList do local result, value = VarLib2:getPlayerVarByName(PlayerList[i],3,"MyPrivateTestVar") if value >= 5 then local _,playerNick = Player:getNickname(PlayerList[i]) SendToAllPlayers(playerNick .. " has leveled up!", PlayerList) VarLib2:setPlayerVarByName(PlayerList[i],3,"MyPrivateTestVar",0) end end threadpool:wait(1) end end ScriptSupportEvent:registerEvent([=[Game.Start]=], CreatureDefeated) ----