00:00
00:00
3p0ch
If you like hard games try my Daxolissian System series

plasmid @3p0ch

Cat

Scientist

Read the manual & try stuff

So Cal

Joined on 2/13/10

Level:
13
Exp Points:
1,708 / 1,880
Exp Rank:
38,121
Vote Power:
5.50 votes
Audio Scouts
1
Rank:
Portal Security
Global Rank:
23,485
Blams:
50
Saves:
377
B/P Bonus:
8%
Whistle:
Normal
Medals:
4,746
Supporter:
3y 10m 15d

Comments

Thank you for the update! This is really a great and thorough guide. I hope I get to try it soon.

About the data being saved when you test locally, I'd probably simply split the behaviour if you're not in WebGL, i.e.:

if (Application.platform != RuntimePlatform.WebGLPlayer) SaveNormally();
https://docs.unity3d.com/ScriptReference/Application-platform.html

or platform-dependent compilation
https://docs.unity3d.com/Manual/PlatformDependentCompilation.html

#if UNITY_EDITOR
SaveNormally();
#elif UNITY_WEBGL
Save()
#else
// whatever
#endif

Oh, that looks like it might work nicely by wrapping everything in the class definition in

[code]
public static class NGData {
#if UNITY_WEBGL
the current code
#else
// for each PlayerPrefs.FunctionName
public static void FunctionName(parameters) {
PlayerPrefs.FunctionName(parameters);
}
#endif
}
[/code]

Thank you for your post! I also discovered that you need to call sync function in javascript to ensure saved data is written into indexedDB.

Old Unity you can call this after you saved the data.
Application.ExternalEval("FS.syncfs(false, function (err) {})");

In newer Unity you need to create a .jslib and expose the function to Unity:
https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html

And I wrote something like this in .jslib:

mergeInto(LibraryManager.library, {
WebIdbfsSync: function () {
FS.syncfs(false, function (err) {});
},
});

Then you can call WebIdbfsSync() in Unity. :D

This code has two issues.
One mentioned by puetsua.
Another one is that it completely breaks once you have a string with any new line characters.

Here is code that fixes both of them. I use it in my game.

File: NewgroundsPlayerPrefs.cs

#if UNITY_WEBGL && !UNITY_EDITOR
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public static class PlayerPrefs
{
public static string savePathName = "12345";
public static string DataPath => "/idbfs/" + savePathName + "/NGSave.dat";

[Serializable]
private class PlayerPrefsData
{
public List<KeyValue> data;
}

[Serializable]
private class KeyValue
{
public string Key;
public string Value;
}

static Dictionary<string, string> saveData = new Dictionary<string, string>();

// This is the static constructor for the class
// When invoked, it looks for a savegame file
// and reads the keys and values
static PlayerPrefs()
{
// Open the savegame file and read all of the lines
// into fileContents
// First make sure the directory and save file exist,
// and make them if they don't already

if (File.Exists(DataPath))
{
// Read the file if it already existed
string data = System.Text.Encoding.UTF8.GetString(File.ReadAllBytes(DataPath));

// If you want to use encryption/decryption, add your
// code for decrypting here
// ******* decryption algorithm ********

PlayerPrefsData ppd = JsonUtility.FromJson<PlayerPrefsData>(data);
if (ppd != null)
{
saveData = ppd.data.ToDictionary(t => t.Key, t => t.Value);
}
else Debug.LogError("WebGL PlayerPrefs wrong format!");
}
}

[DllImport("__Internal")]
private static extern void FlushIDBFS();

// This saves the saveData to the player's IndexedDB
public static void Save()
{
PlayerPrefsData ppd = new PlayerPrefsData
{data = saveData.Keys.Select(t => new KeyValue {Key = t, Value = saveData[t]}).ToList()};

// If you want to use encryption/decryption, add your
// code for encrypting here
// ******* encryption algorithm ********

// Write fileContents to the save file
string data = JsonUtility.ToJson(ppd);
byte[] bytedata = Encoding.UTF8.GetBytes(data);

File.WriteAllBytes(DataPath, bytedata);

FlushIDBFS();
}

// The following methods emulate what PlayerPrefs does
public static void DeleteAll()
{
saveData.Clear();
}

public static void DeleteKey(string key)
{
saveData.Remove(key);
}

public static float GetFloat(string key)
{
return float.Parse(saveData[key]);
}

public static float GetFloat(string key, float defaultValue)
{
if (saveData.ContainsKey(key))
{
return float.Parse(saveData[key]);
}
else
{
return defaultValue;
}
}

public static int GetInt(string key)
{
return int.Parse(saveData[key]);
}

public static int GetInt(string key, int defaultValue)
{
if (saveData.ContainsKey(key))
{
return int.Parse(saveData[key]);
}
else
{
return defaultValue;
}
}

public static string GetString(string key)
{
return saveData[key];
}

public static string GetString(string key, string defaultValue)
{
if (saveData.ContainsKey(key))
{
return saveData[key];
}
else
{
return defaultValue;
}
}

public static bool HasKey(string key)
{
return saveData.ContainsKey(key);
}

public static void SetFloat(string key, float setValue)
{
if (saveData.ContainsKey(key))
{
saveData[key] = setValue.ToString();
}
else
{
saveData.Add(key, setValue.ToString());
}
}

public static void SetInt(string key, int setValue)
{
if (saveData.ContainsKey(key))
{
saveData[key] = setValue.ToString();
}
else
{
saveData.Add(key, setValue.ToString());
}
}

public static void SetString(string key, string setValue)
{
if (saveData.ContainsKey(key))
{
saveData[key] = setValue;
}
else
{
saveData.Add(key, setValue);
}
}
}
#endif

File: FlushIDBFS.jslib

var FlushIDBFS = {
FlushIDBFS : function()
{
FS.syncfs(false, function (err) {});
},
};
mergeInto(LibraryManager.library, FlushIDBFS);

@IncontinentCell You forgot to initialize the Directory / File in your version!

@Frenchie14 Now that I look at it, it seems to be the case, but it appears to be working fine. Haven't gotten any complaints about saves disappearing. Let me quickly test it.
Just tested it by deleting all the files related to the game in IDBFS and it saved anyway. At least in chrome. I suppose that's because IDBFS is using a key value database and only emulates directories, which means it doesn't check if a directory exists. You can see what I mean by clicking "F12" -> "Application" -> "Indexed database"

@IncontinentCell Currently, without the directory initialization a brand new build *will* fail to do any operations. I have added the code from the original that checks the directory/file's existence and creates it if it doesn't exist and it now works. (Unity 2022 LTS)