Getting started gets the collection onto your machine and a game running. This page is for the next step: you want to make a game that anybody running a server can install and anybody with the client can play, without downloading anything but your game.
1. Start from the template
dot-game-template is a small, complete, networked game, Coin Grab, written the way the platform wants a game written: server-authoritative, predicted on the client, delivered as a signed pack, and playable offline from the same scene. Press Use this template on GitHub, or clone it, then rename it once:
git clone --depth 1 https://github.com/modcommunity/dot-game-template crate-rush
cd crate-rush && rm -rf .git && git init -b main
tools/rename.sh cr crate-rush
The prefix (cr) is what keeps your files apart from every other game a server has installed, because a delivered game has no class_name: the script renames every tpl_ file and Tpl name, and every mention of the template’s repository, to yours. Use your repository’s exact name.
No class_name in a game, and no bare res:// paths
A game is mounted at runtime under a path that depends on its version, so it cannot declare a global class name and must wrap the paths of its own files (TplPaths.rebase() in the template). The template’s pack suite refuses both, and CI runs it.
2. Link the addons
The addons are not copied into your repository. The /addons/<name> lines in .gitignore are the list, and one script reads it and links them:
git clone https://github.com/modcommunity/dot-ci ../dot-ci
../dot-ci/scripts/resolve-deps.sh .
godot --headless --path . --import
Re-run the import whenever an addon adds a class_name: each project keeps its own class cache, and a stale one reads like a broken addon (Identifier "DotX" not declared). To use another addon, add its line to .gitignore and link again. Working inside the whole family? dot-bootstrap does all of this for every repository at once.
3. Run it, host it, test it
The template checks itself with tools/check.sh (import, parse every script, every suite, and the rename). The reference games also carry a ./game.sh with one verb for each thing you do all day, and the same script is in every one of them, so it is worth knowing what they are:
| Command | What it does |
|---|---|
./game.sh or ./game.sh play |
Play the game offline, in a window. Arguments after -- go to the game. |
./game.sh online |
A local server plus the browser client, and it prints the link. ./game.sh online down stops it. |
./game.sh server |
A dedicated server only. server check starts it, loads the game, stops, and exits 0 if that worked. |
./game.sh test |
Parse every script, then run every suite, each under a timeout, and read each suite’s stderr. test NAME... runs only those suites. |
./game.sh shot |
Save a screenshot to screenshots/. |
./game.sh soak [MAP] [MIN] |
Run bots headless for a while and judge the result: Bot soak testing. |
./game.sh suites |
List the suites. |
Options go before the command: ./game.sh --no-parse test, never test --no-parse. online and server use dot-server-deploy beside your repository and rebuild your game’s pack first, so what you play is what is on disk now.
Look at it, too
No assertion reaches an interface or a motion. Every view or UI change wants a frame rendered and looked at; that is what shot is for.
The template’s README walks the same ground by hand, including publishing a pack into a local server and joining it from two clients; read it when you want to see what online is doing for you.
4. The five ideas under every line
These are the conventions in dot-core that the rest of the family assumes. The longer version, with the reasons, is Conventions.
- No autoloads. An autoload reserves a global name in every consumer and cannot be instantiated twice, which would rule out a server and a client in one process. You place nodes yourself and find services through
DotRegistry. DotResultfor anything that can fail. Nevernull, never a pair. Checkok, branch onerror.codeagainst theDotError.CODE_*constants (never on the message), and add context withwrap().DotNodeRefinstead of scene paths. A component that needs a host node exports aDotNodeRef(relative, absolute, group, registry, nearest ancestor of a type, or create if missing), so it works wherever you place it.DotConfigfor settings. Declare@exportproperties and they are layered the same everywhere: exported defaults, then a JSON file, then the environment, then the command line. Secrets are refused from the environment and argv.DotLogfor logging, with a promise in the level. TRACE is per-frame detail, DEBUG a decision, INFO what an admin would keep, WARN something recoverable that somebody should look at, ERROR a failed operation, FATAL the process cannot continue.printis a program’s output, not logging.
const CHANNEL := "crate"
func join(uid: String) -> DotResult:
var seat := _free_seat()
if seat < 0:
return DotResult.fail(DotError.CODE_CONFLICT, "server is full")
DotLog.info(CHANNEL, "seated", {"uid": uid, "seat": seat})
return DotResult.success(seat)
# a caller
var res := join(uid)
if not res.ok:
DotLog.warn(CHANNEL, "join refused: %s" % res.error)
return
Everything is Dot-prefixed in the addons because class_name is global; your game’s prefix plays the same part.
5. The client starts from DotGameClient
Every game client adds dot-game’s DotGameClient first in _ready. It is the platform layer every game repeats: the loading cover, the settings and Escape menu, the console, Tab for the board, M for the map menu, and input held while the cover is up or a menu is open. Add your own holds on its gate (gate.hold(&"map", "Loading the map"), then gate.release(&"map")). The template’s game/tpl_client.gd is the converted client to copy from, and dot-game’s docs/client-migration.md lists the steps and what a game deletes afterwards.
6. Ship it
Push a v* tag and dot-ci packages the game; the site signs and publishes it as <your site username>/<repository>, and a server installs it by that name. The order of tags across addons, the lock and the shell is on Releasing.
