Skip to content

Custom Save Location

By default, Game Creator saves games using the PlayerPrefs built-in system. However, although this solution is cross-platform and will work for most users, some might prefer to sync their saves with an online database or use a different system than Unity's PlayerPrefs.

Here we will explore how easy it is to extend the save location.

IDataStorage interface

To create a custom save location, one must create a class that implements the IDataStorage interface, which contains all the necesary methods to store game information.

To make things easier, we're going to create a very simple system that communicates with an online database and stores the game saves there using http requests.

Note

Notice that there aren't any error handling mechanism for sake of simplicity. A production-ready product should also check and inform of the necessary errors that may ocurr.

Let's create our storage location class called MyOnlineDatabase.cs:

[Serializable]
public class MyOnlineDatabase: IDataStorage
{
    private const string URL_DB_SET = "https://database.mywebsite.com/set";
    private const string URL_DB_GET = "https://database.mywebsite.com/get";
    private const string URL_DB_DEL = "https://database.mywebsite.com/del";

    string IDataStorage.Title => "My Online Database";
    string IDataStorage.Description => "Store data in online database";

    async Task IDataStorage.DeleteAll()
    {
        // Create a web request to delete the content
        UnityWebRequest request = UnityWebRequest.Post(URL_DB_DEL, "");
        UnityWebRequestAsyncOperation handle = request.SendWebRequest();
        while (!handle.isDone) await Task.Yield();
    }

    async Task IDataStorage.DeleteKey(string key)
    {
        // Create a web request to delete a key
        UnityWebRequest request = UnityWebRequest.Post(URL_DB_DEL, key);
        UnityWebRequestAsyncOperation handle = request.SendWebRequest();
        while (!handle.isDone) await Task.Yield();
    }

    async Task<bool> IDataStorage.HasKey(string key)
    {
        // Checks whether a key exists in the database (code 200)
        UnityWebRequest request = UnityWebRequest.Post(URL_DB_GET, key);
        UnityWebRequestAsyncOperation handle = request.SendWebRequest();
        while (!handle.isDone) await Task.Yield();
        return handle.webRequest.responseCode == 200;
    }

    async  Task<object> GetBlob(string key, Type type, object value)
    {
        // Create a request to get the value identified by a key
        UnityWebRequest request = UnityWebRequest.Post(URL_DB_GET, key);
        UnityWebRequestAsyncOperation handle = request.SendWebRequest();
        while (!handle.isDone) await Task.Yield();
        return JsonUtility.FromJson(
            handle.webRequest.downloadHandler.text, 
            type
        );
    }

    async Task<string> IDataStorage.GetString(string key, string value)
    { /* ... */ }

    async Task<float> IDataStorage.GetFloat(string key, float value)
    { /* ... */ }

    async Task<int> IDataStorage.GetInt(string key, int value)
    { /* ... */ }

    async Task SetBlob(string key, object value)
    {
        // Requests the creation or update of a value onto the database
        UnityWebRequest request = UnityWebRequest.Post(URL_DB_SET, new Data(){
            id =  key,
            data = JsonUtility.ToJson(value)
        });
        UnityWebRequestAsyncOperation handle = request.SendWebRequest();
        while (!handle.isDone) await Task.Yield();
    }

    async Task IDataStorage.SetString(string key, string value)
    { /* ... */ }

    async Task IDataStorage.SetFloat(string key, float value)
    { /* ... */ }

    async Task IDataStorage.SetInt(string key, int value)
    { /* ... */ }
}

The first properties Title and Description allow to give a name to this system, which later can be selected from a dropdown menu in the Preferences window.

The following methods define how data is manipulated: retrieving data, setting data and deleting data. There are 3 URL we're using to exemplify how we can create an http request to send the information to our server, which can delete, create or retrieve the information depending on the endpoint used.

Some methods have been skipped because their implementation was very similar to other ones.

It is important to note though that all methods have the async prefix and either return a Task object or a Task associated with an object. This is because there's a certain amount of time elapsed between the http request and the answer from the server. Being able to await requests let's you tailor how to safely chain commands and make sure each request is successfully fulfilled.

Back to top