GameMaker Studio · GML
Global leaderboards
Creating Leaderboards
From the dashboard, open the Leaderboards section and create a table by giving it a name. You can declare up to 10 tables per game.
Leaderboard Settings
Once created, configure:
- Order: descending or ascending.
- Scores to show: number of rows returned.
- Min/Max score: optional bounds; submissions outside them are rejected.
- Custom fields: extra data per score (level, country, etc.), up to 20 depending on your plan.
- Temporal scores: daily/weekly/monthly buckets. Off = historic-only table; a submit that does not beat the best score stores nothing.
Save each table key; you pass them to gmda_setLeaderboard in the controller.
Multiple Tables (1-based)
Declare all your tables together. The order defines the 1-based table number you pass to every other call. The first key is table 1.
gmda_setLeaderboard("key_1", "key_2", "key_3");
// key_1 -> table 1, key_2 -> table 2, key_3 -> table 3Submitting Scores
Submit a score to a table by its 1-based number, with an optional struct of custom fields, then read submit_completed from the queue.
if (gmda_status()) {
gmda_submit(1, score, { level: level_reached }); // -> submit_completed
}
// offline-safe variant: queues + retries, survives app closes
gmda_submitWithRetry(1, score, { level: level_reached });// submit_completed -> e.response:
{
"effect": "updated", // "updated" | "periods_only" | "not_improved"
"periods_enabled": true, // this table's temporal-scores toggle
"improved": true, // historic (all-time) score written
"improved_periods": { "daily": true, "weekly": true, "monthly": true },
"score": { "score": 1500, "custom_data": { "level": 3 }, "updated_at": "..." }
}gmda_submitWithRetry only replaces a queued score when the new one is higher. Use gmda_submit for a simple send without retry. The submit_completed event reports the exact effect in e.response.effect: "updated" (historic score written), "periods_only" (only the daily/weekly/monthly buckets improved) or "not_improved" (nothing stored). On tables with temporal scores disabled, a score that cannot beat your cached best is not even sent (the call returns 0), check gmda_getTableConfig(table).
Reading Scores
These synchronous getters return the last loaded data instantly, with no network wait:
- gmda_getTabledata(table): the cached top of the table as a ds_grid.
- gmda_getBestscore(table) / gmda_getPlayerpos(table): your best score and your rank.
var grid = gmda_getTabledata(1); // ds_grid: rank, player_name, score, cd0, cd1, cd2
for (var i = 0; i < ds_grid_height(grid); i++) {
if (ds_grid_get(grid, 1, i) == "") break; // empty row -> end of data
show_debug_message(string(ds_grid_get(grid, 0, i)) + " " +
ds_grid_get(grid, 1, i) + " " + string(ds_grid_get(grid, 2, i)));
}
show_debug_message("Your best: " + string(gmda_getBestscore(1)) +
" (rank " + string(gmda_getPlayerpos(1)) + ")");// gmda_getTabledata(table) -> ds_grid (6 columns x 10 rows)
// col 0: rank col 1: player_name col 2: score
// col 3: custom_data[0] col 4: custom_data[1] col 5: custom_data[2]
// rows fill top-down; an empty player_name marks the end of the data.
// gmda_getExtrainfo(table) -> { level, country, ... } // your panel field names, as submitted : your own custom_data on that table.Views: Periods & Friends
Request filtered views (daily, weekly, monthly, or friends-only) and read view_loaded from the queue.
gmda_loadLeaderboardView(1, "weekly"); // weekly top
gmda_loadLeaderboardView(1, "all", { friends_only: true }); // friends only
// in the poll loop:
case "view_loaded":
var scores = e.response.scores;
for (var i = 0; i < array_length(scores); i++) {
var row = scores[i];
show_debug_message("#" + string(row.rank) + " " + row.player_name + " " + string(row.score));
}
break;# scores: Array, each row:
{
"rank": int,
"player_key": String,
"player_name": String,
"score": int,
"custom_data": Dictionary,
"date": String
}
# player_score: Dictionary (your own row), or null if you have no score:
{
"rank": int,
"score": int,
"custom_data": Dictionary,
"date": String # period bucket the score belongs to
}
# custom_data keys are the extra fields you defined for the table in the panel.
# The response also carries the table's dashboard config:
# "leaderboard": { "name": String, "sort_order": "asc" | "desc", "periods_enabled": bool }
# Period views (daily/weekly/monthly) additionally include "period" and "period_key".Search & View Players
Search by name within a table, or look up a full player profile.
gmda_searchPlayers(1, "corvus"); // -> players_search_completed
gmda_loadPlayerInfo("player_key"); // -> player_info_loaded# players_search_completed -> results: Array, each:
{
"rank": int,
"player_key": String,
"player_name": String,
"score": int
}
# player_info_loaded -> player: Dictionary
{
"player_key": String,
"player_name": String,
"is_invitable": bool,
"created_at": String
}
# ...plus scores: Array, one per leaderboard the player appears on:
{
"leaderboard": {
"table_key": String,
"name": String,
"sort_order": String # "asc" | "desc"
},
"rank": int,
"score": int,
"custom_data": Dictionary,
"date": String,
"updated_at": String
}