Achievements
DavidnetSDK.unlockAchievement({ id, name, description?, icon?, progress?, target? })
Unlocks (or advances) an achievement for the current player. id is a stable string YOU choose (letters, numbers, - / _ , up to 100 chars) — unique within your game, not globally, so keep it short and stable (e.g. "first_win" , not something you'll rename later). name is what's shown to the player; description and icon (an emoji works well) are optional. Resolves to: { isNew, achievement: { id, name, description, icon, progress, target, unlockedAt } }
Classic instant unlock
Without progress / target : first call wins — if the player already has this id , the stored name/description/icon don't change. Repeat calls are cheap no-ops server-side, but
still call this ONCE when the condition first becomes true, not in a loop that re-checks the condition
every frame — each call is still a network round trip that counts against your rate limit .
// Call this from the "boss defeated" event handler, not from your update loop.
async function onBossDefeated() {
try {
const { isNew } = await window.DavidnetSDK.unlockAchievement({
id: "first_boss_kill",
name: "Giant Slayer",
description: "Defeat the first boss",
icon: "⚔️"
});
if (isNew) showMessage("Achievement unlocked: Giant Slayer!");
} catch (e) {
console.warn("Could not unlock achievement", e);
}
}
// A "collect all the things" style achievement - fine to call every time an
// item is picked up, since the unlock condition is only true once.
async function onItemCollected(totalCollected, totalItems) {
if (totalCollected !== totalItems) return; // not done yet, don't call at all
await window.DavidnetSDK.unlockAchievement({
id: "collector",
name: "Completionist",
description: "Collect every item in the game",
icon: "🏆"
});
}Progress-bar achievements
With progress + target (both positive integers): tracks a progress bar instead of unlocking instantly — call this every
time progress changes (e.g. { progress: 12, target: 50 } for "12 of 50 enemies defeated"). The server remembers the HIGHEST progress seen; isNew only flips true the moment progress reaches target (the achievement completes), after which it's immutable, same
as a classic achievement.
// Call with the current/target progress whenever it changes - "isNew" only
// flips true the moment it actually completes, so this is safe to call often
// (e.g. once per enemy kill, not once per frame).
async function onEnemyDefeated(totalDefeated) {
try {
const { isNew, achievement } = await window.DavidnetSDK.unlockAchievement({
id: "monster_hunter",
name: "Monster Hunter",
description: "Defeat 50 enemies",
icon: "🗡️",
progress: totalDefeated,
target: 50
});
updateProgressBar(achievement.progress, achievement.target); // e.g. 23 / 50
if (isNew) showMessage("Achievement unlocked: Monster Hunter!");
} catch (e) {
console.warn("Could not update achievement progress", e);
}
}DavidnetSDK.getAchievements()
Every achievement the current player has unlocked OR made progress on in THIS game. unlockedAt is null for an in-progress (not yet completed) achievement — use this to rebuild a progress bar on load
without tracking it yourself. unlockedPercentage (0-100) is the share of players who have fully unlocked that achievement id — handy for a rarity
badge like "3% of players have this". Resolves to: { achievements: [{ id, name, description, icon, progress, target, unlockedAt, unlockedPercentage
}, ...] }
async function showAchievementsScreen() {
const { achievements } = await window.DavidnetSDK.getAchievements();
for (const a of achievements) {
const isDone = a.unlockedAt !== null;
renderAchievementRow({
icon: a.icon,
name: a.name,
description: a.description,
done: isDone,
progressText: a.target ? `${a.progress} / ${a.target}` : null,
rarity: a.unlockedPercentage + "% of players have this"
});
}
}