Our first project
As discussed in the Core Concepts article, workspaces are made up of projects.
In the previous article, we created our workspace file, which also defines our project root, which we'll
use // to reference in paths, see below for an example. This type of syntactic sugar (of which you'll find plenty in
Gaffer) is supported in all paths; ranging from includes to outputs.
The project file
We'll create a project for our game's lobby, since this'll be our 'start place' on Roblox. Remember that Gaffer is unopinionated,
we can create this project file anywhere in our workspace; but lets put it in a projects/ folder to keep everything clean.
//projects/lobby/project.toml
name = "lobby"
edition = 0
[outputs]
build = "//artifacts/lobby.rbxl"
Since we're at it, we'll also create our //artifacts directory. Lets put a .gitkeep file there.
This doesn't have any special meaning, but it signifies to other programmers that the directory is purposefully
empty; remember that Git ignores empty directories.
The code
Coinciding with standard game architecture, we'll create a server, client, and shared folder within our lobby. This'll
store the code for our lobby place.
For our server/init.server.lua and client/init.client.lua files, we can just put a print("Hello World!") to make sure
everything works. We'll leave shared empty for now; but remember that Git ignores empty directories, so if you commit an empty
shared directory, other people will have to re-create it before they build.
We'll create a tree file in the same directory as our project.toml file. This tree file represents how our
place will look when we build it.
//projects/lobby/tree.json
{
"ReplicatedStorage": {
"Shared": "./shared"
},
"ServerScriptService": {
"Server": "./server"
},
"StarterPlayer": {
"StarterPlayerScripts": {
"Client": "./client"
}
}
}
Anyone whose used Rojo before is probably a bit confused; there's so much wrong with this!!!
- We're not defining our Tree as having a
$classNameof DataModel - We're not defining the
$classNameof our services or ofStarterPlayerScripts(although this is optional in Rojo) - We're not including
$path, instead just setting our nodes to strings
Yet if you run gaffer build, you'll get a perfect place file, all up to scratch. What is this dark magic? Well, it's
actually pretty simple. Since Gaffer already is managing our Rojo project files, we use that opportunity to add some syntactic sugar:
- There's a
base-tree.jsonfile which applies some sensible defaults, like enabling HttpService, setting default lighting, and also setting the root$classNametoDataModel. - With Gaffer trees, any 'node' that doesn't have a
$classNameor$pathwill have its className set to the node's key.- While Rojo does do this for services and other well-known singletons (like StarterPlayerScripts), Gaffer is more exhaustive and will add a
$classNamefor all instances. This can include things likeBlurEffects. - Heads up though, unclassified nodes that have children are not inferred by Gaffer, and Rojo will usually assume these are folders.
- While Rojo does do this for services and other well-known singletons (like StarterPlayerScripts), Gaffer is more exhaustive and will add a
- When a node is set to a string, Gaffer assumes we want that node to reference to that path. So Gaffer will transform it for us.
If we pop open our project's .project.json file and prettify it, we'll see a valid Rojo project file:
{
"name": "lobby",
"tree": {
"$className": "DataModel",
"HttpService": {
"$properties": { "HttpEnabled": true },
"$className": "HttpService"
},
"SoundService": {
"$properties": { "RespectFilteringEnabled": true },
"$className": "SoundService"
},
"Lighting": { ... },
"ReplicatedStorage": {
"Shared": { "$path": "./shared" },
"$className": "ReplicatedStorage"
},
"ServerScriptService": {
"Server": { "$path": "./server" },
"$className": "ServerScriptService"
},
"StarterPlayer": {
"StarterPlayerScripts": {
"Client": { "$path": "./client" },
"$className": "StarterPlayerScripts"
},
"$className": "StarterPlayer"
}
}
}
These generated project files are not meant to be read by humans, so they'll look a bit weird, but you can see how it's all there. Plus, it's included the default Roblox Lighting for us; you can read the base-tree.json file here to see what gets set.
If you're creating a plugin or model, or are introducing Gaffer in an existing project and don't want the base tree, you can add use_base_tree = false in your project.toml file to disable it for that project. Heads up though that any workspace-level includes will still be included even if the base tree is disabled.