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 Bank

A 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, interfacewhat a file declares (one per file)
server, clientput before class / module / func: which side may use it
split module, diffsource funcone name with a client and a server version
public, internal, override, staticclass members
constructora class's creating function
init()inside a class: a new, empty object of the class
selfinside a class: the object
moduleinside a module: the module itself (module.Count, module.Add())
extendsinheritance
implementsa class promises an interface's members (Interfaces)
requireda function the game must call somewhere (Modules)
matchcode by the value of a string union, every value covered (Match)
usesin Types.shot: the systems this one may use (Uses)
eventin Types.shot: an EventBus event this system publishes (Events)
idin Types.shot: a kind of string id this system defines (Id kinds)
ownsin Types.shot: only this system writes a field (Owned fields)
after, beforeon a Start's header: which start calls come first (Start order)
invariantin Types.shot: a rule shot run checks after every frame (Invariants)
useshort for a require: use Signal (the nearest one) or a path
types, import, public type, internal typethe 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|disabledfirst line of a script
@shotReplicatedStorage.Shot in paths
Fileshow a project is laid out, paths, .instance files
Remotessignatures on .instance files: what crosses the network, typed Fire and handlers
Classesclasses, class properties, constructors, methods, statics, inheritance, XClass
Modulesmodules
Funcsfuncs
Scripts@client / @server scripts and Init
Server and clientserver, client and shared code, split modules, diffsource funcs
TypesTypes files, what each file sees, imports
Useswhich systems a system may use
Eventstyped EventBus events: declared once, checked at every publish and listener
Id kindsid kinds: string ids one system defines, checked wherever they are written
Owned fieldswhich system may change a field, and through which function
Start orderwhich Starts must run before which, checked against the start script
Saved data lockSaved.lock: the shape each saved section promises to old saves, Version and Migrate checked
Invariantsrules that always hold across systems, checked by shot run after every frame
Matchmatch on a string union
Interfacesinterfaces and implements
Doc commentsdoc and rootdoc
The shot commandshot map, outline, show, find, check, and the workflow for a change
Scenariostesting a change: your own scenarios, played by shot run, never part of the game
Rulesevery error shot check gives
Luau tipstype-checker traps and their fixes
Compiledwhat the compiled Luau looks like

In a terminal, read one with "shot docs <name>" and search them all with "shot docs search <text>".