Tutorials , Articles and Hope!
Tutorials , Articles and Hope!
Saving and loading with BSON

Saving and loading to BSON format

learn how to store your games sensitive information through BSON serialization.

Saving and loading with BSON

All games need some kind of storage right? In most cases we tend to rely on Json or playerprefs to store and handle our data but that has its drawbacks depending on where the info file is saved! On the other hand if we encrypt out data then our mind should be a bit more at ease, its time for Saving and loading tutorial!

We will be using the Json.Net package from unity and the BSON serializer found here!

Creating the Static class

Personally i do make a habit of relying on static classes for lots of my functions and variables so this is the route we will be taking for this particular tutorial but just in case you prefer otherwise you could also derive the following class from MonoBehaviour!

First of all we should download the Json.Net package from the Unity store! which you can find here.

using UnityEngine;
using System;
using System.IO;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json;

public static class DataHandler
{
 
}

Starting the class we need to reference a few namespaces from our newly imported package and declare the class as static!

Next in line is adding the class that will get serialized into a json format!

[Serializable]
public class Info
{
    public int exp = 0;
    public int coins = 0;
    public int level = 0;
}
Tip! Remember to add the [Serializable] attribute! and also remember to use the System namespace! otherwise it needs to be [System.Serializable]!

In this particular case we will be using just 3 variables but this is up to you to let it grow!

So far we should be looking at something like this:

using UnityEngine;
using System;
using System.IO;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json;

[Serializable]
public class Info
{
    public int exp = 0;
    public int coins = 0;
    public int level = 0;
}


public static class DataHandler
{

}

Great! lets move on now to the rest of the class!
This time we will add our variables which i made static so we can access them from anywhere! as well as making the serialized class into a static reference so we can too access that! To demonstrate here is how it should look like :

    private static Info info;
    public static Info Info { get => info; }

    public static int exp;
    public static int coins;
    public static int level;

This allows us to call those variables inside the serialized class from anywhere just by writing info.exp or any of the other ones and you will see the use of it later on when we create the in scene controller.

Now let us create a few functions that will handle

  • Creation of file.
  • Saving.
  • Loading.

Creating the functions, Saving and Loading

So normally when we play a game things tend to load up on their own on game start or through a button press! That is how the development spirits indented! But! its always good to create that loading file before everything else! In my opinion its always good to be more prepared with files that to let bugs run around freely. I cant stress how many times i have deleted my save file for testing only to find out my automated loading system throws an error since.. there is nothing to load! So in my amateur little mind i though why not create the base in case its missing!? And that i did!

public static void CreateData()
   {
       MemoryStream ms = new MemoryStream();
 
       Info inf = new Info
       {
           coins = 0,
           exp = 0,
           level = 0
       };
 
       using (BsonWriter writer = new BsonWriter(ms))
       {
           JsonSerializer serializer = new JsonSerializer();
           serializer.Serialize(writer, inf);
       }
 
       string data = Convert.ToBase64String(ms.ToArray());
 
       if (!File.Exists(data))
       {
           File.WriteAllText(Application.persistentDataPath + "/Data.json", data);
           Debug.Log("Create Data");
       }
       else
       {
           Debug.Log("Data exist");
       }
 
   }

The above function will come into play later on so bare with me!
A for a little explanation now! We open a memory stream and create a new object class called “inf” which then sets the variables to 0 so that our trusty serializer knows what to write in that wonderful json!
Meanwhile we are checking to see if our file exists in the devices data path, if it does, Sweet! move on! But in case it doesn’t we create it and write our info there!

Lets move on the two main functions that handle things! Yes saving and loading!

public static void Save()
    {
        MemoryStream ms = new MemoryStream();

        Info newInfo = new Info
        {
            coins = coins,
            exp = exp,
            level = level
        };

        using (BsonWriter writer = new BsonWriter(ms))
        {
            JsonSerializer serializer = new JsonSerializer();
            serializer.Serialize(writer, newInfo);
        }

        string data = Convert.ToBase64String(ms.ToArray());

        try
        {
            File.WriteAllText(Application.persistentDataPath + "/Data.json", data);
            Debug.Log("Data Saved");
            Debug.Log(data);

        }
        catch (Exception e)
        {
            Debug.Log("Error Saving Data:" + e);
            throw;
        }
    }


    public static void Load()
    {
        try
        {
            string userString = File.ReadAllText(Application.persistentDataPath + "/Data.json");

            byte[] data = Convert.FromBase64String(userString);

            MemoryStream ms = new MemoryStream(data);
            using (BsonReader reader = new BsonReader(ms))
            {
                JsonSerializer serializer = new JsonSerializer();

                Info e = serializer.Deserialize<Info>(reader);

                exp = e.exp;
                coins = e.coins;
                level = e.level;
            }
            Debug.Log("Data Loaded");
        }
        catch (Exception e)
        {
            Debug.Log("Error Loading Data:" + e);
            throw;
        }
    }

On the subject of saving i think we can spot quite a few similarities with the Creation function!
Once again we start with a new object reference of the class and store our variables in reference to the static ones that we will be using in the game. As far as the loading is concerned its the same principle but in reverse. We load up the file from the device and we convert it into a Byte array called “data” , following that we open a new stream using that data byte array and deserialize the class while simultaneously pulling the saved data back into the games variables!

  JsonSerializer serializer = new JsonSerializer();

                Info e = serializer.Deserialize<Info>(reader);

                exp = e.exp;
                coins = e.coins;
                level = e.level;

This part in specific since i do now realize that it might be confusing having the same names! the left part is our static variables which hold the current game session info which are equal to the serialized class variables!Thus whatever is saved will be loaded back to the game!


Finishing touches

For the sake of simplicity and the ability to better handle things i have also added a clear function which as the name implies clears the current session variables!

  public static void Clear()
    {
        Debug.Log("Data Cleared");
        exp = 0;
        coins = 0;
        level = 0;
    }

Altogether we should be looking at something along the lines of!

using UnityEngine;
using System;
using System.IO;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json;

[Serializable]
public class Info
{
    public int exp = 0;
    public int coins = 0;
    public int level = 0;
}


public static class DataHandler
{
    #region Data System
    private static Info info;
    public static Info Info { get => info; }

    public static int exp;
    public static int coins;
    public static int level;

    public static void CreateData()
    {
        MemoryStream ms = new MemoryStream();

        Info inf = new Info
        {
            coins = 0,
            exp = 0,
            level = 0
        };

        using (BsonWriter writer = new BsonWriter(ms))
        {
            JsonSerializer serializer = new JsonSerializer();
            serializer.Serialize(writer, inf);
        }

        string data = Convert.ToBase64String(ms.ToArray());

        if (!File.Exists(data))
        {
            File.WriteAllText(Application.persistentDataPath + "/Data.json", data);
            Debug.Log("Create Data");
        }
        else
        {
            Debug.Log("Data exist");
        }

    }

    public static void Save()
    {
        MemoryStream ms = new MemoryStream();

        Info newInfo = new Info
        {
            coins = coins,
            exp = exp,
            level = level
        };

        using (BsonWriter writer = new BsonWriter(ms))
        {
            JsonSerializer serializer = new JsonSerializer();
            serializer.Serialize(writer, newInfo);
        }

        string data = Convert.ToBase64String(ms.ToArray());

        try
        {
            File.WriteAllText(Application.persistentDataPath + "/Data.json", data);
            Debug.Log("Data Saved");
            Debug.Log(data);

        }
        catch (Exception e)
        {
            Debug.Log("Error Saving Data:" + e);
            throw;
        }
    }

    public static void Load()
    {
        try
        {
            string userString = File.ReadAllText(Application.persistentDataPath + "/Data.json");

            byte[] data = Convert.FromBase64String(userString);

            MemoryStream ms = new MemoryStream(data);
            using (BsonReader reader = new BsonReader(ms))
            {
                JsonSerializer serializer = new JsonSerializer();

                Info e = serializer.Deserialize<Info>(reader);

                exp = e.exp;
                coins = e.coins;
                level = e.level;
            }
            Debug.Log("Data Loaded");
        }
        catch (Exception e)
        {
            Debug.Log("Error Loading Data:" + e);
            throw;
        }
    }

    public static void Clear()
    {
        Debug.Log("Data Cleared");
        exp = 0;
        coins = 0;
        level = 0;
    }
    #endregion

}// class

Creating the in-game handler

Great! we managed to make our static class and now its time to make another script that will handle all of that information so fire up that right mouse button and click to create a new C# script!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Data : MonoBehaviour
{
    [SerializeField] private Info info; // so we can see our serialized class

    public Text text;
    private void Awake()
    {
        if (!PlayerPrefs.HasKey("GameInit"))
        {
            DataHandler.CreateData();
            PlayerPrefs.SetInt("GameInit", 1);
        }
        else
        {
            DataHandler.Load();
        }
    }

    private void Update()
    {
        UpdateVisual();
        ViewStats();
    }

    // for assigning the buttons
    public void AddValues(int _type)
    {
        switch (_type)
        {
            case 0:
                DataHandler.exp++;
                break;
            case 1:
                DataHandler.coins++;
                break;
            case 2:
                DataHandler.level++;
                break;
        }
       
    }

    public void SaveInfo()
    {
        DataHandler.Save();
    }

    public void LoadInfo()
    {
        DataHandler.Load();
    }

    public void Clear()
    {
        DataHandler.Clear();
    }

    void UpdateVisual()
    {
        text.text = "Exp" + " " + DataHandler.exp.ToString() + "\n" + "Coins" + " " + DataHandler.coins.ToString() + "\n" + "Level" + " " + DataHandler.level.ToString();
    }
    
    // for visual representation in the editor
    void ViewStats()
    {
        info.exp = DataHandler.exp;
        info.coins = DataHandler.coins;
        info.level = DataHandler.level;
    }
}

Did you see that we did use the CreateData function?! So to break things down a bit more i tend to use Playerprefs for storing information and variables that i either need to act as a check method for current game session or to store some references as to what the game should do! In this case i check if this particular key exists ( “GameInit” ) in order to load the game as soon as it runs! or in case it doesn’t just create the file so its ready to save all the things we need!

The rest of the functions are purely for visual purposes and demonstrations as to how the variables are adjusted! In the AddValues() function you can see me referencing the static variables we created in the beginning, For use of access and visibility the very first line of code after our class declaration is set so we can see our class in the inspector as we increment the values!

As they say a picture is worth a thousand words so here is the project in use!

SaveLoadSystemPreview

I can increment the variables with visual representation in the editor, save them , clear the view and load them back in!

To sum up!

Normally a json structure would be like this :

Save and load system

But! in our case since we are using BSON the output data is like this:

BSON serialized

Due to it being encrypted into a string its harder to break into and manually adjust the variables thus preventing the users from “Hacking” the game on their own!
yeah i know i have a spare coma..

Altogether our system is good to go and easily adjustable! If you need more variables saved all you got to do is add them in the serialized class and the save / load function!!

Thank you for your time and as always you can get the complete project in my Github !

Leave a comment