Making In-Game Leaderboards For GXC Challenges


Making In-Game Leaderboards For GXC Challenges

If you used the GXC API to create challenges, you will know that leaderboards for such challenges are viewable on GXC, where you can see how many points each player scored:

leaderboard_main.png

The GXC API also allows you to read these scores in-game, so you can practically do whatever you want with them: show a custom leaderboard, show an avatar of the highest scorer in-game to motivate other players, show the user their own high score in the pause menu, and so on.

In this tutorial, we'll create an in-game leaderboard by reading challenge scores through the GXC API and display them using objects:

leaderbaord_simple.png

GXC API Functions

Let's first take a look at the relevant functions in the GXC API.

gxc_challenge_get_global_scores()

This function is used to get the scores for the currently active challenge. It asynchronously gets an array of all scores, sorted from best to worst.

Here is a code example of this function from the GXC API documentation:

gxc_challenge_get_global_scores(function(_status, _result)
{
    if (_status == 200)
    {
        show_debug_message("Challenge: " + _result.data.challenge.name);

        var _scores_count = array_length(_result.data.scores);
        for (var i = 0; i < _scores_count; i ++)
        {
            var _score_data = _result.data.scores[i];
            show_debug_message("Score " + string(i) + " is " + _score_data.score);
        }
    }
});

This function requests the API to load the global scores for the currently active challenge, and once they are returned (in the callback function), it prints the name of the challenge and all of its scores (present in the _result.data.scores array) to the output log.

gxc_challenge_get_user_scores()

This function is similar to the previous function but gets the scores of the current user only. This way you are able to see all of the scores that the user has submitted for the challenge instead of only seeing their top score.

Read the GXC API documentation for more detailed information on these functions (which is also provided as a PDF in the Included Files of the GXC API asset).

Leaderboard Tutorial

NOTE: You need to have the GXC API and your game's challenges set up before you can follow this tutorial. Follow the challenges tutorial to set that up. 

We'll use two main objects for our leaderboard: obj_leaderboard and obj_leaderboard_entry.

  • obj_leaderboard: This reads scores through the GXC API and creates the leaderboard.

  • obj_leaderboard_entry: This represents one entry in the leaderboard, and displays the data related to it (i.e. the name, avatar and score).

Let's now program these objects!


obj_leaderboard_entry: Create Event

We'll first program the leaderboard entry object before we program how the complete leaderboard is created.

Create the obj_leaderboard_entry object, and add the following code in its Create event:

// Avatar loading variables
avatarUrl = noone;
avatarSprite = noone;
avatarRequest = noone;

// UI properties
avatarSize = 60;
avatarNameMargin = 5;

// Other user properties
username = "";
scoreValue = 0;

// Setup function
setup = function(_avatarUrl, _username, _scoreValue)
{
    if (is_string(_avatarUrl)) avatarRequest = sprite_add(_avatarUrl, 0, 0, 0, 0, 0);

    username = _username;
    scoreValue = _scoreValue;
}

We're first setting up some avatar-related variables: one to store its URL, one to store its sprite ID and one to store the request ID for the sprite download request.

Then we're creating variables for the avatar's UI properties (size and margin), the username and the score value.

Finally we have a setup() function that takes three arguments: the avatar URL for the user, the username and the score value. These will be passed in by the obj_leaderboard object once it retrieves the scores through the GXC API and creates the entry instances.

The function calls sprite_add() on the avatar URL argument if it is a string -- which it will not be if the user has no avatar. In case a string is supplied, this function will start downloading the user's avatar as a sprite.

The function will also store the username and score value into its own variables.


obj_leaderboard_entry: Async - Image Loaded Event

The Asynchronous Image Loaded event will be called when the user's avatar is done downloading, so we will confirm this and store the avatar's sprite ID in a variable:

// Async - Image Loaded event
if (async_load[? "id"] == avatarRequest)
{
    if (async_load[? "status"] >= 0)
    {
        avatarSprite = avatarRequest;
    }
    else
    {
        show_debug_message("[WARNING] Image Loaded event, failed to load image");
    }
}

This code checks if the Async event's ID matches the avatar request ID (meaning that we are receiving information for the correct request). If the Async status is then at or above 0, meaning the download was successful, it sets avatarSprite equal to the avatarRequest variable (which points to the sprite itself).

However if the status is negative, meaning there was an error while downloading the sprite, it prints a debug message to the console saying the same.


obj_leaderboard_entry: Draw Event

We'll now add the Draw event to draw the user's information and score.

// Avatar
draw_rectangle_color(x, y, x + avatarSize, y + avatarSize, c_lime, c_lime, c_lime, c_lime, 1);

if (avatarSprite != noone)
{
    draw_sprite_stretched(avatarSprite, 0, x, y, avatarSize, avatarSize);
}

// Other text
draw_text(x + avatarSize + avatarNameMargin, y, "User: " + username);
draw_text(x + avatarSize + avatarNameMargin, y + 30, "Score: " + string(scoreValue));

This first draws a rectangle where the avatar is supposed to be, and then draws the avatar itself if the avatarSprite variable is not noone. Note that this code uses draw_sprite_stretched() to ensure that the sprite fits the avatar area, and so will scale the avatar up or down depending on its original size.

Finally it draws the username and score value to the right of the avatar.


obj_leaderboard_entry: Clean Up Event

This is the final event in the object: it makes sure to destroy the avatar sprite when the instance stops existing, and also resets the avatarSprite variable to noone to avoid errors (such as some code after the destroy call referencing the variable which would contain the ID of an non-existent sprite).

if (avatarSprite != noone)
{
    sprite_delete(avatarSprite);
    avatarSprite = noone;
}

 

obj_leaderboard: Create event

The obj_leaderboard object will load the scores for the currently active challenge and create an instance of obj_leaderboard_entry for each individual entry. The object will contain two methods: clear() and refresh(). The former will clear the existing leaderboard and the latter will fill it with data from GXC.

Create the object and add this in its Create event:

entries = [];
maxEntries = 8;

/// Clears the current leaderboard
clear = function ()
{
    var _count = array_length(entries);

    repeat(_count) {
        instance_destroy(entries[--_count]);
    }

    entries = [];
}

We're creating an entries[] array that will store the IDs of the obj_leaderboard_entry instances that we create. We also have a maxEntries variable that stores the maximum number of entries that are visible in the leaderboard.

Then we have the clear() function that loops through the entries array and destroys each entry instance. At the end, it empties the array.

Let's now create the refresh() function:

/// Refreshes/loads leaderboard data
refresh = function ()
{
    // Exit if there is no challenge active
    if (is_undefined(gxc_get_query_param("challenge"))) exit;

    // Request global scores
    gxc_challenge_get_global_scores(function(_status, _result)
    {
        // Exit if there was an error
        if (_status != 200) exit;

        // Clear all existing entries
        clear();

        var _scoresData = _result.data.scores;
        
        // Limit the number of entries to be displayed
        var _count = min(array_length(_scoresData), maxEntries);
        
        // Loop through the scores
        for (var i = 0; i < _count; i++)
        {
            // Cache the score data
            var _scoreData = _scoresData[i];
            
            // Read the required information
            var _username = _scoreData.player.username;
            var _avatarUrl = _scoreData.player.avatarUrl;
            var _scoreValue = _scoreData.score;
        
            // Create an entry on the leaderboard
            var _entry = instance_create_layer(x, y + 70 * i, layer, obj_leaderboard_entry);
            
            // Set up the newly created instance
            _entry.setup(_avatarUrl, _username, _scoreValue);
        
            // Add the entry to the entries array
            array_push(entries, _entry);
        }        
    });
}

// Call refresh at the start of the game
refresh();

The refresh function calls gxc_challenge_get_global_scores() and specifies a callback function which runs when the scores are returned by the GXC API. In the callback, it gets the score data array and loops through it, creating an obj_leaderboard_entry instance for each score entry it finds. It calls the setup() method on each leaderboard entry instance after it's created, and then pushes its instance ID into the entries array.

At the end of the event, it calls the refresh() function to initially load score data into the leaderboard.


obj_leaderboard: Key Press - Space event

This one is optional: you can add a Key Press - Space event to the object and call the refresh() function inside it. This way you will be able to refresh the leaderboard in-game whenever you need.

refresh();

 

Testing the Leaderboard

Now go ahead and place obj_leaderboard in a room, then upload the game to GXC and play your challenge. (If you don't want to upload or update your game yet, you can test your challenge locally.)

If you already have scores submitted for your challenge, you will see them in the leaderboard:

leaderbaord_simple.png

Of course, if your challenge has no scores then you will need to submit some test scores using the gxc_challenge_submit_score() function. The demo project provided with this tutorial contains a functional "Submit Score" button that can be used to test this leaderboard; download it here.

You can now incorporate this leaderboard into your game's UI and make it look as neat as you like!

mesh_leaderboard.png
From the game Mesh.