Wednesday 29 March 2017

How to Implement Scoreboards in Godot with the GameJolt API

GameJolt is not the largest gaming platform, nor is Godot the most popular editor. Despite this, a kind user known as "ackens" has created a plugin for Godot, which allows super easy integration with the GameJolt API.

This plugin can be downloaded at this link: https://github.com/ackens/-godot-gj-api

Since the Internet failed me for quite a while on how to install this plugin- I will enlighten the kind readers of my blog. Inside your project folder, (res://), you need to make a folder called "addons". Place the "gamejolt_api" folder inside that folder(res://addons/).

If you have done this correctly, you can return to the editor and press "Scene" in the top-left, down to "Project Settings" and select the "Plugins" tab. The API should show up as an entry called "Game Jolt API". Simply set its status on the right hand side from "Inactive" to "Active", and you're good to go.

From here, there are a number of things to tackle. I'm going to be primarily explaining how to submit guest scores to a scoreboard since this is what I used the API for in my game, Super Displacement.

The assumptions that I will be making from here on are that:
  1. You're using a scene that isn't your main gameplay loop to do this(though if you are, I'm sure this can be adjusted)
  2. You can make a better UI for this than I can.
  3. You have a given score to submit.
If all of these 3 apply, then we can get started.



Before you can do anything, you must add a GameJoltAPI node to your scene. Each API command you make will be a method on this node, i.e it will be of the form:

    get_node("../GameJoltAPI").method(argument)


Before making any of these calls, it's important to set two properties of the node: your game's Private Key and its ID. These are used to identify to the GameJolt servers as to which game is being edited.

Both of these variables can be found by going to your game's page on GameJolt, clicking "Manage Game", "Game API" and selecting "API Settings" on the left hand side.


Once you have entered these values, you're ready to start making some API calls.

For Super Displacement, the need to log into GameJolt was never present, so I did not need to use .auth_user("token", "username"). Fortunately for me, the GameJolt API has a function called "add_score_for_guest". This - as the name would suggest - allows a score to be submitted as a guest, where the user never has to log in or input anything other than a display name. This makes things very easy.



I used a LineEdit object for the user to input their desired display name, and on pressing either the enter key(using the "text_entered" signal on the LineEdit) or the "Submit Score" button, the text in the LineEdit(get_node("../LineEdit").get_text()) is returned to a script which then submits the request.

However, that's not quite my implementation of it.


One. For some reason, either GameJolt or the implementation of it in Godot freaks out if there are spaces in the display name. This is a super simple fix, as the only way around this (beyond rejecting the user's input if they input a name with spaces) is to simply remove the spaces from the name, using:

    guest_name.replace(" ", "")

This command quite simply moves through the string, replacing any instances of the space character with an empty string. In effect, this removes the spaces. "The Best" becomes "TheBest", etc.

Two. What if the user doesn't input a name? While this doesn't stop the request from happening(as far as I know), it may be helpful to put a stock username in its place.  For this, I did a simple check:

    if(guest_name == ""):
     get_node("../../GameJoltAPI").add_score_for_guest( .. , .. , "Guest" + str(randi()%10000000))


Though it makes my inner PEP8 fanatic weep, it does the job. If the user has not entered a name into the LineEdit, it generates a random string of 7 numbers and appends it to the word "Guest".

At this point(and probably a bit earlier) I should explain how this method works.

The first argument (called the "score" in the plugin's documentation on Github) is a string which is displayed on the "Scores" section of your game's page. This might be "23 Castles Destroyed", "381 Enemies Slain", or whatever quantifier you want to add. In my case, I simply set this to "str(latest_score)", since there isn't much of a quantifier beyond "Points" to add to an arcade score.

The second argument (called the "sort value") is an integer value which tells GameJolt how to order the scores in the table. Assuming you have the score table in "Ascending" mode - big means good - a higher sort value will mean a higher placement on the scoreboard. In my case, this is "int(latest_score)"(since latest_score is originally a float value).

After that, that's really all there is to it. If you wanted to add scores for a logged in user, you would have to .auth_user("token", "username") and then .add_score_for_user(visible_score, sort_value).

Displaying scores is also very simple, though it requires some playing with JSON files first.




Again, assuming you have a GameJolt API node in your scene, you're ready to make some API calls.

For my HighScores.tscn scene, I put a standalone call for 30 scores into the _ready(): function:

    get_node("../../GameJoltAPI").fetch_scores("30")

Your immediate reaction might be confusion as to why this isn't being printed, assigned to a variable or anything else- it's because the plugin responds with a signal that carries the scores in a string. This signal is called "api_score_fetched(var scores)".

You might also be confused as to why the "30" is a string and not an integer and to be quite honest I have no idea, but it has to be a string for some reason.

Connect this signal to a node of your choice, and try to print(scores) - you'll get something that looks an awful lot like JSON. The explanation for this is that this is JSON, but it's encoded in a string, and we have to parse it into a dictionary.

Do something like this:

    var scores_dictionary = {}
    scores_dictionary.parse_json(scores)

This creates an empty dictionary, and parses "scores" into a nice dictionary format, where it can be indexed. There is a list of dictionaries contained at the ["response"]["scores"] indices.

I implemented the "table" in the screenshot above by creating 6 separate labels, for 2 sets of 3 labels. The first label consists of the numbers, which I added manually. This could very easily be automated, but that is an exercise left to the reader.

The second field consists of the names of the users who have submitted scores. This can be obtained by creating a variable named "names", iterating through "scores_dictionary" and concatenating each guest name to "names" along with a \n for the linebreak.

The code I used for this part is as follows: 

    var names = ""
    var names2 = ""
    for i in range(visible_scores.size()):
        if(i<15):
            names += visible_scores[i]["guest"] + "\n"
        elif(i<30):
            names2 += visible_scores[i]["guest"] + "\n"
    get_node("../names").set_text(names)
    get_node("../names2").set_text(names2)
  
Assuming the line spacing and Y coordinate is the same, this will line up with the numbers.

The variable and node called "names2" is the second instance of each list of names, as shown in the screenshot above.

The exact same process can be used for the score, all you have to do is reference [i]["score"] instead of [i]["guest"].

If you have implemented these correctly, you should get a nice, basic scoreboard for further extension and development. Also, I'm sure there's something better than Labels to use for this kind of thing, but this technique can be adapted suitably.

If you have any further queries, you can leave a comment below and I am very likely to answer it. In any case, thanks for reading, and good luck!

If you want to download my game "Super Displacement", you can at the link below:

http://gamejolt.com/games/super-displacement/244666

No comments :

Post a Comment