How to use the GameJolt achievements API

If you’re trying to find somewhere to publish your Blender game after making it, you could try GameJolt.
One of the great things about the site is it allows you to save high scores, saved games and add trophies and achievements to your game which show up on the game homepage.

It’s actually pretty easy to do, if you know a little bit of python you can set it up quite quickly and you can test it without even uploading the game.

First, here’s the Achievements API. This is useful to look at but not essential, as someone has already written a python interface that we will be using.

So, go get the python API. (extra info here).

You can read the documentation and copy the python script in to a blender text window. You can call the script whatever you like, but make sure it ends with .py


In your main script, you have to import the game API. Just add it to your imports, like “import bge”


You will be dealing with an object, from the GameJoltTrophy class.

So you first need to create this object:

py_gjapi.GameJoltTrophy(<i>username</i>, <i>user_token</i>, <i>game_id</i>, <i>private_key</i>)[source]
  • py_gjapi is whatever you’ve called your gamejolt api script when you imported it.
  • username is the player’s gamejolt username.
  • user_token is the game token given to gamejolt users. It can be found next to the user’s Avatar.
  • game_id is the id number of your game,
  • private_key is the special key for your game to access the website for setting trophies.

You can get most of this info from your game on gamejolt:


There are a number of ways of getting the player’s username and game token. You could get them to write it in a txt file and read this from blender, or use a keyboard sensor with allkeys and keylogging.


This will save the user name (and later the game token) in globalDict where they can be picked up and added to the GameJoltTrophy object when you create it.

1 Like

API Object

Now to create your game API object:


Pick up username and token from where you’ve saved them in the globalDict.
You have to enter your game ID and private key here. I guess someone could hack your blend and find the private key, but it’s not a big problem. You can always generate a new key if you think you’ve been hacked. :slight_smile:

You should check if the user name and token are correct by using:

valid_user = jolt_object.authenticateUser()

This can take a second or two to process, so leave some text on screen saying “searching” or whatever. You can print a message to say that the username is valid, or you can skip straight to the game, your choice.

Don’t forget to put your game jolt object in to the global dict so you can pick it up later.

bge.logic.globalDict['jolt_object'] = jolt_object

***Scores.
Adding high scores is simple, you just need to call

score = own['score']

bge.logic.globalDict['jolt_object'].addScores(score,score)

To upload a score.
Again it can take a moment or two to upload so it’s good to do this at a time when there isn’t anything happening on screen. Instead of when your player dies, it can be best to send a request when you press space to restart, or when you exit to the main menu or whatever.

You can set the high score table manually if you have more than one by including the table_id. You can also allow guest scoring (people who didn’t log in) by using guest=True, guestname=‘guest’
In fact if you can’t get anything else to work or you don’t want the fuss of people having to set thier user name and token, you can still use your game ID and private key with a blank ID and game token to log in to game jolt and add scores for guest users.

Here’s the format you should follow:

addScores(score, sort, table_id=None, extra_data='', guest=False, guestname='')

You can fetch scores for the game too, so they can be displayed in your game. In that case use:

fetchScores(<i>limit=10</i>, <i>table_id=None</i>, <i>user_info_only=False</i>)

which will return a dictionary with two items:
success and scores.


Success tells you if it found the scores, and scores is just a list of scoring info.

if scores['success']:
    scoreboard = scores['scores']
else:
    scoreboard = []

I’m sure you can think of ways to display this info in a scoreboard in your game. :stuck_out_tongue:

***Trophies.
There are also the trophies.

You need to set up the trophies in Gamejolt first, as that will give you the trophy ID. You need it to set a trophy as achieved. You can go back and edit them later, adding a trophy image and other info as you like.

Again adding trophies is as simple as making a call you your game jolt object:


Remember, making a call to the gamejolt website takes quite a bit of time. So don’t do the check often.
In my case I make the call when you start a new level, but you could make it happen when you pick up a certain object, or kill a special enemy or whatever. The best time though is when starting or leaving a scene, as the normal lag of loading a new scene hides the lag of sending a message to game jolt.

As with score you can get a list of a player’s trophies to display in game.

fetchTrophy(<i>achieved=None</i>, <i>trophy=None</i>)

***game data:

EDIT:
After testing it seems you can’t send game data with any spaces. So we have to convert the string to have a dummy character which we convert back when loading.

If you store the gamelogic dictionary as json first, then use replace on the json.dumps(data) object, you can save it just fine.
Then fetch it, replace the filler with spaces and then use json.loads(data) to get a dictionary with properly formatted items.


import json
#####################

### save

data_file = {"something":(12,"x",-12),"other":["this is 
 a test"]}

data_item = json.dumps(data_file)

converted = data_item.replace(" ","%20")

set_data = jolt_object.storeData("saved",converted)

######################

### load
        
recovered = jolt_object.fetchData("saved")["data"]

reconverted = recovered.replace("%20"," ")

loaded_data = json.loads(reconverted)

print (loaded_data)

print (loaded_data["something"][2] + 5)

This will preserve all your data. Though it’s best not to try and save classes or other complex objects. Strings, ints, floats, lists, dicts, tuples etc… should all be fine.

***Taking it to the next level:

I think this achievements system is great. It opens doors for some interesting meta games, such as a treasure hunt, where people have to play a few different games to pick up trophies to unlock a final game which can only be played if you have all the trophies. Think of the novel “Ready Player One” which sets up a easter egg hunt for the players of a MMORPG with a prize of millions of dollars. It’s a shame there’s not really that many games on game jolt that use the system.

Battery (the host of BGMC16) did a good tutorial on using phyon’s built in webbrowser module to access a webpage from within Blender. If you wanted you could use that as a starting point for writing your own achievements and scores API to add high scores toyour own website.

Just to show that guest scoring works:

[ATTACH=CONFIG]386232[/ATTACH]

You don’t need to enter a valid user name, just a valid game ID and private key.
Don’t forget to set “allow guest scores” on the scoreboard for your game at gamejolt.

Thanks for this resource! However, does this require theese game tokens? I didn’t read whole thing fully yet. I will obiouvsly use this to publish some of my simplier projects in gamejolt later.

Yes, after uploading your game you can get some info that allows you to set up high scores and such. With flash games or other browser based games they can pick up the player info (game token and user name) automatically, but as blender games must be downloaded before playing the player has to enter log in style info directly in to your game (unless you use guest scores).

Some things to remember;
Only generate the gamejolttrophy object once. You can save the object in global dict and access it later.
Only send high scores and trophy info when you have to. You don’t need to send the high score info to the website every frame just when the player dies or the game restarts.
Make sure the script name matches your import name.
If you have any problems check the console.

Hi smoking mirror,
Thanks for this tutorial :slight_smile:, I’m pretty sure I’ll use this for my game (Aside of if my game will finish one day or not…).
Can you please tell me how to save the globalDict file to gamejolt? And how to load it?
So people can play the game on different computers and/or multiple people can play the game at one computer using multiple gamejolt profiles.

This sounds prety interesting. Maybe also a way for players to play game multiplayer using their GameJolt profiles? So that their data is saved in GameJolt(health, equip, owned locations etc.) and they can always log in and get their saved stuff aswell as see other players, fight them or help them.

From what I can tell the data storage is a JSON dictionary but you can just insert the elements of a dictionary in to it one by one using the API. When you download the script theres a documentation file in there. Just open up the methods part to find how to put data in to storage.

One cool thing I can see about this is you can save data from one game into a different game. So you can transfer saved games from a demo to a full release. Or you could save a character from episode one of a game and carry them in to episode two. You just need to use the second game’s game_id and private key when saving.

But the data is tied to a specific user and token so I dont think you could use it for multiplayer or sharing data between multiple players.

logic.loadGlobalDict()
globalDict = logic.globalDict
data = json.dumps(globalDict)
print(data) #this works, I see the whole globalDict printed in the console
key = str(username + '_globalDict')
jolt_object.storeData(key, data, user_info_only=False) #it doesn't appear in gamejolt

No errors in console, but it doesn´t appear on the data storage page in gamejolt.
When I set data to something simpler like ´hello´, it does work and it will appear in gamejolt.

EDIT: I noticed that it won’t upload to gamejolt when there are spaces in the string, so maybe I should first replace them with another symbol like _

EDIT:

data = str(json.dumps(globalDict))
dataCompatible = str(str.replace(data, ' ', '%20'))

When you replace ’ ’ with ‘%20’ it will appear as ’ ’ in gamejolt. So this works fine now, now I’m going to look how to import from gamejolt to globalDict

jolt_object.fetchData(key, user_info_only=False)

Hi smoking_mirror,

Can you tell me how to get the data after doing this?

I see you used this after fetching the scores:

if scores['success']:    
    scoreboard = scores['scores']
else:
    scoreboard = []

But what to do after fetching data?

I think this is because the data is being sent as a URL, and they shouldn’t contain whitespace???

If you store the gamelogic dictionary as json first, then use replace on the json.dumps(data) object, you can save it just fine.
Then fetch it, replace the filler with spaces and then use json.loads(data) to get a dictionary with properly formatted items.

#####################

### save

data_file = {"something":(12,"x",-12),"other":["this is 
 a test"]}

data_item = json.dumps(data_file)

converted = data_item.replace(" ","%20")

set_data = jolt_object.storeData("saved",converted)

######################

### load
        
recovered = jolt_object.fetchData("saved")["data"]

reconverted = recovered.replace("%20"," ")

loaded_data = json.loads(reconverted)

print (loaded_data)

print (loaded_data["something"][2] + 5)

This will give you something like this:


***A thought on multiplayer…

It’s possible to save data which is not linked to a single player. It’s available globally for the game.

You could add data with a local keycode, containing the player names and a password:

our_game = jolt_object.storeData("Pickledtezcat$vs$adriansnetlis&f2425",multiplayer_data)

Then load that data to share it between two different machines. Because the local game would need to know the player names and the password, it would be mostly secure.

But, it’s very slow. :frowning:

Save and load even a simple dict like in the above example takes 0.8 seconds on my computer, which is too slow for the likes of a FPS. You could use it for a turn based game though (like chess), or a play by e-mail style multiplayer game (civilization 3 had this option). In that case just save and load at the start and end of each turn. You could use threads to avoid the saving and loading locking up the game loop each turn.

One problem would be matchmaking though. You could set up a kind of chatroom, using save and load data to display currently logged in players. When you choose a partner for play you could be given a password automatically.

Once you start doing all this though it makes me think, why not just use the game data to save and display a list of logged in players, then use Agoose’s multiplayer module to handle the actual multiplayer. You could use the session part of the gamejolt API to show who is online and ready for matchmaking.

Thanks!
Is it also possible to save and fetch data while being a guest?
That would be awesome because I have something in mind that doesn’t require the player to have a gamejolt account.

Yes, I think so.
Global data can be set by anyone I think.

Thanks smokingmirror for explaining us how to use the gamejolt api and for helping me :slight_smile:

EDIT:
You said in the game data section that we have to convert spaces to a dummy character.
When I tested it I converted it to %20. When I looked in gamejolt I saw gamejolt had already converted %20 into spaces which is great because we don’t have to convert it anymore when we fetch the data.

The documentation says also that it is a bug:

  • The blankspace character
  • Some methods on the API requires that a string is passed as an argument. These arguments is for storing not only a single word, but an entire text, according to the wishes of the user. For the first release, it won’t be supported this blank character. But if you want it anyways, you can write your text replacing the blank spaces by a ‘+’ character. It will work just fine for the API and won’t give you a signature error. Sorry for this, it was bit of a rush to think on a good way to solve this. Youcan expect this being fixed even on the minor update of the module.