Sau khi tạo một vài script, không lâu trước khi bạn muốn tái sử dụng một số mã giữa chúng. Tùy thuộc vào vị trí, ModuleScripts cho phép bạn tái sử dụng mã giữa các script ở các bên khác nhau của ranh giới client-server hoặc cùng một bên của ranh giới.
Bạn có thể đặt module scripts ở bất kỳ đâu mà bạn đặt các scripts, nhưng ReplicatedStorage là một vị trí phổ biến; lưu trữ module scripts ở đây cho phép bạn tái sử dụng mã giữa server và clients.
Cấu trúc của một module script
Trong Roblox Studio, thêm một module script vào ReplicatedStorage và đổi tên thành PickupManager. Mỗi ModuleScript bắt đầu bằng mã sau:
local module = {}return module
Mã này tạo ra một bảng Luau trống và trả về nó cho bất kỳ script nào yêu cầu module script.
Giá trị trả về có thể là bất kỳ kiểu dữ liệu nào ngoại trừ nil, nhưng hầu hết các module scripts sẽ trả về một hàm, một bảng, hoặc một bảng các hàm. Để tạo ra giá trị trả về của nó, module scripts có thể chạy mã tùy ý, bao gồm cả yêu cầu các module scripts khác.
Ví dụ sau trả về một bảng với một hàm duy nhất gọi là getPickupBonus. Dán nó vào module script mới:
-- ModuleScript in ReplicatedStorage
local PickupManager = {}
local defaultMultiplier = 1.25
local rarityMultipliers = {
common = 10,
uncommon = 20,
rare = 50,
legendary = 100
}
-- Thêm hàm getPickupBonus vào bảng PickupManager
PickupManager.getPickupBonus = function(rarity)
local bonus = rarityMultipliers[rarity] * defaultMultiplier
return bonus
end
return PickupManager
Thêm hàm vào một bảng là không hoàn toàn cần thiết - bạn có thể chỉ cần trả về chính hàm đó - nhưng đó là một mẫu tốt để tuân theo; nó cung cấp cho bạn cú pháp dễ hiểu khi bạn gọi hàm từ một script khác và cho phép bạn dễ dàng thêm nhiều hàm hơn vào module script theo thời gian.
Yêu cầu module scripts
Để tải một module script, bạn gọi hàm require(). Trong ReplicatedStorage, thêm một script mới và thay đổi RunContext của nó thành Client. Sau đó thêm mã sau để gọi hàm PickupManager.getPickupBonus:
Client script in ReplicatedStoragelocal ReplicatedStorage = game:GetService("ReplicatedStorage")-- Lấy giá trị được trả về bởi ModuleScriptlocal PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))-- Gọi một hàm trong ModuleScriptlocal bonus = PickupManager.getPickupBonus("legendary")print(bonus) --> 125
Lưu trữ các module scripts trong ReplicatedStorage cho phép bạn chia sẻ mã giữa server và client, vì vậy bạn có thể sử dụng cùng một mã để yêu cầu script từ ServerScriptService:
Script in ServerScriptServicelocal ReplicatedStorage = game:GetService("ReplicatedStorage")local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))
Khi bạn gọi require() trên một ModuleScript, nó chạy một lần và trả về một mục duy nhất dưới dạng tham chiếu. Gọi require() lần nữa sẽ trả về cùng một tham chiếu, có nghĩa là nếu bạn sửa đổi một bảng được trả về hoặc Instance, các cuộc gọi require() tiếp theo sẽ trả về tham chiếu đã sửa đổi đó. Chính module sẽ không chạy nhiều lần.
Nếu bạn yêu cầu một ModuleScript từ cả hai bên của ranh giới client-server, như trong ví dụ trên, ModuleScript sẽ trả về một tham chiếu duy nhất cho mỗi bên.
Các mẫu
Các module scripts có một số mẫu thông dụng mà bạn có thể sử dụng để đơn giản hóa mã của mình và tránh những cạm bẫy khi trò chơi của bạn phát triển về quy mô và phức tạp.
Chia sẻ dữ liệu
Để liên kết dữ liệu với các đối tượng riêng lẻ, bạn có thể gán thuộc tính cho chúng hoặc tạo các thư mục Configuration với các đối tượng giá trị như StringValue hoặc IntValue. Tuy nhiên, cả hai cách tiếp cận đều khó khăn nếu bạn muốn thêm hoặc sửa đổi hàng chục đối tượng hoặc giá trị dữ liệu. Chúng cũng không lưu trữ bảng hoặc hàm.
Nếu bạn muốn sửa đổi cùng một dữ liệu cho nhiều bản sao của cùng một đối tượng hoặc tái sử dụng cùng một dữ liệu cho các đối tượng khác nhau, hãy lưu dữ liệu trong ModuleScripts. Đó là một cách dễ dàng hơn để bạn tái sử dụng dữ liệu trong các script khác, và bạn có thể lưu trữ bảng và hàm.
Ví dụ sau là ModuleScript trong ReplicatedStorage lưu trữ các giá trị cấu hình cho một khẩu súng chung:
ModuleScript in ReplicatedStoragelocal GunConfig = {}GunConfig.MagazineSize = 20GunConfig.AmmoCount = 100GunConfig.Firerate = 600GunConfig.Damage = {["Head"] = 50;["Torso"] = 40;["Body"] = 25;}return GunConfig
Sự kiện tùy chỉnh
Các sự kiện tùy chỉnh cho phép các script giao tiếp với nhau, nhưng việc phải theo dõi các tham chiếu đến các đối tượng BindableEvent riêng lẻ có thể gây rối cho mã của bạn.
Bạn có thể sử dụng ModuleScripts để lưu trữ các BindableEvents và cung cấp các trình xử lý sự kiện tùy chỉnh mà gắn trực tiếp với các phương thức của ModuleScript.
Ví dụ ModuleScript sau trong ReplicatedStorage có một sự kiện tùy chỉnh sẽ kích hoạt khi công tắc thay đổi trạng thái:
ModuleScript in ReplicatedStorage
local Switch = {}
-- Tạo bindable để bất kỳ script nào cũng có thể lắng nghe khi công tắc thay đổi
local bindableEvent = Instance.new("BindableEvent")
Switch.Changed = bindableEvent.Event
local state = false
function Switch.flip()
state = not state
bindableEvent:Fire(state)
end
return Switch
Ví dụ về một client script trong ReplicatedStorage kết nối một hàm để gọi khi sự kiện Switch.Changed được kích hoạt.
Script in ReplicatedStorage
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Switch = require(ReplicatedStorage:WaitForChild("Switch"))
Switch.Changed:Connect(function(newState)
print("Trạng thái công tắc hiện là", newState)
end)
-- Thử lật vài lần
task.wait(1)
Switch.flip()
task.wait(1)
Switch.flip()
Đóng gói
Đóng gói là thực tiễn tạo một lớp trừu tượng xung quanh các đối tượng hoặc logic lập trình để che giấu độ phức tạp. Bạn có thể sử dụng ModuleScripts để đóng gói các đối tượng Roblox với các hàm Luau tùy chỉnh nhằm đơn giản hóa mã.
Ví dụ, bạn có thể sử dụng đóng gói để:
- Đơn giản hóa giao tiếp giữa các mạng với một đối tượng RemoteEvent duy nhất.
- Gói mã xử lý lỗi quanh các dịch vụ nhạy cảm như DataStoreService.
- Định nghĩa các phương thức tùy chỉnh để điều khiển hoặc mở rộng các tính năng của đối tượng Roblox.
Thật khó để theo dõi hàng chục đối tượng RemoteEvent riêng lẻ để thực hiện mạng trong một trò chơi. Bạn có thể sử dụng một ModuleScript để đóng gói một RemoteEvent duy nhất để giúp đơn giản hóa vấn đề này. Bằng cách bao gồm một đối số id duy nhất, bạn vẫn có thể gửi các thông điệp mạng khác nhau trong khi chỉ sử dụng một RemoteEvent.
Ví dụ dưới đây, ModuleScript có tên NetworkManagerClient đóng gói phương thức RemoteEvent:FireServer() để bao gồm đối số id bổ sung này. Ngoài ra, ModuleScript này tham chiếu đến đối tượng RemoteEvent tự nó, vì vậy bạn không cần phải tham chiếu nó ở các phần khác trong mã của bạn. Bạn chỉ cần yêu cầu ModuleScript này để gửi các thông điệp mạng và không cần xử lý các đối tượng RemoteEvent ở phần còn lại của mã nguồn của bạn.
Ví dụ ModuleScript sau trong ReplicatedFirst cung cấp một hàm được đóng gói mà bạn có thể gọi trên các client scripts của bạn để gửi một thông điệp mạng:
Network Module
-- ModuleScript in ReplicatedFirst named NetworkManagerClient
local NetworkManagerClient = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
-- Đóng gói hàm FireServer của đối tượng remote
function NetworkManagerClient.FireServer(id, ...)
remoteEvent:FireServer(id, ...)
end
return NetworkManagerClient
Ví dụ ModuleScript sau trong ServerScriptService sử dụng BindableEvents để mỗi script kết nối với một ID cụ thể. Khi một client gửi một thông điệp mạng, mỗi BindableEvent liên kết với ID được chỉ định sẽ được kích hoạt.
-- ModuleScript in ServerScriptService named NetworkManagerServer
local NetworkManagerServer = {}
local networkSignalList = {}
function NetworkManagerServer.GetServerEventSignal(id)
local bindableEvent = Instance.new("BindableEvent")
-- Liên kết BindableEvent mới với id
table.insert(networkSignalList, {
id = id,
bindableEvent = bindableEvent,
})
return bindableEvent.Event
end
-- Kết nối tới
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEvent")
remoteEvent.OnServerEvent:Connect(function(player, id, ...)
-- Tìm mỗi sự kiện bindable khớp với ID của sự kiện remote nhận được
for _, signal in networkSignalList do
if signal.id == id then
signal.bindableEvent:Fire(player, ...)
end
end
end)
return NetworkManagerServer
Ví dụ LocalScript gửi một thông điệp với ID RequestA kèm theo tham số tùy chọn Hello.
-- LocalScript in ReplicatedFirstlocal ReplicatedFirst = game:GetService("ReplicatedFirst")local NetworkManagerClient = require(ReplicatedFirst:WaitForChild("NetworkManagerClient"))NetworkManagerClient.FireServer("RequestA", "Hello")
Ví dụ Script kết nối với ID thông điệp mạng RequestA và in ra một câu thông báo kèm theo bất kỳ tham số bổ sung nào khi nhận được yêu cầu.
-- Script in ServerScriptService
local ServerScriptService = game:GetService("ServerScriptService")
local NetworkManagerServer = require(ServerScriptService:WaitForChild("NetworkManagerServer"))
NetworkManagerServer.GetServerEventSignal("RequestA"):Connect(function(player, ...)
print("Nhận RequestA từ", player, ...)
end)