Skip to content

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:

// Add a mode when it starts
sb_add_mode(gs, "MB:Multiball");

// Remove a mode when it ends
sb_remove_mode(gs, "MB:Multiball");

// Commit the changes
sb_commit(gs);
// Add a mode when it starts
gs.addMode("MB:Multiball");

// Remove a mode when it ends
gs.removeMode("MB:Multiball");

// Commit the changes
gs.commit();
# Add a mode when it starts
gs.add_mode("MB:Multiball")

# Remove a mode when it ends
gs.remove_mode("MB:Multiball")

# Commit the changes
gs.commit()

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:

// Update player score - achievements check automatically
sb_set_score(gs, player, new_score, 0);
sb_commit(gs);
// Update player score - achievements check automatically
gs.setScore(player, newScore);
gs.commit();
# Update player score - achievements check automatically
gs.set_score(player, new_score)
gs.commit()

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);
    }
}
if event.type() == scorbit.EventType.AchievementLocked:
    success, key, name, user_id, username, icon_url = \
        event.get_achievement_locked()
    if success:
        print(f"Trophy revoked: {name}")
        print(f"  User: {username}")

        # Update UI to show trophy was lost
        remove_trophy_display(key, user_id)

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.