The module framework is an extremely simple way of enabling us to primarily use module scripts for our entire game. Why would this be useful? Module scripts do the exact same thing as a Script or Local Script, but they also allow us to share code among other scripts. Have you ever written code in a script, but as you got further into the project you realized that you need to reuse that code in another script? Rather than making a seperate module script, extracting the code, then requiring it in both scripts, our code will already be in a module script.
What is a Framework?
If you’re unfamiliar with what a framework is, it is the structure of your codebase. Frameworks are created and shared to make developers lives easier. The motivation to create a framework generally results from creating many projects and realizing a pattern or process which can be useful in other or all projects. Here are some common features that frameworks offer:
- Networking
- Lifecycle Events
- Utilities
Why use a Framework?
From the outside, you might not be able to comprehend why a framework is worth using. I was this way for the first year of my Roblox development journey. I saw all the benefits the frameworks showcased, but they all seemed like miniscule benefits or things I could very easily implement myself.
Why do I feel differently about this framework? It’s such a simple, but impactful framework. It doesn’t require you to learn an entire new way of structuring your codebase or writing your logic. It enables you to easily modularize your code by writing all of the logic inside of module scripts. It is no different than writing an ordinary script.
Setup
1. Creating the Module Directories
We will create folders inside of the ServerScriptService and StarterPlayerScripts which will hold all of our Module Scripts containing the logic to our game.
- Create a Folder inside the ServerScriptService called “services”
- Create a Folder inside the StarterPlayerScripts called “controllers”
2. Creating the Module Loader
We’ll now create the script which the Server & Client will use for requiring all the Module Scripts are logic is written in.
- Create a Folder inside the ReplicatedStorage called “Utils”
- Create a Module Script inside the utils Folder called “ModuleLoader”
- Update the module to the following code:
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local RunService = game:GetService("RunService")
local StarterPlayer = game:GetService("StarterPlayer")
local StarterPlayerScripts = StarterPlayer.StarterPlayerScripts
local IsServer = RunService:IsServer()
local RootDirectory = if IsServer then ServerScriptService else Players.LocalPlayer:WaitForChild("PlayerScripts")
local ModuleDirectory = if IsServer then RootDirectory.Services else RootDirectory:WaitForChild("Controllers")
local function RequireModule(module: ModuleScript)
if not module:IsA("ModuleScript") then
return
end
local import = require(module)
local onStart = import.OnStart
if onStart then
onStart()
end
end
return function()
for _, descendant: ModuleScript in ModuleDirectory:GetDescendants() do
RequireModule(descendant)
end
if not IsServer then
ModuleDirectory.DescendantAdded:Connect(function(descendant: ModuleScript)
RequireModule(descendant)
end)
end
end
3. Creating the Loaders
We now need to create a script on both the Server & Client to require the ModuleLoader module we just created.
- Create a Script named “Loader” inside both the ServerScriptService & the StarterPlayerScripts
- Update the scripts to the following code:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ModuleLoader = require(ReplicatedStorage.utils.ModuleLoader)
ModuleLoader()
Creating our Main Logic Modules
We can now add Module Scripts to the services
& controllers
folders we created earlier. All of our Server logic will be stored in the services
folder, while all of the Client logic will be stored inside controllers
.
- Create a Module Script inside the
controllers
folder named “TestController” - Create a Moudle Script inside the
services
folder named “TestService” - Update them with the following code, but change the module variable to
TestService
when pasting it into the service:
local TestController = {}
function TestController.OnStart()
print("Started")
end
function TestController.Test()
end
return TestController
Lifecycle Event
Our Services & Controllers both contain the OnStart
function. This is because we included that function call inside the ModuleLoader
logic, which we can see a snippet of below.
...
local import = require(descendant)
local onStart = import.OnStart
if onStart then
import.OnStart()
end
...
After we require each Module Script, we then check if they have a member named OnStart
and if they do, we call it expecting it to be a function. Each module is not required to include this function, it is optional.
Why is the helpful? Rather than storing all of your initial function calls towards the bottom of your script, you can organize them all inside of a single function. You can include this pattern for each script, which means you’ll know exactly where to look if you’re searching for the initial function calls.
Use Case
In one of my projects I had to create a Script & Module Script to manage my player’s data. The module script was only a few lines long because it was only used to share a variable which held each player and their data, which could be used by other scripts. Of course I couldn’t make this all in a single module script as they do not automatically load.
Before
PlayerData – Script
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local ProfileService = require(ServerScriptService.Libs.ProfileService)
local ProfileDataManager = require(ServerScriptService.ProfileDataManager)
...
PlayerDataManager – Module Script
local ProfileDataManager = {}
ProfileDataManager.Profiles = {}
return ProfileDataManager
After
PlayerDataService – Module Script
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local ProfileService = require(ServerScriptService.Libs.ProfileService)
local ProfileDataManager = {}
ProfileDataManager.Profiles = {}
...
return ProfileDataManager
Other Frameworks
The framework you saw today was created from Sleitnick’s mention of the idea in a medium article.
- Knit – Most popular and includes networking
That seems like the only commonly used framework in the community. I have seen others, but not mentioned enough to feel like I could reliably list them here. If you have suggestions, let me know!