Using the advice from @BuildIntoGames and @lArekan , I’ve made a system which works as follows:
For the server scripts, I have one Script named ‘ServerMain’ containing all the server modules:
For the client, I have a LocalScript located in StarterPlayerScripts with all the client modules located in a folder in ReplicatedStorage:
Both the ServerMain and LocalScript require a module called ‘MainFramework’ located in Universal in Modules:
-- << RETRIEVE FRAMEWORK >>
local main = require(game:GetService("ReplicatedStorage").Modules.Universal.MainFramework)
main.Initialize()
local modules = main.modules
MainFramework contains all the services and widely used variables for both server and client:
local main = {}
function main.Initialize()
-- << SERVICES >>
main.rs = game:GetService("ReplicatedStorage")
main.ss = game:GetService("ServerStorage")
main.sss = game:GetService("ServerScriptService")
main.players = game:GetService("Players")
-- << MAIN VARIABLES >>
main.revents = main.rs.RemoteEvents
main.moduleGroup = nil
if game.Players.LocalPlayer ~= nil then
main.moduleGroup = main.rs.Modules
main.player = game.Players.LocalPlayer
else
main.moduleGroup = main.sss.ServerMain
end
-- << SETUP MODULES >>
main.modules = {}
for a,b in pairs(main.moduleGroup:GetDescendants()) do
if b.ClassName == "ModuleScript" then
main.modules[b.Name] = require(b)
end
end
for a,b in pairs(main.rs.Modules.Universal:GetDescendants()) do
if b.ClassName == "ModuleScript" and b.Name ~= script.Name then
main.modules[b.Name] = require(b)
end
end
end
return main
Note: I’ve cut out a lot of the services and variables I use to make it easy to read
Finally, in every module I retrieve the ‘framework’:
local module = {}
-- << RETRIEVE FRAMEWORK >>
local main = require(game:GetService("ReplicatedStorage").Modules.Universal.MainFramework)
local modules = main.modules
-- Module stuff here
return module
This enables all modules to require any module they need (without the risk of cyclic calls) and allows them to reference any variable defined in MainFramework (e.g. print(main.player.Name)
would print the local player’s name for Client modules).
Hope this helps any future readers looking to modularise their code