Am I using module Scripts the right way?

I’m sorry if this is the wrong category for asking this but I saw it most fitting for my question.
I’ve never worked with someone else other than me so I was wondering if I’m using moduleScripts
the right way.

Usually I do format scripts like this :
image

With one normal scripts controlling and using the modulescripts to preform “X” In this case sound.
For example the Main script updates SoundClass Each frame.

Is this way of using moduleScripts causing unnecessary strain or is this the intended use for them?

1 Like

You are using ModuleScripts kind of the right way. Separating functions into individual ModuleScripts is best for adhering to the DRY (Don’t Repeat Yourself) rule, yes, but having a single LocalScript handling ALL of your ModuleScripts can get pretty messy. Especially later down into development where you have a bunch of ModuleScripts but your main LocalScript where you handle everything is like thousands of lines long. Not really ideal.

What you should start doing is to have another ModuleScript inside ReplicatedStorage that will essentially “load” your ModuleScripts when passed as an argument by calling a function. Personally I use start to load my ModuleScripts, you can use something like Initialize or what not.

Here, I have already written you a ModuleScript that will do just that.

type LoaderType = {
	LoadedModules: {},
	Initialized: boolean,
	InitializedEvent: BindableEvent,
	
	Load: ({ModuleScript}) -> (),
	Get: (string) -> (any),
	isInitialized: () -> (boolean)
}

local Loader: LoaderType = {} :: LoaderType
Loader.LoadedModules = {}
Loader.Initialized = false
Loader.InitializedEvent = Instance.new("BindableEvent")

function Loader.Load(
	modules: {ModuleScript}
)
	for _, obj in modules do
		if not obj:IsA("ModuleScript") then continue end
		if Loader.LoadedModules[obj.Name] then continue end
		
		local module = require(obj)
		if module.start then
			task.spawn(module.start)
			Loader.LoadedModules[obj.Name] = module
		end
	end

	Loader.Initialized = true
	Loader.InitializedEvent:Fire()
end

function Loader.Get(
	moduleName: string
)
	if not Loader.isInitialized() then
		Loader.InitializedEvent.Event:Wait()
	end
	
	local module = Loader.LoadedModules[moduleName]
	return module
end

function Loader.isInitialized(): boolean
	return Loader.Initialized
end

return Loader

Afterwards, you’re going to restructure your ModuleScripts to have a ‘start’ function just like so

--!strict
export type ClassType = {
	start: () -> (),
	foo: () -> ()
}

local ModuleA: ClassType = {} :: ClassType

function ModuleA.start(): ()
	print("'ModuleA' started!")
end

function ModuleA.foo(): ()
	print("bar")
end

return ModuleA

And then inside your LocalScript you’re going to load your ModuleScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Loader = require(ReplicatedStorage.Loader)

local dependencies = script.parent.dependencies

Loader.Load(
	dependencies:GetChildren()
)

You can also grab other ModuleScripts through the Loader module and run their functions as well upon startup

--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Loader = require(ReplicatedStorage.Loader)

export type ClassType = {
	start: () -> ()
}

local ModuleB: ClassType = {} :: ClassType

function ModuleB.start()
	print("'ModuleB' started!")
	
	local moduleA = Loader.Get("ModuleA")
	moduleA.foo() -- Prints "bar" in output
end

return ModuleB

What I like about this approach is that it keeps everything tidy and I personally believe this should be standard practice. Pretty much every single game you see on the front page utilizes this method and I can see why. You can read more about it here

2 Likes

What about loading / initiating modules that have nothing to initiate, like:

local getREG = {}
function getREG.getValue()
	return GetSomeValue()
end
return getREG

Should i just put a Start function in them that does nothing?

Modules that have nothing to initialize would just be outside the folder of whatever you’re initializing and return some function.

In this case, it would just be this:

return function()
   return GetSomeValue()
end

A lot of my utility functions often look like this

1 Like