ModuleScript Patterns

You can solve many common problems by sharing code or abstracting over objects with ModuleScripts. For example, you can:

You can use these patterns to simplify your code and provide more flexibility over the features Roblox Studio provides. By incorporating these ModuleScript patterns into your development, you can avoid common pitfalls as your Roblox experience grows in size and complexity.

Data Sharing

To associate data with individual objects, you can assign attributes to them or create Configuration folders with value objects such as StringValue or IntValue. However, both approaches are troublesome if you want to add or modify dozens of objects or data values. They also don't store tables or functions.

If you want to modify the same data for multiple copies of the same object or reuse the same data for different objects, store the data in ModuleScripts. It's an easier way for you to reuse the data in other scripts, and you can store tables and functions.

Example: Weapon Stats

The following ModuleScript in ReplicateStorage stores the configuration values for a generic gun.


-- ModuleScript in ReplicatedStorage named GunConfig
local GunConfig = {}
GunConfig.MagazineSize = 20
GunConfig.AmmoCount = 100
GunConfig.Firerate = 600
GunConfig.Damage = {
["Head"] = 50;
["Torso"] = 40;
["Body"] = 25;
}

Custom Events

Bindable events enable scripts to communicate with each other, but having to keep track of references to individual BindableEvent objects may clutter your code.

You can use ModuleScripts to store BindableEvents and provide custom event handlers that are directly tied to the methods of ModuleScript.

Example: Switch Module

The following ModuleScript in ReplicateStorage has a custom event that fires when the switch changes state.


-- ModuleScript in ReplicatedStorage named Switch
local Switch = {}
-- Creating bindable so any script can listen to when the switch was changed
local bindableEvent = Instance.new("BindableEvent")
Switch.Changed = bindableEvent.Event
local state = false
function Switch.flip()
state = not state
bindableEvent:Fire(state)
end
return Switch

The following LocalScript in ReplicatedFirst connects a function to call when the Switch.Changed event fires.


-- LocalScript in ReplicatedFirst
local Switch = require(game.ReplicatedStorage:WaitForChild("Switch"))
Switch.Changed:Connect(function(newState)
print("Switch state is now", newState)
end
-- Test the flipping a few times
wait(1)
Switch.flip()
wait(1)
Switch.flip()

Encapsulation

Encapsulation is the practice of creating a layer of abstraction around objects or scripting logic to hide complexity. You can use ModuleScripts to encapsulate Roblox objects with custom Lua functions to simplify code.

For example, you can use encapsulation to:

  • Simplify cross-network communication with a single RemoteEvent object.
  • Wrap error handling code around sensitive services such as DataStoreService.
  • Define custom methods to control or extend Roblox object features.

Example: Network Module

It's difficult to keep track of dozens of individual RemoteEvent objects to implement networking in your game. You can use a ModuleScript to encapsulate a single RemoteEvent to help simplify this problem. By including a unique id argument, you can still send different network messages while only using a single RemoteEvent.

In the example below, the ModuleScript named NetworkManagerClient encapsulates the RemoteEvent:FireServer() method to include this extra id argument. Additionally, this ModuleScript references the RemoteEvent object itself so you don't have to reference it in other parts of your code. You only needs to require this ModuleScript to send network messages and don't need to deal with RemoteEvent objects in the rest of your codebase.

The following ModuleScript in ReplicatedFirst provides an encapsulated function that you can call on your client scripts to send a network message.


-- ModuleScript in ReplicatedFirst named NetworkManagerClient
local NetworkManagerClient = {}
local remoteEvent = game.ReplicatedStorage:WaitForChild("RemoteEvent")
-- Encapsulating the remote object's FireServer function
function NetworkManagerClient.FireServer(id, ...)
remoteEvent:FireServer(id, ...)
end
return NetworkManagerClient

The following ModuleScript in ServerScriptService uses BindableEvents for every script to connect to a specific id. When a client sends a network message, each BindableEvent associated with the specified id fires.


-- ModuleScript in ServerScriptService named NetworkManagerServer
local NetworkManagerServer = {}
local networkSignalList = {}
function NetworkManagerServer.GetServerEventSignal(id)
local bindableEvent = Instance.new("BindableEvent")
-- Linking the new BindableEvent to the id
table.insert(networkSignalList, {
id = id;
bindableEvent = bindableEvent;
})
return bindableEvent.Event
end
-- Connecting to
local remoteEvent = game.ReplicatedStorage:WaitForChild("RemoteEvent")
remoteEvent.OnServerEvent:Connect(function(player, id, ...)
-- Finding every bindable event that matches the id of the received remote event
for _, signal in next, networkSignalList do
if signal.id == id then
signal.bindableEvent:Fire(player, ...)
end
end
end)
return NetworkManagerServer

The following LocalScript sends a message with the id "RequestA" with an optional "Hello" argument.


-- LocalScript in ReplicatedFirst
local NetworkManagerClient = require(game.ReplicatedFirst:WaitForChild("NetworkManagerClient"))
NetworkManagerClient.FireServer("RequestA", "Hello")

The following Script connects to the network message id "RequestA" and prints out a statement with any additional parameters when it receives the request.


-- Script in ServerScriptService
local NetworkManagerServer = require(game.ServerScriptService:WaitForChild("NetworkManagerServer"))
NetworkManagerServer.GetServerEventSignal("RequestA"):Connect(function(player, ...)
print("Received RequestA from", player, ...)
end)