Unlocking Achievements¶
Achievements are unlocked through the Scorbit platform based on game events and player progress. The SDK provides mechanisms to report achievement-relevant events and receive unlock notifications.
How Achievements Unlock¶
Achievements unlock based on their rules. Each achievement has one or more rules that define the unlock conditions:
| Rule Type | Evaluated By | Unlock Condition |
|---|---|---|
| MODE | SDK (in-session) | Player completes a specific game mode |
| MODE_START | SDK (in-session) | Player starts a specific game mode |
| MODE_STACK | SDK (in-session) | Player stacks a specific game mode |
| SCORE | SDK (in-session) | Player reaches or exceeds target score |
| GAME_CODE | Game code | Custom game-specific logic |
| ACHIEVEMENT | Server (post-game) | Another achievement must be unlocked first |
| PROGRESS | Server (post-game) | Counter-based progress tracking |
For multi-rule achievements, all evaluable rules must be satisfied. For example, an achievement with both a MODE and SCORE rule requires the mode to be completed and the score threshold to be met.
In-Session vs Post-Game Evaluation
The SDK evaluates in-session rules (MODE, SCORE) in real-time during gameplay. Server-evaluated rules (ACHIEVEMENT chains, PROGRESS counters) and global-scope achievements are processed by the server after game completion via a post-game evaluation task.
Reporting Mode Events¶
For mode-triggered achievements, report mode starts and completions using the mode tracking API:
Mode Names
Mode names must match exactly what was configured in the Scorbit portal when defining the achievement. Mode names are case-sensitive.
Score-Based Achievements¶
Score achievements unlock automatically when the reported score meets or exceeds the target:
The Scorbit platform automatically checks if the committed score triggers any score-based achievements.
Local Achievement Matching¶
The SDK evaluates cached achievement rules locally for real-time UI anticipation. It checks all in-session rules (MODE, MODE_START, MODE_STACK, SCORE) and skips server-evaluated rules (ACHIEVEMENT, PROGRESS). For multi-rule achievements, all evaluable rules must be satisfied.
// Check which mode achievements might unlock
const char* potential_keys[32];
size_t count = sb_check_mode_achievements(
gs,
"wizard_mode", // mode name
"complete", // mode type: "start", "complete", or "stack"
user_id,
potential_keys,
32
);
for (size_t i = 0; i < count; i++) {
printf("Potential unlock: %s\n", potential_keys[i]);
}
// Check which score achievements might unlock
count = sb_check_score_achievements(
gs,
current_score,
user_id,
potential_keys,
32
);
// Check which mode achievements might unlock (mode rules only)
auto potentialModeUnlocks = gs.checkModeAchievements(
"wizard_mode", // mode name
"complete", // mode type: "start", "complete", or "stack"
userId
);
// Check mode+score combined achievements
// Use this for multi-rule achievements that require both a mode AND a score
auto potentialCombinedUnlocks = gs.checkModeAchievementsWithScore(
"wizard_mode", // mode name
"complete", // mode type
userId,
currentScore // also evaluate SCORE rules
);
for (const auto& key : potentialCombinedUnlocks) {
std::cout << "Potential unlock: " << key << "\n";
}
// Check which score-only achievements might unlock
auto potentialScoreUnlocks = gs.checkScoreAchievements(
currentScore,
userId
);
# Check which mode achievements might unlock
potential_mode_unlocks = gs.check_mode_achievements(
"wizard_mode", # mode name
"complete", # mode type: "start", "complete", or "stack"
user_id
)
for key in potential_mode_unlocks:
print(f"Potential unlock: {key}")
# Check which score achievements might unlock
potential_score_unlocks = gs.check_score_achievements(
current_score,
user_id
)
checkModeAchievementsWithScore
Use checkModeAchievementsWithScore (C++) when your game has achievements that combine both mode and score conditions. This evaluates both rule types simultaneously, catching multi-rule achievements that checkModeAchievements alone would miss.
Local Matching is Predictive Only
Local matching helps anticipate unlocks for UI purposes, but the server is the authoritative source. Server-evaluated rules (ACHIEVEMENT chains, PROGRESS counters) and global-scope post-game achievements cannot be matched locally. Always wait for the AchievementUnlocked event before confirming an unlock to the player.
Receiving Unlock Notifications¶
Register a callback to receive real-time achievement unlock notifications. The callback must be set on the Config before creating the GameState:
// Event callback function
void on_event(const sb_event_t* event, void* user_data) {
sb_event_type_t type = sb_event_type(event);
if (type == SB_EVT_ACHIEVEMENT_UNLOCKED) {
const char *key = NULL;
const char *name = NULL;
const char *user_id = NULL;
const char *username = NULL;
const char *icon_url = NULL;
bool is_trophy = false;
if (sb_event_achievement_unlocked(event, &key, &name, &user_id,
&username, &icon_url, &is_trophy)) {
printf("Achievement unlocked: %s\n", name);
printf(" Key: %s\n", key);
printf(" User: %s (ID: %s)\n", username, user_id);
printf(" Is Trophy: %s\n", is_trophy ? "yes" : "no");
// Show unlock animation/notification
display_achievement_unlock(name, icon_url);
}
}
}
// Register the callback during configuration (before creating GameState)
sb_config_t config = sb_config_create();
sb_config_set_event_callback(config, on_event, NULL);
// ... other config settings ...
sb_game_handle_t gs = sb_create_game_state(config);
// Event callback function
void onEvent(const scorbit::Event& event) {
if (event.type() == scorbit::EventType::AchievementUnlocked) {
std::string key, name, userId, username, iconUrl;
bool isTrophy = false;
if (event.getAchievementUnlocked(key, name, userId, username, iconUrl, isTrophy)) {
std::cout << "Achievement unlocked: " << name << "\n";
std::cout << " Key: " << key << "\n";
std::cout << " User: " << username << " (ID: " << userId << ")\n";
std::cout << " Is Trophy: " << (isTrophy ? "yes" : "no") << "\n";
// Show unlock animation/notification
displayAchievementUnlock(name, iconUrl);
}
}
}
// Register the callback during configuration (before creating GameState)
scorbit::Config config;
config.setEventCallback(onEvent);
// ... other config settings ...
auto gs = scorbit::createGameState(config);
def on_event(event):
if event.type() == scorbit.EventType.AchievementUnlocked:
success, key, name, user_id, username, icon_url, is_trophy = \
event.get_achievement_unlocked()
if success:
print(f"Achievement unlocked: {name}")
print(f" Key: {key}")
print(f" User: {username} (ID: {user_id})")
print(f" Is Trophy: {'yes' if is_trophy else 'no'}")
# Show unlock animation/notification
display_achievement_unlock(name, icon_url)
# Register the callback during configuration (before creating GameState)
config = scorbit.Config()
config.set_event_callback(on_event)
# ... other config settings ...
gs = scorbit.create_game_state(config)
Unlock Event Data¶
The unlock event provides the following information via output parameters:
| Field | Type | Description |
|---|---|---|
key |
string | Unique achievement identifier |
name |
string | Display name of the achievement |
user_id |
string | UUID of the user who unlocked it |
username |
string | Username of the player |
icon_url |
string | URL to the achievement badge image |
is_trophy |
bool | Whether this is a trophy achievement |
Trophy Achievements¶
Trophy achievements are special achievements that can be revoked. For example, a "Current High Score Holder" trophy would be revoked when another player beats the score.
Handle trophy lock events:
if (type == SB_EVT_ACHIEVEMENT_LOCKED) {
const char *key = NULL;
const char *name = NULL;
const char *user_id = NULL;
const char *username = NULL;
const char *icon_url = NULL;
if (sb_event_achievement_locked(event, &key, &name, &user_id,
&username, &icon_url)) {
printf("Trophy revoked: %s\n", name);
printf(" User: %s\n", username);
// Update UI to show trophy was lost
remove_trophy_display(key, user_id);
}
}
if (event.type() == scorbit::EventType::AchievementLocked) {
std::string key, name, userId, username, iconUrl;
if (event.getAchievementLocked(key, name, userId, username, iconUrl)) {
std::cout << "Trophy revoked: " << name << "\n";
std::cout << " User: " << username << "\n";
// Update UI to show trophy was lost
removeTrophyDisplay(key, userId);
}
}
Best Practices¶
1. Always Use Server Events for Confirmation¶
// WRONG: Assuming unlock based on local check
if (!gs.checkScoreAchievements(score, userId).empty()) {
showUnlockAnimation(); // Don't do this!
}
// RIGHT: Wait for server confirmation
void onEvent(const scorbit::Event& event) {
if (event.type() == scorbit::EventType::AchievementUnlocked) {
showUnlockAnimation(); // Server confirmed!
}
}
2. Handle Network Delays¶
Unlock notifications may be delayed. Consider showing a "checking achievements..." state for important milestones.
3. Cache Unlock State¶
Store unlock state locally to avoid re-showing notifications on reconnection.