Split one giant script into reusable, readable pieces with ModuleScripts and require.
Your clicker works, but all the logic is stuffed into one Script. It's getting hard to read, and the constants (prices, click values) are buried mid-code. Real Roblox projects split logic into ModuleScripts — separate files that other scripts can load and reuse.
A ModuleScript is a file that returns a value when it's loaded. The loading is done by require, and the returned value can be anything: a number, a table of config, a table of functions. Its superpower is that every script that requires it shares the same copy — change one line in the module and every user sees it.
What does require return?
Create a ModuleScript in ReplicatedStorage named GameConfig, and put this in it:
local config = {
coinsPerClick = 1,
upgradeBaseCost = 25,
saveInterval = 60,
}
return config
That's it. A ModuleScript is a script that ends in return. Load it anywhere with:
local GameConfig = require(ReplicatedStorage.GameConfig)
print(GameConfig.coinsPerClick) --> 1
Now your prices live in one obvious place. Change upgradeBaseCost and every script that requires it sees the new price — no hunting through a giant file.
A ModuleScript gives require its value with a final statement.
Config is just the start. Modules can hold logic:
local CoinMath = {}
function CoinMath.rewardForClick(multiplier)
return 1 * multiplier
end
function CoinMath.upgradeCost(baseCost, currentLevel)
return baseCost * (currentLevel + 1)
end
return CoinMath
local CoinMath = require(ReplicatedStorage.CoinMath)
local reward = CoinMath.rewardForClick(3)
print(reward) --> 3
The empty local CoinMath = {} starts an empty table, functions get attached to it, and the table is returned. This is the standard way to organize: one module per concern. Prices, coin math, UI builders — each gets its own file.
A ModuleScript doesn't run at all until something requires it — and it runs once, with the result shared by everyone who requires it. That sharing is why config and math belong in modules: one source of truth.
A ModuleScript holds a function that doubles a number. Write the module's final line so require hands back the table.
local CoinMath = {}
function CoinMath.double(n)
return n * 2
end
-- your line here
Move the upgrade prices out of the server Script into GameConfig:
GameConfig in ReplicatedStorage.local GameConfig = require(ReplicatedStorage.GameConfig).GameConfig.coinsPerClick and GameConfig.upgradeBaseCost.Run it — behavior identical, but now the numbers live in one obvious file. That's refactoring: same behavior, better structure.
Two scripts require the same ModuleScript. What do they get?
return. A ModuleScript with no return gives require nil — and every use of it errors. The return is the whole point.print("loaded") in a module fires once, when first required. Keep modules as pure data/logic.That closes the Game Systems section: interfaces, economies, and organization. Next section: objects — the parts, players, and effects that bring the world to life.