Modules
Keep one shared table of properties and functions for the whole game: a registry, a service or a catalog.
A module file declares one table. Every property and function in it is public to any file that requires it. Inside its own functions the module is called module, and whatever it keeps to itself is plain Luau above the declaration.
A module
A server module that opens a wallet for every player who joins and gives them coins:
Wallet/Bank.shot
types script.Parent.Types
local Players = game:GetService("Players")
local Wallet = require(script.Parent.Wallet)
local started = false
local function whole(amount : number) : number
return math.max(0, math.floor(amount))
end
server module Bank = {
doc { Every player's wallet }
Wallets : {[Player] : Wallet} = {}
Count : number = 0
doc { Opens a wallet for the player; returns false if they have one }
function Open(player : Player) : boolean
if module.Wallets[player] then
return false
end
module.Wallets[player] = Wallet.Empty()
module.Count += 1
return true
end
function Give(player : Player, amount : number)
local wallet = module.Wallets[player]
if wallet then
wallet:Add(whole(amount), "Pickup")
end
end
function Start()
if started then
return
end
started = true
Players.PlayerAdded:Connect(function(player : Player)
module.Open(player)
end)
end
}Using it, in server code:
local Bank = require(@shot.Wallet.Bank)
Bank.Start()
print(Bank.Count, "wallets open")Properties
Name : Type = value- Every property needs a type and a value ("module Bank: property Count needs a value (Count : type = value)").
- Inside the module, read and write them as module.Count (module is explained below); other files use Bank.Count.
- A value may span several lines. For a long value, building it in a local above the module also reads well:
local PICKUP_VALUES : {[string] : number} = {
copper = 1,
gold = 10,
}
server module Bank = {
PickupValues : {[string] : number} = PICKUP_VALUES
...
}Functions
function Name(params) : Return
...
end- Called with a dot: Bank.Open(player). There is no self, so never call them with a colon.
- Inside the module, call the others through module: module.Open(...). A bare Open(...) is "Unknown global 'Open'".
- Give every parameter a type (see Classes, PARAMETERS). Type parameters are fine:
function Map<T, U>(list : {T}, f : (T) -> U) : {U}Module: the module's own name
Inside a module's functions, module is the module itself: module.Count, module.Open(player). It reads apart from other modules at a glance (Market.Server.Start() is another module, module.Start() is this one), and it is typed exactly like the module, so autocomplete and the type check work through it.
- It compiles to one line right after the table: local module = Bank.
- The module's name still works (Bank.Count), but write module. inside the module and keep the name for other files.
- module is reserved in every .shot file. Used outside a module, it is an error:
module only works inside a module: in class Shape use self (Shape.Name for statics and class properties)
module only works inside a module: func NextId has no table of its own- A local or parameter named module is an error too:
module is a module's own name in ShotScript: rename this one- In a class, self is the object; there is no module.
Required functions
A function the game must call somewhere, or it silently stops doing its job, is marked required:
server module Bank = {
doc {Gives every player who joins a wallet. Call it once when the server starts}
required function Start()
print("opening a wallet for every player who joins")
end
}shot check reports a required function that no code calls:
Wallet/Bank.shot:5 required Bank.Start is never called: the game runs without itCalls are found the way the NEVER CALLED list of shot overview finds them (see The shot command): by name, then by a type check. A call made through a value typed any can't be seen, so it doesn't count; call it through the module itself.
Everything is public
A module has no public / internal: every property and function is visible to anyone who requires it. Anything the module keeps to itself is plain Luau above the declaration:
- state in locals (started above),
- helper functions as local functions (whole above).
The name exists from its declaration down
The compiler turns "server module Bank = {" into "local Bank = ...". Code above that line can't use the name:
local function countAll() : number
return Bank.Count -- Unknown global 'Bank'; consider assigning to it first
end
server module Bank = { ... }module is the same: it exists from the declaration down.
What to do instead:
- Make the helper a module function (module.CountAll() inside, Bank.CountAll() outside).
- Or pass it what it needs: local function countAll(wallets : {[Player] : Wallet}) : number.
- Code below the declaration may use the name, for example to hook up events:
server module Bank = { ... }
Players.PlayerAdded:Connect(function(player : Player)
Bank.Open(player)
end)The same holds for classes and funcs.
Sides
server module Bank = { ... } | only server code may require it |
client module Hud = { ... } | only client code may require it |
module Toast = { ... } | shared: may only require shared code |
For one name with a client and a server version, use a split module (Server and client, SPLIT MODULE).
Class or module?
- Many objects, each with its own state (an item, a player's wallet): a class.
- One of it in the whole game (a registry, a service, a catalog): a module.
- Just one function: a func (see Funcs).
