NOTE This article contains older information on saving game information on the Google Play servers. The updated documentation on Saved Games can be found on the Google Play Services wiki. |
In this article we will be exploring the setup of cloud saving on Android devices. Cloud saving gives you the ability to save any information in your game to the Google Play servers, and so perpetuate game data for each user across multiple devices and installs. Before continuing, you should have already setup and tested the Android export and have a test project or finished game that you want to add cloud saving into.
You can find out how to set up GameMaker for the Android platform here:
Note that if you want to add Amazon Appstore cloud saving, you should be using the functions that come as part of the GameCircle extension, as Google Cloud Sync is only available for the Google Play store:
Set Up Google Play
Before you can add any cloud sync code and test it, you first have to set up an app listing on your Google Play Developer Console for the game and you will also have had to upload an APK to one of the available channels for testing - either Internal Test (recommended), Alpha or Beta is fine. Once that has been done, you will also need to set up the Game Services for the app.
From the Developer dashboard, click the Game Services button on the left, then click the Add New Game button:
This will then show you a screen where you have to give some details about the game, including a name and a category:
After filling in the information and pressing Continue, you will now need to go to the Linked Apps section and get the App ID and set the app as being for Android:
The App ID is shown at the top and you will need to take a note of it as we'll be using it in GameMaker later. When you click the Android button, you will then be prompted to give some information about the app you want to link the services too, and you should link it to an app that you have previously uploaded to the store.
Once that's done, we have one final task and that is to enable Cloud Saving for the game services. This is done from the Game Details page:
Before publishing the game publicly, you will need to complete the rest of the game details on this page, but for now, you can simply enable saving and then continue on to add the code into your project in GameMaker.
Setting Up GameMaker
Now we need to prepare our game. For that you'll need to get the Google Licence Key from the Console and add it into GameMaker. You can get the key by going to the section Development Tools > Services and APIs:
Carefully copy this whole string and then in GameMaker open the Android Game Options and browse to the section Packaging. Here you need to paste the licence key string into the section labelled Google Licencing Public Key:
We now need to install the extensions that the cloud sync system requires to communicate with the Google APIs. For that we need the Google Play Services extension, which you can easily get by going to the Game Options > Add-ons page and clicking the Download button next to the extension:
This will add the extension to your project, and you can close the Game Options once this is complete.
NOTE: The Google Play Services extension comes in two parts, one for Ads and one for the general Google Play Services. If you do not require ads, then that section can be removed from the extension.
Once you have installed the extension, you then need to go to the Android Game Options and in the Social section tick Cloud Saving and add in the Services App ID that you got when you set up the game services API on the Play console (see the Setup Google Play section, above, for more details):
Click Apply or Ok to save the changes. We can now start to code our cloud saving...
Coding Cloud Saving - Setup
In general when working with the Android extensions, you will have a persistent controller object that is created once on game start and then perpetuated across every room of the game. Then, in this object, you would deal with the functions and asynchronous callbacks that the extensions generate. In this case, we are using the Google Play Services extension, and even if you are not using the other Play features, for cloud saving to work the user still needs to be logged in to Google Play.
To set things up, we'll use the following code in the Create Event or Game Start event of the controller object:
global.PlayerName = "";
global.PlayerLevel = 1;
global.PlayerConnected = false;
cloud_sync_id = -1;
cloud_save_id = -1;
if achievement_available()
{
if !achievement_login_status()
{
achievement_login();
show_debug_message("Logging Player In");
}
else
{
global.PlayerConnected = true;
cloud_sync_id = cloud_synchronise();
show_debug_message("Player Logged In!");
}
}
else show_debug_message("Cannot Connect To Google Play");
Here we check to see if the game can connect to the Google Services API, then we check to see if the player is logged in or not. We also set up some global variables to control everything and to give us something to save, and finally we call the cloud_synchronise() function to synchronise the game with the cloud (assuming the player is logged in already). This function returns a unique ID value which we store, and it also triggers an Asynchronous Cloud Event where we'll check this ID to get information about the save status.
We will also need to add some code here to load a backup file or set backup values should the player not actually be connected to the store nor logged in. For that we'd add something like this:
if !global.PlayerConnected
{
ini_open("save.ini");
global.PlayerName = ini_read_string("Data", "Player", "Guest");
global.PlayerLevel = ini_read_real("Data", "Level", 1);
ini_close();
}
If the save file exists, then the values will be loaded from that, but if it doesn't then they will be initialised to default values.
We should now add in a new event to the object, this time an Asynchronous Social Event. This is where we will deal with the logging in to the Google Play Services, retrieve some data about the player, and then synchronise with the cloud:
if ds_map_exists(async_load, "id")
{
if async_load[? "id"] == achievement_our_info
{
global.PlayerName = async_load[? "name"];
cloud_sync_id = cloud_synchronise();
show_debug_message("Player Logged In Correctly");
}
}
With that done, we need to turn our attention to the Asynchronous Cloud Event.
The Asynchronous Cloud Event
As has been mentioned previously, the cloud functions will trigger asynchronous callbacks that can be dealt with in the Asynchronous Cloud Event of the controller object. Let's look at what this event contains when the cloud_synchronise() function has been called.
In our test code, above, if the player is logged in or if a login has been successful, we called cloud_synchronise() so we need to add the following code to deal with the callback that it generates:
if ds_map_exists(async_load, "id")
{
if async_load[? "id"] == cloud_sync_id
{
cloud_sync_id = -1;
switch(async_load[? "status"])
{
case 0:
show_debug_message("CloudSynced - "+ async_load[? "resultString"]);
show_debug_message("Description - "+ async_load[? "description"]);
ini_open("save.ini");
global.PlayerName = ini_read_string("Data", "Player", "Guest");
global.PlayerLevel = ini_read_real("Data", "Level", 1);
ini_close();
break;
case 1:
case 2:
show_debug_message(async_load[? "resultString"]);
break;
default:
show_debug_message(async_load[? "errorString"]);
break;
}
}
}
In the above example, we simply check to make sure the event was triggered on synchronise by checking the synchronise ID, and then we check the status of the callback and react accordingly. This is the minimal code required and you should edit and adapt it to suit your game. Note that if the file we have synchronised has been updated, then we access it and update the required variables.
Saving Data To The Cloud
To save data to the cloud, you have two options:
- save a single string using the function cloud_string_save()
- save a single file using the function cloud_file_save()
Which you choose is up to you, but note that you can only save one "data blob" per user, per game.
To save data, you simply call one of those functions, and in this example we'll save the INI file that we created earlier. The code would look something like this (and can go anywhere in the game that it's required):
ini_open("save.ini");
ini_write_string("Data", "Player", global.PlayerName);
ini_write_real("Data", "Level", global.PlayerLevel);
ini_close();
if global.PlayerConnected
{
cloud_save_id = cloud_file_save("save.ini", "General save game data");
}
The unique ID value for the save function call will be stored in a variable and, like with the cloud_synchronise() function, this can be checked in the Asynchronous Cloud Event. You already have that event with code, so it's simply a case of adding another if to check for this ID:
if async_load[? "id"] == cloud_save_id
{
cloud_save_id = -1;
if async_load[? "status"] < 0
{
global.PlayerConnected = false;
show_debug_message("Cloud Upload Failed: " + async_load[? "errorString"]);
}
else if async_load[? "status"] == 3
{
show_debug_message(async_load[? "resultString"]);
}
}
We've simply added debug messages in to the code, but you can add whatever is required for your game here (like rechecking if the Play Services API is still available, or if the user is logged in, etc...).
Summary
As you can see, cloud saving using Google Play is a simple process and requires only a couple of function calls. When adding it to your game, just keep in mind that if the user isn't connected to the store, the save will still be registered, so that the next time the game connects files will be synced. Any conflicts between the save file and the cloud file will be flagged to the user and they can resolve it themselves using the Google Play dialogues, but you may wish to add some code into the cloud async event to check this, should the player decide to cancel and not synchronise.
It's also important to note that you should NOT synchronise files saved using the function ds_map_secure_save(), as these files are device locked so if the user installs the game on another device, the save file won't work correctly, so don't use cloud saving for these kinds of files.