What it is. The menu every Dot game opens with Escape: a sidebar of pages (General, Video, Audio, Controls, Advanced, plus any your game adds), a Resume button, Help and Leave. Every switch, slider and drop-down is a view of a dot-settings value, so it shows what is stored and writes it back when changed. There is no Apply button, and a value changed from the console shows up the next time the menu opens.
When to use it. You want your game to have settings, rebindable keys, a help screen and a scoreboard that look and behave like every other Dot game’s, without copying them. It was pulled out of one game’s menus so that all of them could share one. If you only need a settings panel generated from a config, dot-ui is smaller.
Requires dot-core, dot-ui and dot-settings. Opening the addon’s project runs a small demo world with the menu on it: Escape, H, P and M all work.
What you get
- Stock settings: volumes, window mode, V-Sync, frame-rate cap, render scale, field of view, effects quality, sensitivity, invert mouse, camera shake, screen flashes, theme, HUD size, a frame-rate counter and the chat box. They are declared once with fixed keys and scopes, so moving a game onto the menu keeps every player’s choices.
- An applier (
DotMenuApplier) that pushes each setting at what it controls (the engine, the audio buses, a camera, a sampler), once at load and again on every change. - Keys:
DotMenuBindingsis one table of actions. It declares a setting per key, puts them on the InputMap, draws a button per row on the Controls page and lists them on the help screen. Binding a key already in use swaps the two, and Escape can never be bound. A second key on one action is an extra row (add_extra), never a secondadd, which would replace the row. The mouse wheel isadd_wheel_jump()in a game with nothing to switch andadd_wheel_weapons(prev, next)in one with weapons;add_movement()includes neither. - Help on H: your game’s goal and modes, every key as currently bound, the server’s chat commands, and in a browser what the browser does with Ctrl+W and friends.
- Themes: seven ship (Midnight, Ember, Violet, Classic, Arcade, Paper, High contrast); a theme is a palette for the menus and the HUD and can be written as JSON. Midnight’s HUD is dark and is the default; Arcade is the bright one. A game’s HUD follows the theme when you call
menu.follow_hud(root, accent)where it is built: the menu restyles that root at once and on every change, keeps your accent in every theme, and applies the player’shud_sizesetting (0.7 to 1.4). Call it throughhas_methodin a delivered game, since an older shell lacks it. - A picker (a numbered list, seven to a page), a HUD layout editor (drag pieces, saved as fractions of the screen), and net_stat, a connection overlay with frame rate, ping, jitter, loss and bandwidth. Its levels go from numbers to a graph of arrivals by channel, and at the top level to departures, wall-clock frame times split by part, renderer counts, hitches, the server’s own frame and prediction cost.
- Server rules: while connected, a server can hide rows, narrow the themes and cap settings such as field of view. It can never change or read a player’s stored value, and leaving puts everything back.
- The scoreboard: hold Tab for the server’s name, map and slots, teams side by side and a row per player (name, score, time, ping) plus any columns your game adds. It never takes the mouse or stops you moving.
Minimal example
# The keys your game reads.
var keys := DotMenuBindings.new()
keys.add_movement() # WASD, Space, Ctrl
keys.add(&"mygame_flashlight", &"bind_flashlight", "Flashlight", "F", "View")
keys.add_help() # H opens help; rebindable like any key
# The settings: the stock ones, your own, and your config over both.
var config := DotMenuConfig.new()
config.load_layered("res://menu.json")
var schema := DotSettingsSchema.new()
DotMenuStock.declare(schema)
schema.add(DotSettingsDef.boolean(&"show_speed", true, &"hud"))
keys.add_to_schema(schema)
config.apply_to_schema(schema)
var settings := DotMenuStock.manager(schema, "user://my_game_settings", &"my_game")
add_child(settings)
settings.setup()
# What applies them.
var applier := DotMenuApplier.new()
applier.settings = settings
applier.bindings = keys
add_child(applier)
applier.bind_camera(camera) # field of view
applier.on(&"show_speed", func(on, _why): hud.speed.visible = on)
applier.setup()
# The menu.
var menu := DotMenu.new()
menu.settings = settings
menu.bindings = keys
menu.config = config
add_child(menu)
menu.setup()
menu.help.about = "First to twenty frags wins."
menu.leave_on_its_own = true # Leave disconnects, or quits a solo game
menu.overlays_changed.connect(func(open): sampler.suspended = open) # stop game input while it is open
applier.bind_audio(audio) and applier.bind_look(sampler.tunables) do the same for volumes and mouse sensitivity.
Adding your own page
var page := DotMenuPage.make(&"gameplay", "Gameplay", "Your view and the HUD.")
page.section("HUD").add(DotMenuRow.toggle(&"show_speed", "Speed", "Under the clock."))
page.section("View").add(DotMenuRow.live_toggle("Flashlight", "",
func(): return light.on, func(on): light.on = on))
menu.add_page(page, &"video") # before Video
A row is one of toggle, slider, select (over a setting), live_toggle, live_select, live_slider (over state your game owns, which is not stored), button, note and custom (any Control you build). A setting row takes its range and options from the setting. A row for a setting your schema does not have is not drawn, which is how the stock pages fit a game that only declared some of the stock settings. Everything on a stock page can be moved, reworded or hidden with menu.page(&"general").take_row(...) and menu.find_row(...).
Configuration without code
DotMenuConfig is layered like every Dot config (inspector defaults, then a JSON file, then environment, then command line), so a developer can brand the menu, hide rows, narrow themes or change a setting’s default and range from a file:
{
"brand_mark": "g2g", "brand_name": "fast",
"theme": "ember", "themes_allowed": ["ember", "paper", "contrast"],
"rows_hidden": ["render_scale"],
"settings": { "field_of_view": {"default": 90, "min": 75, "max": 120} }
}
A changed default reaches a player who has never run the game; a player who already has a value keeps it. The addon’s README lists every key.
The scoreboard and the server
The board is held on whatever action you name (menu.scoreboard_action, bound to Tab in your key table). Online, dot-server sends the roster only while a player holds the board, and your server adds its own columns and teams through server.scoreboard_fields and server.scoreboard_extra. Offline, set menu.scoreboard.source to a function that returns the same dictionary. A server hands the menu what it allows with menu.apply_server_rules({...}) and takes it back with menu.release_server_rules().
menu.toast(...) shows a short line, and menu.toast_top sets how many pixels below the top edge it sits, so a game whose HUD draws a strip there (scores, a clock) can place it underneath.
A game whose Escape is its own
A garage or a lobby sets menu.open_on_escape = false and opens the menu some other way; Escape still closes it. In a browser, where Escape releases the mouse, the menu opens anyway and an Escape arriving right after is ignored so it does not flash shut.
