Reusing Code | Documentation - Roblox Creator Hub (2024)

After creating a few scripts, it's never long before you want to reuse some code between them. Depending on location, ModuleScripts let you reuse code between scripts on different sides of the client-server boundary or the same side of the boundary.

Creating Module Scripts

You can put module scripts anywhere that you put scripts, but ReplicatedStorage is a popular location; storing module scripts here lets you reuse code between the server and clients.

  1. In Roblox Studio, hover over ReplicatedStorage in the Explorer window and click +.

  2. Select ModuleScript to add a new module script.

  3. Right-click the script and rename it to PickupManager.

  4. Double-click the script to open it in the Script Editor.

Anatomy of a Module Script

Each ModuleScript starts with the following code:

local module = {}

return module

This code creates an empty Luau table and returns it to any script that requires the module script.

The return value can be any data type except for nil, but most module scripts return a function, a table, or a table of functions. To generate its return value, module scripts can of course run arbitrary code, which includes requiring other module scripts.

Be careful not to have module scripts require each other in a circular manner, which results in a Requested module was required recursively error.

The following example returns a table with a single function called getPickupBonus. Paste it into your new module script:

-- ModuleScript in ReplicatedStorage

local PickupManager = {}

local defaultMultiplier = 1.25

local rarityMultipliers = {

common = 10,

uncommon = 20,

rare = 50,

legendary = 100

}

-- Add the getPickupBonus function to the PickupManager table

PickupManager.getPickupBonus = function(rarity)

local bonus = rarityMultipliers[rarity] * defaultMultiplier

return bonus

end

return PickupManager

Adding the function to a table isn't strictly necessary—you could just return the function itself—but it's a good pattern to follow; it gives you an easy-to-understand syntax when you call the function from another script and lets you easily add more functions to the module script over time.

Requiring Module Scripts

To load a module script, you call the require() function. In ReplicatedStorage, add a new script, and change its RunContext to Client. Then add the following code to call the PickupManager.getPickupBonus function:

Client script in ReplicatedStorage

local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Get value returned by ModuleScript

local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))

-- Call a ModuleScript function

local bonus = PickupManager.getPickupBonus("legendary")

print(bonus) --> 125

You can use the same code to require the script from ServerScriptService:

Script in ServerScriptStorage

local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Get the return value for the ModuleScript named "PickupManager"

local PickupManager = require(ReplicatedStorage:WaitForChild("PickupManager"))

The Instance:WaitForChild() pattern is an important safety measure due to the time it can take for an experience to load and the lack of guarantees that Roblox makes around loading order. If you had assurances that everything had loaded, you could call require(ReplicatedStorage.PickupManager), but WaitForChild() is safer.

When you call require() on a ModuleScript, it runs once and returns a single item as a reference. Calling require() again returns the exact same reference, meaning that if you modify a returned table or Instance, subsequent require() calls return that modified reference. The module itself doesn't run multiple times.

If you require a ModuleScript from both sides of the client-server boundary, the ModuleScript returns a unique reference for each side.

Patterns

Module scripts have some common patterns that you can use to simplify your code and avoid pitfalls as your experience grows in size and complexity.

Most of these patterns require an understanding of events. If you're not familiar with them, see Events.

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 sameobject or reuse the same data for different objects, store the data inModuleScripts. It's an easier way for you to reuse the data in other scripts, and you can store tables and functions.

The following example ModuleScript in ReplicatedStorage stores the configuration values for a generic gun:

ModuleScript in ReplicatedStorage

local GunConfig = {}

GunConfig.MagazineSize = 20

GunConfig.AmmoCount = 100

GunConfig.Firerate = 600

GunConfig.Damage = {

["Head"] = 50;

["Torso"] = 40;

["Body"] = 25;

}

return GunConfig

Custom Events

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

You can use ModuleScripts to storeBindableEvents and providecustom event handlers that are directly tied to the methods of ModuleScript.

The following ModuleScript in ReplicatedStorage has a custom event that fires when the switch changes state:

ModuleScript in ReplicatedStorage

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 client script in ReplicatedStorage connects a function to call when the Switch.Changed event fires.

Script in ReplicatedStorage

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Switch = require(ReplicatedStorage:WaitForChild("Switch"))

Switch.Changed:Connect(function(newState)

print("Switch state is now", newState)

end

-- Test the flipping a few times

task.wait(1)

Switch.flip()

task.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.

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 need 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:

Network Module

-- ModuleScript in ReplicatedFirst named NetworkManagerClient

local NetworkManagerClient = {}

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = 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 ReplicatedStorage = game:GetService("ReplicatedStorage")

local remoteEvent = 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 ReplicatedFirst = game:GetService("ReplicatedFirst")

local NetworkManagerClient = require(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 ServerScriptService = game:GetService("ServerScriptService")

local NetworkManagerServer = require(ServerScriptService:WaitForChild("NetworkManagerServer"))

NetworkManagerServer.GetServerEventSignal("RequestA"):Connect(function(player, ...)

print("Received RequestA from", player, ...)

end)

Reusing Code | Documentation - Roblox Creator Hub (2024)

References

Top Articles
Latest Posts
Article information

Author: Sen. Ignacio Ratke

Last Updated:

Views: 6258

Rating: 4.6 / 5 (56 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Sen. Ignacio Ratke

Birthday: 1999-05-27

Address: Apt. 171 8116 Bailey Via, Roberthaven, GA 58289

Phone: +2585395768220

Job: Lead Liaison

Hobby: Lockpicking, LARPing, Lego building, Lapidary, Macrame, Book restoration, Bodybuilding

Introduction: My name is Sen. Ignacio Ratke, I am a adventurous, zealous, outstanding, agreeable, precious, excited, gifted person who loves writing and wants to share my knowledge and understanding with you.