Architecture
Everything works best when using a multi-script architecture. This means there is no centralized framework, and instead code is just modularized and loaded normally. Multiple scripts are allowed on the client and server, but they must be in their respective structure folders. Ensure a new script is only created when it concerns a different top-level system.
All script, asset, config, and other file names are in pacal case (PascalCase) capitalization.
Avoiding a central framework allows for easy onboarding and non-strict development, which decreases hiring costs. Central frameworks tend to overcomplicate codebases, often leading to memory, typechecking, and design problems.
If you want to learn more about our rationale for not using SSA or central frameworks, read here.
File Structure
Here is the recommended file structure for code:
.
└── Project/
├── ReplicatedFirst/
│ └── Modules
├── ServerScriptService/
│ ├── Scripts
│ ├── Modules
│ └── ServerData
├── ReplicatedStorage/
│ ├── SharedModules/
│ │ └── Packages
│ └── Data
└── StarterPlayer/
└── StarterPlayerScripts/
└── Scripts
And this is how other instances should be structured:
.
└── Project/
├── ReplicatedStorage/
│ ├── Assets
│ └── Remotes
└── ServerStorage/
└── ServerAssets
Which results in this final combined structure:
.
└── Project/
├── ReplicatedFirst/
│ └── Modules
├── ServerScriptService/
│ ├── Scripts
│ ├── Modules
│ └── ServerData
├── ReplicatedStorage/
│ ├── SharedModules/
│ │ └── Packages
│ ├── Data
│ ├── Assets
│ └── Remotes
├── StarterPlayer/
│ └── StarterPlayerScripts/
│ └── Scripts
└── ServerStorage/
└── ServerAssets
ReplicatedFirst
Any client-specific modules should go under Modules under ReplicatedFirst. An example of a client-specific module would be an interface component.
StarterPlayerScripts
Any local scripts should go under Scripts inside of StarterPlayerScripts.
ServerScriptService
Any server scripts should go under Scripts under ServerScriptService, and any server-specific modules should go under Modules. An example of a server-specific module would be a datastore wrapper. Server-only config/constant data should go under the ServerData folder.
ReplicatedStorage
Store any common/shared modules in SharedModules (for example, utility modules). Packages is a folder of packages imported by a package manager, such as Wally (and belongs under Modules). Data is a folder used to store modules or other data that is constant (for example, game config). Assets is used to store non-code instances that is used by client or server. Remotes is exclusively for remote events and remote functions.
Client-only modules should ALWAYS go under ReplicatedFirst/Modules. Server-only modules should ALWAYS go under ServerScriptService/Modules. This better respects the client-server boundary.
Remotes can either be a folder of remote instances or a module representing remotes from a networking module.
ServerStorage
Store any server-specific assets in ServerAssets. For example, you would store maps here.
Modularization
Modularization is important to make sure that functionality is divided into isolated scopes, either through functions or modules. This makes it easy for team members to navigate a codebase and understand what something does at a glance, but more importantly, it makes it easy to scale.
Functions should be reserved for splitting functionality that is part of a larger picture in a script or module. For example, a weapon might have functions to shoot, reload, etc.
Modules are used to separate bigger things to create libraries, databases, and systems. Some examples of things that are their own modules include:
- Configurations
- Utilities
- Systems
- Frameworks
- Classes
- Etc
Variable modularization is the smallest level of splitting up information into something more digestible. This is something you should eyeball, with the general rule of thumb being "if it's readable, it's good”. Make sure to utilize variables for repeated information, and split one variable into more if complex operations or algorithms are being done.
Always require module using dot notation when possible:
local ModuleA = require(path.to.module) -- Good
local ModuleB = require("path/to/module") -- Bad
Don't use a central framework
Seriously, don't. It's annoying to work with and causes friction in too many places. Central frameworks (like Knit) do not solve any real problems.
Generally, we're against single-script architecture. Read more here: https://blog.igottic.com/ssa/