Unity WebGL Saving system for a game.

Hello, I am currently trying to make my first game in unity, and wanted to upload it on itch.io and github pages. Problem is I made the saving system and all but it only works in the Unity play mode, when I upload it on the web it just doesn't work.

using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public class DataSave
{
    private const string FileType = ".txt";
    private static string SavePath => Application.persistentDataPath + "/saves/";
    private static string BackUpSavePath => Application.persistentDataPath + "/backups/";

    private static int SaveCount;
    public static void SaveData<T>(T data, string fileName)
    {
        Directory.CreateDirectory(SavePath);
        Directory.CreateDirectory(BackUpSavePath);

        if (SaveCount % 5 == 0)
            Save(BackUpSavePath);
        Save(SavePath);
        SaveCount++;

        void Save(string path)
        {
            using (StreamWriter writer = new StreamWriter(path + fileName + FileType))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                MemoryStream memoryStream = new MemoryStream();
                formatter.Serialize(memoryStream, data);
                string dataToSave = Convert.ToBase64String(memoryStream.ToArray());
                writer.WriteLine(dataToSave);
            }
        }
    }

    public static T LoadData<T>(string fileName)
    {
        Directory.CreateDirectory(SavePath);
        Directory.CreateDirectory(BackUpSavePath);

        bool backUpInNeed = false;
        T dataToReturn;

        Load(SavePath);
        if (backUpInNeed)
        {
            Load(BackUpSavePath);
        }

        return dataToReturn;

        void Load(string path)
        {
            using (StreamReader reader = new StreamReader(path + fileName + FileType))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                string dataToLoad = reader.ReadToEnd();
                MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(dataToLoad));
                try
                {
                    dataToReturn = (T)formatter.Deserialize(memoryStream);
                }
                catch
                {
                    backUpInNeed = true;
                    dataToReturn = default;
                }
            }
        }
    }

    public static bool SaveExists(string fileName)
    {
        return (File.Exists(SavePath + fileName + FileType)) ||
               (File.Exists(BackUpSavePath + fileName + FileType));
    }
}

99% sure the problem is in the Application.persistentDataPath, but I have no idea how to fix it.

You have very limited access to file system in a webgl app. I believe File.Write..() with just the filename should work because that maps to the app’s sandbox storage area (limited to 10mb at most, possibly less). But when you try to access specific paths that is likely to fail because specific paths do not exist in a webgl app.

See this post and thread: https://discussions.unity.com/t/619521/6

What I did at the end is using PlayerPrefs, which I don't think is the best solution but it worked. I hope this helps someone in the future.

using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public class DataSave
{
    private const string FileType = ".txt";

    private static int SaveCount;
    public static void SaveData<T>(T data, string fileName)
    {
        Save();

        void Save()
        {
            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream memoryStream = new MemoryStream();
            formatter.Serialize(memoryStream, data);
            string dataToSave = Convert.ToBase64String(memoryStream.ToArray());
            PlayerPrefs.SetString(fileName + FileType, dataToSave);
        }
    }

    public static T LoadData<T>(string fileName)
    {
        T dataToReturn = default;

        Load();

        return dataToReturn;

        void Load()
        {
            string dataToLoad = PlayerPrefs.GetString(fileName + FileType, "");
            if (!string.IsNullOrEmpty(dataToLoad))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(dataToLoad));
                try
                {
                    dataToReturn = (T)formatter.Deserialize(memoryStream);
                }
                catch
                {
                    dataToReturn = default;
                }
            }
            else
            {
                dataToReturn = default;
            }
        }
    }

    public static bool SaveExists(string fileName)
    {
        return PlayerPrefs.HasKey(fileName + FileType);
    }
}

Keep in mind: the storage limit for PlayerPrefs in WebGL is just 1 MB!

Check the length of dataToSave and multiply by how many different save games you have, and consider if a player will be able to grow that size - for example if it stores inventory items and the inventory size is fixed you can calculate the max. memory usage relatively well. But if the savegame contains data per object spawned and there is no limit to how many the player can spawn, then technically you have no limit for savegame size and can't use PlayerPrefs.

1 Like

In case PlayerPrefs won't fit your usecase anymore: Your previous save code should work, it is probably just missing a call to sync the files. The thread that CodeSmile referenced mentions this, too. Here a direct link to the post that contains the current way of syncing the files to IndexedDB: https://discussions.unity.com/t/619521/11