GMS2: Telling the player there is a new update available


This devlog covers the challenges of working out how to read the latest version of your game from Itch.io's API endpoint in regards to Game Maker Studio 2. While it's based on the challenges of getting GMS2 to read the file it can be helpful for developers using other programs to work things out on their own.

Disclaimer: I am no expert when it comes to GMS2. My solution is likely inelegant and niche when it comes to reading .json files but it's the way I managed to get it to work.

The Problem

While the itch.io app automatically pushes updates to the player on things that they have downloaded many people still use their web browsers to download and play games or use tools. While that's fine for games that are out of development a game that is in early access can receive updates weekly, daily, or even multiple times a day.  So how do you let someone know that their is an update available and take the worry of checking constantly away from them? 

The simple answer is to put an indicator in the game that let's the player know. In theory it should be easy to do as itch.io offers an API endpoint for developers to "query the latest version of a game and notify your players from within the game, that a new version is available". While it seems simple enough the actual implementation can be tricky especially for developers who haven't had much, or any, experience in http requests and reading .json files.


The Setup

Itch.io's API endpoint for each game returns a single line of code wrapped in a .json file. It's simple enough and contains either the user generated version or itch's own versioning. Either way the line will read something like this:

{"latest":"0.3.1.5"}

In my case the URL for this endpoint is https://itch.io/api/1/x/wharf/latest?target=theonewhoisodd/ovivore&channel_name=windows-pre-alpha but we'll come back to this.

We now need to look at a few GML functions to start with to meet our basic needs "http_get_file", "file_text_open_read", "file_text_read_string".

The first of these function "http_get_file" is what we use to pull the file from itch's website. It's a simple two variable function that requires the website you want to pull a file from, and the name you want to save the file as: http_get_file( website, fileName). Once the file is downloaded to our working directory, "appdata\local\Ovivore" in my case, we'll use the next function "file_text_open_read" to mark which files we are going to start reading from. Again a very simple function with only 1 variable: file_text_open_read(working_directory +  fileName)

Now we start to get into the slightly more complicated aspects. We will now use "file_text_read_string" to pull the contents out of the file we just opened. Where this becomes a little more complex is that we aren't going to be using the file name for this. Instead when we use the previous function "file_text_open_read" we save the result to a local variable. In my case I used readFile but you can use whatever you want that helps you keep it straight in your head. Thus we wind up with file_text_read_string(readFile) and we are going to pass that on to another variable which will finally give us our raw string that we saw at the API endpoint above.

So far we should have something like this:

file = http_get_file("https://itch.io/api/1/x/wharf/latest?target=theonewhoisodd/ovivore&channel_name=windows-pre-alpha", "latest.json");
readFile = file_text_open_read(working_directory + "latest.json");
latest = file_text_read_string(readFile);

Note: The very first local variable "file" can be anything you want. It's never referenced again but as far as I'm aware you have to store the result of the http_get_file function somewhere in order to get it to work.

At this point we have the the raw string of the .json file into our game but still no easy way to compare it against GMS2's built in version info  which displays as:

"0.3.1.5"

If we were to compare the two we would obviously find that {"latest":"0.3.1.5"} is not the same as "0.3.1.5" even though the version numbers are actually the same. So from here we have to figure out how to get the version number out of the string.

Iterating the String

We're going to need our final three functions now: "string_length", "string_char_at", and a "for loop". We're going to use the rather simple "string_length" to, shockingly, pull the length of the string that we saved to the "latest" variable. It returns a real number so we are going to save that under "strLength". With the length in hand we are now going to put that into a for loop.  We know that the first part of the .json code will always be the same:  {"latest":" so we are going to count the characters in that string and keep it in our minds (there are 11 characters). Next within the "for loop" we will initialize a temporary variable, i, and set that to 13. We'll get to why we don't set it to 11 in a second but first we're going to work on the next two parts of the "for loop", the next bit of which is telling it how long it should run. For this we are going to tell it that while i is less then strLength it should run the code. Finally we will put in the last bit which is telling the loop what to do after every iteration which will be i++ . In other words we're telling the loop to add 1 to whatever is.

strLength = string_length(latest)
for (var i = 13; i < strLength; i++) {

So now we need to write the code that the loop should run. This is where string_char_at comes in. This function requires two variables the first being which string and the second being at what position in the string it should be looking at. For what string we're looking at we're going to use the local variable "latest" that we talked about earlier and the second variable will be what the for loop has told "i" it needs to be.

 Since we don't want the trailing string after the version number ( "}  ) and we know the character directly after our version number is always a double quotation we need to tell it that while string_char_at( latest, i) does not equalthat it can run the next bit of code. Once it comes to the double quotation mark though it needs to stop and break the loop.

House Keeping

There are a couple of things that I have passed over in the code that necessary to understanding this next bit. At this start of the code we need to initialize a global variable that we'll just call version and then set it to "0". However this is based upon my version numbers and if you use a different versioning scheme than I do then you will want to use the leading number or letter in your version code. (i.e. If you are already into version "1.0" you will want to use 1 for this variable, or if you use a letter code such as A0030 you will want to set this variable to "A"). 

Now because the lead up string to the version number is 11 characters long and we have already stored the first character of the version we are going to set "i" to 13 because we want to read the character directly after the leadup (character 11) and after the first character of your version number (character 12).

Another quick note is that in GML there are two bits of 'language' that may be hard to understand by someone unfamiliar with GML or other coding languages. "!=" is the same as saying does no equal and when you need to write a double quotation as part of a string you have to add a backslash so it appears like this "\"" otherwise GMS2 gets confused.

The Final Stretch

With everything else out of the way now we just need to finish fleshing out the for loop code. We are already saying that while string_char_at( latest, i) != "\"" then we will do 'something'. That something is this:

version = string(version)+string_char_at(latest,i);

What we are doing here is taking the current string of the global variable version and then adding the next character in the original string to it. By the time the loop finally hits a double quotation it will have added the whole version code. Each iteration of the loop will appear like this:

0
0.
0.3
0.3.
0.3.1
0.3.1.
0.3.1.5

Then once it has hit the double quotation it will pass over the if statement to the else statement that contains break; and we will finally have our full version number saved to our global variable. From there we can either have another object activate when the global is not the same number as the version of the game that the player is running, or run other code within the same object to warn the player.

The Final Code

globalvar version;
version = 0;
file = http_get_file("https://itch.io/api/1/x/wharf/latest?target=theonewhoisodd/ovivore&channel_name=windows-pre-alpha", "latest.json");
readFile = file_text_open_read(working_directory + "latest.json");
latest = file_text_read_string(readFile);
strLength = string_length(latest)
for (var i = 13; i < strLength; i++) {
    if string_char_at(latest,i) != "\""{
        version = string(version)+string_char_at(latest,i);
    }
    else{
        break;
    }
}

Other Notes:

There are definitely some issues with this code and it's a bit niche. It won't really work out for reading larger .json files where you would want to work out ds_maps and ds_lists. I unfortunately tried many variations of using them for this case but I have never used them before and need to work on my basic understanding of how the work and how they populate their information. Almost all of the tutorials and user guides that I have come across rely on the files being structured differently so there wasn't a clear one to one comparison between them and what I was trying to do here.

Get Ovivore

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.