Remote Config & Game Data
Change your game’s behavior and balance data remotely — no app update required. Remote Config handles feature flags and key-value settings. Game Data provides full balance tables (items, enemies, levels) with local caching.
Remote Config
Section titled “Remote Config”fetch_remote_config()
Section titled “fetch_remote_config()”QuestData.fetch_remote_config()Fetches the latest config values from the server. The SDK automatically includes the player’s player_id so segment-specific overrides are applied.
Config values are cached locally (to disk) and available offline after the first successful fetch.
get_config()
Section titled “get_config()”QuestData.get_config(key: String, default_value: Variant) -> Variant| Parameter | Type | Description |
|---|---|---|
key | String | Config key name |
default_value | Variant | Returned if key doesn’t exist or config hasn’t been fetched yet |
Returns: The config value, or default_value if not found.
get_all_configs()
Section titled “get_all_configs()”QuestData.get_all_configs() -> DictionaryReturns: All config key-value pairs as a Dictionary.
Example
Section titled “Example”func _ready(): # Fetch latest config on game start QuestData.fetch_remote_config()
func get_difficulty_multiplier() -> float: return QuestData.get_config("difficulty_multiplier", 1.0)
func is_holiday_event_active() -> bool: return QuestData.get_config("holiday_event", false)
func get_max_lives() -> int: return QuestData.get_config("max_lives", 3)Segment Overrides
Section titled “Segment Overrides”Configs can have different values per player segment. For example, “VIP” players could get max_lives = 5 while everyone else gets 3. Set up overrides in Configuration > Remote Config in the dashboard.
Tag players using set_user_tag() (see Players & Segments) and the SDK fetches the correct config values automatically.
A/B Testing
Section titled “A/B Testing”Remote Config integrates with the Experiments system. When a player is assigned to an experiment variant, the config values for that variant are merged into the response. No SDK changes needed — just call get_config() as usual.
Game Data
Section titled “Game Data”Game Data provides full balance tables — structured data like item stats, enemy configurations, or level definitions. Think of it as a spreadsheet your game can read at runtime.
get_game_data()
Section titled “get_game_data()”QuestData.get_game_data(table_name: String, callback: Callable = Callable(), force_refresh: bool = false)| Parameter | Type | Default | Description |
|---|---|---|---|
table_name | String | required | Name of the data table |
callback | Callable | Callable() | Called with Array of row Dictionaries |
force_refresh | bool | false | Skip cache and fetch from server |
The callback receives an Array of Dictionaries, where each Dictionary is a row from the table.
get_game_data_cached()
Section titled “get_game_data_cached()”QuestData.get_game_data_cached(table_name: String) -> ArrayReturns cached data synchronously. Returns an empty Array if the table hasn’t been fetched yet.
preload_game_data()
Section titled “preload_game_data()”QuestData.preload_game_data(table_names: Array, callback: Callable = Callable())Fetches multiple tables in parallel. The callback fires when all tables are loaded.
clear_game_data_cache()
Section titled “clear_game_data_cache()”QuestData.clear_game_data_cache()Clears all cached game data, forcing a re-fetch on next access.
Example
Section titled “Example”# Preload all balance tables on game startfunc _ready(): QuestData.preload_game_data(["weapons", "enemies", "levels"], func(): print("All game data loaded!") start_game() )
# Read weapon statsfunc get_weapon(weapon_id: String) -> Dictionary: var weapons = QuestData.get_game_data_cached("weapons") for weapon in weapons: if weapon.get("id") == weapon_id: return weapon return {}
# Fetch with callback (first time or force refresh)func refresh_enemies(): QuestData.get_game_data("enemies", func(rows: Array): for enemy in rows: print(enemy["name"], " - HP: ", enemy["hp"]) , true) # force_refresh = trueLive Balancing (Composition API)
Section titled “Live Balancing (Composition API)”Live Balancing takes Game Data one step further: instead of reading rows manually, you bind a Resource directly to a table row. The SDK updates your resource’s @export properties in-place whenever the table changes — at boot, on demand, or pushed live from the dashboard via WebSocket.
bind_balancing()
Section titled “bind_balancing()”QuestData.bind_balancing(resource: Resource, table: String, row_id: String = "", id_column: String = "id") -> void| Parameter | Type | Default | Description |
|---|---|---|---|
resource | Resource | required | The Resource instance whose @export properties will be overridden |
table | String | required | Name of the game data table |
row_id | String | "" | Row to match against. If empty, reads the resource’s id property automatically |
id_column | String | "id" | Column name used to find the matching row |
Registers the resource for live overrides and immediately applies current cached values (or triggers a fetch if not yet cached). Safe to call in _ready().
var hut := preload("res://buildings/hut.tres") as BuildingQuestData.bind_balancing(hut, "buildings") # reads hut.id automaticallyunbind_balancing()
Section titled “unbind_balancing()”QuestData.unbind_balancing(resource: Resource) -> intRemoves all bindings for resource. Returns the number of bindings removed. Call in _exit_tree() if the resource is scene-local (global resources don’t need this).
refresh_balancing()
Section titled “refresh_balancing()”QuestData.refresh_balancing(table_name: String = "") -> voidForce-fetches table_name and reapplies overrides to all bindings. When table_name is omitted, refreshes every bound table. Useful after a manual config change in development.
get_realtime()
Section titled “get_realtime()”QuestData.get_realtime() -> QDRealtimeReturns the live WebSocket channel. Primarily used for pivot-table consumers that need to react to table changes manually (scalar bindings via bind_balancing are updated automatically).
QuestData.get_realtime().gamedata_updated.connect(func(table, _ver, _act, _env): if table == "building_costs": _rebuild_cost_cache())balancing_applied Signal
Section titled “balancing_applied Signal”signal QuestData.balancing_applied(resource: Resource, table: String)Emitted after apply_overrides mutated at least one property on resource. Fires once per resource per update pass — only when something actually changed, never on no-op fetches.
Use this to trigger UI refreshes without polling or table-level routing:
QuestData.balancing_applied.connect(func(res: Resource, _table: String) -> void: if res == my_building_data: _refresh_tooltip())How It Works
Section titled “How It Works”Remote Config
Section titled “Remote Config”fetch_remote_config()sends a GET request with the player’s ID- The server returns configs merged with any segment overrides and experiment variants
- Values are cached in memory and persisted to disk (
user://quest_config.save) get_config()reads from the in-memory cache (instant, no network)
Game Data
Section titled “Game Data”get_game_data()checks the local cache first- On cache miss (or
force_refresh), fetches fromGET /v1/gamedata/:table_name - Response includes rows, columns, and a version number
- Data is cached in memory and persisted to disk (
user://quest_gamedata.save) - Multiple calls to the same table while a fetch is pending are batched (callback queued)
Dashboard
Section titled “Dashboard”- Configuration > Remote Config — Create and edit config keys, set segment overrides
- Live Ops > Balancing — Edit game data tables with a spreadsheet UI, CSV import, version history
- Live Ops > Experiments — Create A/B tests that override config values
Best Practices
Section titled “Best Practices”- Always provide a default value —
get_config("key", default)ensures your game works even if the config hasn’t been fetched yet - Fetch config early — Call
fetch_remote_config()in_ready()or on the loading screen - Preload game data — Use
preload_game_data()on the loading screen to avoid mid-game fetches - Use
get_game_data_cached()in hot paths — It’s synchronous and free;get_game_data()with a callback is for initial loading