Stats are numbers that track progress per player — total kills, distance run, games played. Achievements are milestones players unlock, like "First Blood" or "Centurion." You define both in the Developer Portal (or with the CLI's wavedash stat and wavedash achievement commands) and interact with them from your game through the SDK.
Setting up achievements
Go to Developer Portal → your game → Achievements to create and manage achievements. You can add them one at a time or bulk import via JSON.
Each achievement has:
| Field | Required | Description |
|---|---|---|
| ID | Yes | A unique identifier like FIRST_BLOOD |
| Title | Yes | The display name players see, like "First Blood" |
| Description | Yes (optional in bulk import) | What the player did to earn it |
| Secret | No | Hide the achievement until it's unlocked |
| Image | No | A custom icon shown in the achievement list |
| Trigger rule | No | Automatically unlock when a stat reaches a threshold |
Achievements can be unlocked in two ways:
- Manually — your game calls the SDK to unlock it
- Automatically — you set a trigger rule (e.g. "Total Kills is at least 100") and Wavedash unlocks it when the stat threshold is reached
Bulk import
Click Add achievement → Import JSON to import multiple achievements and stats at once. The expected format:
{
"achievements": [
{
"identifier": "FIRST_KILL",
"display_name": "First Blood",
"description": "Get your first kill",
"secret": false,
"stat_requirement": null
},
{
"identifier": "GOLD_100K",
"display_name": "Rich!",
"description": "Collect 100,000 gold",
"stat_requirement": {
"stat": "GOLD_COLLECTED",
"threshold": 100000
}
}
],
"stats": [
{
"identifier": "GOLD_COLLECTED",
"display_name": "Gold Collected"
}
]
}Existing items (matched by identifier) are updated in place: the JSON overwrites the display name, description, secret, and stat requirement (a missing or null stat_requirement clears the link). Icons are kept, and items not in the JSON are left alone. A stat_requirement.stat must name a stat in the same file or one already defined, or the import fails.
Setting up stats
Go to Developer Portal → your game → In-Game Stats to define stats. Each stat has an ID and a display name. Stats persist across sessions as numeric values (Godot and Unity expose both int and float variants; JavaScript and Defold store them through a single numeric API).
Working with stats
func _ready():
WavedashSDK.current_stats_received.connect(_on_got_stats)
func load_stats():
WavedashSDK.request_stats()
func _on_got_stats(response):
if response.get("success", false):
var kills = WavedashSDK.get_stat_int("total_kills")
print("total_kills: ", kills)
func add_kill():
var kills = WavedashSDK.get_stat_int("total_kills") + 1
WavedashSDK.set_stat_int("total_kills", kills, true)async void Start()
{
bool ok = await Wavedash.SDK.RequestStats();
if (ok)
{
int kills = Wavedash.SDK.GetStatInt("total_kills");
Debug.Log($"total_kills: {kills}");
}
}
public void AddKill()
{
int kills = Wavedash.SDK.GetStatInt("total_kills") + 1;
Wavedash.SDK.SetStatInt("total_kills", kills, storeNow: true);
}function init(self)
local co = coroutine.create(function()
local response = wavedash.request_stats_async()
if response.success then
local kills = wavedash.get_stat("total_kills")
print("total_kills:", kills)
end
end)
assert(coroutine.resume(co))
end
local function add_kill()
local kills = (wavedash.get_stat("total_kills") or 0) + 1
wavedash.set_stat("total_kills", kills, true)
endconst response = await Wavedash.requestStats();
if (response.success) {
const kills = Wavedash.getStat("total_kills");
console.log(`total_kills: ${kills}`);
}
function addKill() {
const kills = Wavedash.getStat("total_kills") + 1;
Wavedash.setStat("total_kills", kills, true);
}All platforms support a storeNow parameter: setStat("id", value, true) and setAchievement("id", true). Every change is saved to the server automatically within about a second. Pass storeNow: true, or call storeStats(), to save immediately instead. Nothing is flushed when the session ends, so a change made in the last second before the player leaves can be lost unless you save it immediately.
Stats and achievements load in two parts, and requestStats() can resolve before both are ready. Until they are, getStat returns 0, and setStat / setAchievement return false and drop the write. The same happens for identifiers not defined in the Developer Portal — check the return value.
Float stats
Use the float variants for stats that aren't whole numbers — distance travelled, completion percentages, best times.
WavedashSDK.set_stat_float("distance_run_km", 42.195, true)
var distance = WavedashSDK.get_stat_float("distance_run_km")Wavedash.SDK.SetStatFloat("distance_run_km", 42.195f, storeNow: true);
float distance = Wavedash.SDK.GetStatFloat("distance_run_km");wavedash.set_stat("distance_run_km", 42.195, true)
local distance = wavedash.get_stat("distance_run_km")Wavedash.setStat("distance_run_km", 42.195, true);
const distance = Wavedash.getStat("distance_run_km");Unlocking achievements
func unlock_first_win():
WavedashSDK.set_achievement("first_win", true)
func has_first_win() -> bool:
return WavedashSDK.get_achievement("first_win")Wavedash.SDK.SetAchievement("first_win", storeNow: true);
bool unlocked = Wavedash.SDK.GetAchievement("first_win");wavedash.set_achievement("first_win", true)
local unlocked = wavedash.get_achievement("first_win")Wavedash.setAchievement("first_win", true);
const unlocked = Wavedash.getAchievement("first_win");Storing stats and achievements together
Batch stat updates and achievement unlocks, then send them in one call:
WavedashSDK.set_stat_int("total_kills", 200, false)
WavedashSDK.set_achievement("first_blood")
WavedashSDK.store_stats()Wavedash.SDK.SetStatInt("total_kills", 200, storeNow: false);
Wavedash.SDK.SetAchievement("first_blood");
Wavedash.SDK.StoreStats(); // returns bool synchronously; OnStatsStored reports the server resultwavedash.set_stat("total_kills", 200, false)
wavedash.set_achievement("first_blood")
wavedash.store_stats()Wavedash.setStat("total_kills", 200);
Wavedash.setAchievement("first_blood");
Wavedash.storeStats();Persistence events
The SDK raises an event after every save — the automatic ~1-second saves as well as storeStats() — so you can show a confirmation, retry on failure, or wait to quit until stats are safely persisted. In Unity, RequestStats() returns a Task<bool> — await it to know when stats are loaded.
func _ready():
WavedashSDK.stats_stored.connect(_on_stats_stored)
func _on_stats_stored(payload):
if payload.get("success", false):
print("Stats saved")
else:
push_warning("Stats failed to save: ", payload.get("message", ""))void Awake()
{
Wavedash.SDK.OnStatsStored += data =>
{
bool ok = (bool)data["success"];
Debug.Log(ok ? "Stats saved" : $"Stats failed: {data["message"]}");
};
}function init(self)
wavedash.init({}, function(_, event, payload)
if event == wavedash.EVENT_STATS_STORED then
if payload.success then
print("Stats saved")
else
print("Stats failed:", payload.message or "")
end
end
end)
endWavedash.on(Wavedash.Events.STATS_STORED, (payload) => {
if (payload.success) console.log("Stats saved");
else console.warn("Stats failed:", payload.message);
});Example: kill tracking
var session_kills: int = 0
func on_enemy_killed() -> void:
var kills = WavedashSDK.get_stat_int("total_kills") + 1
WavedashSDK.set_stat_int("total_kills", kills, true)
session_kills += 1
if kills == 1:
WavedashSDK.set_achievement("first_blood", true)private int sessionKills;
public void OnEnemyKilled()
{
int kills = Wavedash.SDK.GetStatInt("total_kills") + 1;
Wavedash.SDK.SetStatInt("total_kills", kills, storeNow: true);
sessionKills++;
if (kills == 1)
Wavedash.SDK.SetAchievement("first_blood", storeNow: true);
}local session_kills = 0
local function on_enemy_killed()
local kills = (wavedash.get_stat("total_kills") or 0) + 1
wavedash.set_stat("total_kills", kills, true)
session_kills = session_kills + 1
if kills == 1 then
wavedash.set_achievement("first_blood", true)
end
endlet sessionKills = 0;
function onEnemyKilled() {
const kills = Wavedash.getStat("total_kills") + 1;
Wavedash.setStat("total_kills", kills, true);
sessionKills++;
if (kills === 1) Wavedash.setAchievement("first_blood", true);
}