Overview
Write your Roblox game as classes, modules and funcs in plain Luau, and ShotScript writes the metatables, types and boilerplate around them.
This page shows how a project is laid out, walks through a complete small example, lists every keyword ShotScript adds to Luau, and ends with the order to read the other docs in.
What it is
ShotScript is Luau with a thin layer on top. Each .shot file is plain Luau plus one declaration: a class, a module or a func. The compiler (shot) turns every file into a --!strict Luau ModuleScript in ReplicatedStorage.Shot and writes the boilerplate for you: metatables, type definitions, the Types module, re-exports. Everything inside a function body is ordinary Luau, checked by Studio's type checker.
A project is a folder. Its subfolders are systems. Every project has an Init folder with two scripts that start the game.
MyProject/
Init/
Client.shot @client enabled: starts the client
Server.shot @server enabled: starts the server
Wallet/
Types.shot the types of the Wallet system
Wallet.shot class Wallet (named like its folder, so it is the system's entry point)
Bank.shot server module BankA complete small example
A Wallet system in three files (its types, a class for one player's coins and a server module that keeps every player's wallet), and the server script that starts it.
Wallet/Types.shot
doc { Why a player's coins changed }
public type Reason = "Pickup" | "Shop"Wallet/Wallet.shot
types script.Parent.Types
rootdoc {
The coins one player owns.
}
class Wallet = {
doc { Coins the player has now }
public Coins : number
internal _history : {Reason}
constructor new(coins : number)
local self : Wallet = init()
self.Coins = coins
self._history = {}
return self
end
doc { Adds coins and remembers why }
public Add(amount : number, reason : Reason)
self.Coins += amount
table.insert(self._history, reason)
end
public static Empty() : Wallet
return Wallet.new(0)
end
}Wallet/Bank.shot
types script.Parent.Types
local Players = game:GetService("Players")
local Wallet = require(script.Parent.Wallet)
server module Bank = {
Wallets : {[Player] : Wallet} = {}
function Start()
Players.PlayerAdded:Connect(function(player : Player)
module.Wallets[player] = Wallet.Empty()
end)
end
function Give(player : Player, amount : number)
local wallet = module.Wallets[player]
if wallet then
wallet:Add(amount, "Pickup")
end
end
}Init/Server.shot
@server enabled
local Bank = require(@shot.Wallet.Bank)
Bank.Start()What to notice:
- Every file except scripts and Types files starts with "types script.Parent.Types".
- Reason, Wallet and Bank are types every file of the Wallet folder can use without importing anything.
- Plain Luau goes above the declaration (services, requires, constants, helper functions, state).
- Methods are called with ":" (wallet:Add(...)). Constructors, static functions and module functions with "." (Wallet.new(...), Wallet.Empty(), Bank.Give(...)).
- Paths are Roblox paths: script.Parent.Wallet is the file next to this one, @shot is ReplicatedStorage.Shot.
- shot check finds mistakes in all of it and reports them at the .shot line and column.
Keywords
class, module, func, interface | what a file declares (one per file) |
server, client | put before class / module / func: which side may use it |
split module, diffsource func | one name with a client and a server version |
public, internal, override, static | class members |
constructor | a class's creating function |
init() | inside a class: a new, empty object of the class |
self | inside a class: the object |
module | inside a module: the module itself (module.Count, module.Add()) |
extends | inheritance |
implements | a class promises an interface's members (Interfaces) |
required | a function the game must call somewhere (Modules) |
match | code by the value of a string union, every value covered (Match) |
uses | in Types.shot: the systems this one may use (Uses) |
event | in Types.shot: an EventBus event this system publishes (Events) |
id | in Types.shot: a kind of string id this system defines (Id kinds) |
owns | in Types.shot: only this system writes a field (Owned fields) |
after, before | on a Start's header: which start calls come first (Start order) |
invariant | in Types.shot: a rule shot run checks after every frame (Invariants) |
use | short for a require: use Signal (the nearest one) or a path |
types, import, public type, internal type | the Types system |
doc { }, rootdoc { } | documentation |
─┨ Name ┠──┨ | a bookmark: a section banner, a comment (Files, BOOKMARKS) |
section Rules { } (inside a doc) | a named part of a doc: Rules, Extend, Examples, or your own |
@client / @server enabled|disabled | first line of a script |
@shot | ReplicatedStorage.Shot in paths |
Read next
| Files | how a project is laid out, paths, .instance files |
| Remotes | signatures on .instance files: what crosses the network, typed Fire and handlers |
| Classes | classes, class properties, constructors, methods, statics, inheritance, XClass |
| Modules | modules |
| Funcs | funcs |
| Scripts | @client / @server scripts and Init |
| Server and client | server, client and shared code, split modules, diffsource funcs |
| Types | Types files, what each file sees, imports |
| Uses | which systems a system may use |
| Events | typed EventBus events: declared once, checked at every publish and listener |
| Id kinds | id kinds: string ids one system defines, checked wherever they are written |
| Owned fields | which system may change a field, and through which function |
| Start order | which Starts must run before which, checked against the start script |
| Saved data lock | Saved.lock: the shape each saved section promises to old saves, Version and Migrate checked |
| Invariants | rules that always hold across systems, checked by shot run after every frame |
| Match | match on a string union |
| Interfaces | interfaces and implements |
| Doc comments | doc and rootdoc |
| The shot command | shot map, outline, show, find, check, and the workflow for a change |
| Scenarios | testing a change: your own scenarios, played by shot run, never part of the game |
| Rules | every error shot check gives |
| Luau tips | type-checker traps and their fixes |
| Compiled | what the compiled Luau looks like |
In a terminal, read one with "shot docs <name>" and search them all with "shot docs search <text>".
