Migranting PlayerPrefs local saves to Google Play Games Saved Game (Snapshots)

Currently my game save all progress in local using PlayerPrefs, we are moving forward trying to implement Google Play Games Saved Games (Snapshots) and integrating the all players’ save to there.

Since PlayerPrefs use key-value pairs but Snapshots just use plain bytes, I would like to know if there’s already a script or plugin for it to save my time? Or I will need to do data serialization from the PlayerPrefs on my own, just like taught in here?

For those who look for the answer, here’s my code integrating using persistence save/load with Play Games. Basically I have a GameSave Class and save it to a binary file in local, merge is if there’s a Play Games snapshots. And use GameSaveManager.GetInt instead of PlayerPrefs.GetInt, etc.

GameSaveManager.cs:

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using Prime31;

public class GameSaveManager : MonoBehaviour
{

	// Some faking filename
	private const string FILENAME_GAMESAVE = "Physics.dat";

	// Events fired when progress is synced from iCloud / Play Games
	public static event Action loadOnlineCompletedEvent;

	private static GameSaveManager _instance;

	public static GameSaveManager instance {
		get {
			if (_instance == null) {
				GameObject g = new GameObject ("GameSaveManager");
				_instance = g.AddComponent<GameSaveManager> ();
				DontDestroyOnLoad (g);
			}
			return _instance;
		}
	}

	[SerializeField] private bool _debugMode = false;

	private static string gameSaveFilePath {
		get {
			return Path.Combine (Application.persistentDataPath, FILENAME_GAMESAVE);
		}
	}

	void Awake ()
	{
		_instance = this; // For debug only

		#if UNITY_ANDROID
		GPGManager.authenticationSucceededEvent += OnAuthenticationSucceeded;
		GPGManager.loadSnapshotSucceededEvent += OnLoadSnapshotSucceeded;
		GPGManager.saveSnapshotSucceededEvent += OnSaveSnapshotSucceeded;
		GPGManager.saveSnapshotFailedEvent += OnSaveSnapshotFailed;
		#endif
	}

	#region Get/Set

	public static bool GetBool (string key, bool defaultValue = false)
	{
		try {
			return (bool)(typeof(GameSave)).GetField (key).GetValue (GameSave.instance);
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:GetBool - Cannot find key={ 0}, error={ 1}", key, e));
			return defaultValue;
		}
	}

	public static void SetBool (string key, bool value, bool syncOnline = true)
	{
		try {
			(typeof(GameSave)).GetField (key).SetValue (GameSave.instance, value);
			if (syncOnline)
				SaveOnline ();
		}  catch (System.Exception e) {
			Debug.Log (string.Format ("GameSaveManager:SetBool - Cannot find key={ 0}, error={ 1}", key, e));
		}
	}

	public static int GetInt (string key, int defaultValue = 0)
	{
		try {
			return (int)(typeof(GameSave)).GetField (key).GetValue (GameSave.instance);
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:GetInt - Cannot find key={ 0}, error={ 1}", key, e));
			return defaultValue;
		}
	}

	public static void SetInt (string key, int value, bool syncOnline = true)
	{
		try {
			(typeof(GameSave)).GetField (key).SetValue (GameSave.instance, value);
			if (syncOnline)
				SaveOnline ();
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:SetInt - Cannot find key={ 0}, error={ 1}", key, e));
		}
	}

	public static string GetString (string key, string defaultValue = "")
	{
		try {
			return (string)(typeof(GameSave)).GetField (key).GetValue (GameSave.instance);
		}  catch (System.Exception e) {
			Debug.Log (string.Format ("GameSaveManager:SetString - Cannot find key={ 0}", key));
			return defaultValue;
		}
	}

	public static void SetString (string key, string value, bool syncOnline = true)
	{
		try {
			(typeof(GameSave)).GetField (key).SetValue (GameSave.instance, value);
			if (syncOnline)
				SaveOnline ();
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:SetString - Cannot find key={ 0}", key));
		}
	}

	public static DateTime GetDateTime (string key, DateTime defaultValue = default (DateTime))
	{
		try {
			return (DateTime)(typeof(GameSave)).GetField (key).GetValue (GameSave.instance);
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:GetDateTime - Cannot find key={ 0}, error={ 1}", key, e));
			return defaultValue;
		}
	}

	public static void SetDateTime (string key, DateTime value, bool syncOnline = true)
	{
		try {
			(typeof(GameSave)).GetField (key).SetValue (GameSave.instance, value);
			if (syncOnline)
				SaveOnline ();
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:SetDateTime - Cannot find key={ 0}, error={ 1}", key, e));
		}
	}

	public static List<int> GetIntList (string key, List<int> defaultValue = null)
	{
		try {
			return (List<int>)(typeof(GameSave)).GetField (key).GetValue (GameSave.instance);
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:GetIntList - Cannot find key={ 0}, error={ 1}", key, e));
			return defaultValue != null ? defaultValue : new List<int> ();
		}
	}

	public static void SetIntList (string key, List<int> value, bool syncOnline = true)
	{
		try {
			(typeof(GameSave)).GetField (key).SetValue (GameSave.instance, value);
			if (syncOnline)
				SaveOnline ();
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:SetIntList - Cannot find key={ 0}, error={ 1}", key, e));
		}
	}

	public static void AddIntList (string key, int value, bool syncOnline = true)
	{
		try {
			List<int> tmp = GetIntList (key);
			tmp.Add (value);
			SetIntList (key, tmp, syncOnline);
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:AddIntList - Cannot find key={ 0}, error={ 1}", key, e));
		}
	}

	public static void RemoveIntList (string key, int value, bool syncOnline = true)
	{
		try {
			List<int> tmp = GetIntList (key);
			tmp.Remove (value);
			SetIntList (key, tmp, syncOnline);
		}  catch (System.Exception e) {
			Debug.LogWarning (string.Format ("GameSaveManager:RemoveIntList - Cannot find key={ 0}, error={ 1}", key, e));
		}
	}

	#endregion

	#region Local Save / Load

	public static void Load ()
	{    
		if (File.Exists (gameSaveFilePath)) {

			GameSave.instance = GameSave.FromBytes (P31CloudFile.readAllBytes (FILENAME_GAMESAVE));

			//			 Debug.Log ("SavedGameManager:Load - Loading " + savedGameFilePath);

			BinaryFormatter bf = new BinaryFormatter ();
			FileStream fs = File.Open (gameSaveFilePath, FileMode.Open);

			GameSave.instance = (GameSave)bf.Deserialize (fs);
			fs.Close ();

			//			Debug.Log ("SavedGameManager:Load - GameSave=" + GameSave.instance);

			#endif

		}  else { // Create an empty one
			GameSave.instance = new GameSave ();
		}
	}

	public static void Save ()
	{
		if (instance._debugMode)
			Debug.Log ("SavedGameManager:Save - Saving " + gameSaveFilePath);

		BinaryFormatter bf = new BinaryFormatter ();
		FileStream fs = File.Create (gameSaveFilePath);

		bf.Serialize (fs, GameSave.instance);
		fs.Close ();
	}

	#endregion

	#region Play Games Snapshots Save / Load

	#if UNITY_ANDROID
	private void OnAuthenticationSucceeded (string param)
	{
	PlayGameServices.loadSnapshot ("GameSave");
	}

	private void OnLoadSnapshotSucceeded (GPGSnapshot snapshot)
	{
	if (instance._debugMode) {
	Debug.Log ("GameSaveManager:OnLoadSnapshotSucceeded()");
	Prime31.Utils.logObject (snapshot);
	}

	GameSave.instance.Merge (snapshot.snapshotData);

	if (loadOnlineCompletedEvent != null)
	loadOnlineCompletedEvent ();
	}

	private static void DoSaveOnline ()
	{
	PlayGameServices.saveSnapshot ("GameSave", true, GameSave.instance.ToBytes (), StringHelper.Localize ("Game save on {0}", DateTime.Now.ToString ("yyMMdd-HHmmss")));
	}

	private void OnSaveSnapshotSucceeded ()
	{
	Debug.Log( "OnSaveSnapshotSucceeded" );
	}


	private void OnSaveSnapshotFailed (string error)
	{
	Debug.Log( "OnSaveSnapshotFailed: " + error );
	}
	#endif

	#endregion

	#region Save Online

	private static float lastSaveOnlineTime = -10;
	// To prevent multiple sync in short period
	private static IEnumerator waitAndSaveOnlineCoroutine;

	private IEnumerator WaitAndSaveOnlineCoroutine ()
	{
		yield return new WaitForSeconds (5);
		SaveOnline ();
	}

	public static void SaveOnline ()
	{
		if (!PlayGameServices.isSignedIn ())
		return;

		if (instance._debugMode)
			Debug.Log (string.Format ("GameSaveManager:SaveOnline - GameSave.instance={ 0}, bytes.Length={ 1}", GameSave.instance, GameSave.instance.ToBytes ().Length));

		if (waitAndSaveOnlineCoroutine != null) // If already schedule, cancel it
			instance.StopCoroutine (waitAndSaveOnlineCoroutine);

		if (Time.unscaledTime - lastSaveOnlineTime < 5) { // Have a sync within 5 seconds, schedule it and return
			waitAndSaveOnlineCoroutine = instance.WaitAndSaveOnlineCoroutine ();
			instance.StartCoroutine (waitAndSaveOnlineCoroutine);
		}  else {
			DoSaveOnline ();
			lastSaveOnlineTime = Time.unscaledTime;
		}
	}

	#endregion

	void OnApplicationPause (bool isPaused)
	{
		if (isPaused) // Save on pausing / quiting the game
			Save ();
	}

}

GameSave.cs

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

[Serializable]
public class GameSave
{

	public int coins; // Used in MoneyManager
	public int freeCoinsCount; // Used in EventManager
	public DateTime freeCoinsDateTime;
	public bool removedAd; // Userd in AdsManager
	public string rewardedUpgradeVersion; // Used in RewardManager
	public int rewardedSurvivalSeconds;
	public List<int> ownedShips = new List<int> (); // Used in FlyerItem
	public List<int> ownedExpansions = new List<int> (); // Used in WorldInfo

	// How to merge a newer save from server
	public void Merge (GameSave gs)
	{
		if (gs == null) // Return if invalid data
			return;

		Debug.Log ("GameSave:Merge - merging " + gs);

		coins = gs.coins; // Get the newest
		freeCoinsCount = Mathf.Max (freeCoinsCount, gs.freeCoinsCount); // Get largest
		freeCoinsDateTime = gs.freeCoinsDateTime; // Get the newest
		removedAd = removedAd || gs.removedAd; // True if either one is true
		rewardedUpgradeVersion = gs.rewardedUpgradeVersion;
		rewardedSurvivalSeconds = Mathf.Max (rewardedSurvivalSeconds, gs.rewardedSurvivalSeconds); // Get largest
		ownedShips.AddRange (gs.ownedShips); // Merge 2 lists
		ownedShips.Distinct ().ToList ();
		ownedExpansions.AddRange (gs.ownedExpansions); // Merge 2 lists
		ownedExpansions.Distinct ().ToList ();
	}

	#region Consistent code - Don't change below

	private static GameSave _instance;

	public static GameSave instance {
		get {
			if (_instance == null)
				GameSaveManager.Load ();
			return _instance;
		}
		set {
			_instance = value;
		}
	}

	// For debug
	public override string ToString ()
	{
		StringBuilder sb = new StringBuilder ();
		sb.Append ("[GameSave: ");
		foreach (System.Reflection.FieldInfo fi in this.GetType().GetFields (BindingFlags.Instance | BindingFlags.Public)) {
			sb.Append (fi.Name);
			sb.Append ("=");
			if (typeof (IList).IsAssignableFrom (fi.FieldType)) { // A list
				sb.Append (StringHelper.ListToString (fi.GetValue (this) as IList));
			} else
				sb.Append (fi.GetValue (this));
			sb.Append (", ");
		}
		sb.Length -= 2; // Remove the last ', '
		sb.Append ("]");
		return sb.ToString();
	}

	public void Merge (byte[] bytes)
	{
		Merge (GameSave.FromBytes (bytes));
	}

	public static GameSave FromBytes (byte[] bytes)
	{
		if (bytes == null || bytes.Length == 0) {
			Debug.LogWarning ("SavedGameData:FromBytes - Serialization of zero sized array");
			return null;
		}

		using (MemoryStream ms = new MemoryStream (bytes)) {
			try {
				BinaryFormatter formatter = new BinaryFormatter ();
				GameSave data = (GameSave)formatter.Deserialize (ms);
				return data;
			} catch (Exception e) {
				Debug.LogError ("SavedGameData:FromBytes - Error when reading stream: " + e.Message);
			}
		}
		return null;
	}

	public byte[] ToBytes ()
	{
		BinaryFormatter bf = new BinaryFormatter ();
		using (MemoryStream ms = new MemoryStream ()) {
			bf.Serialize (ms, this);
			return ms.ToArray ();
		}
	}

	#endregion

}

hi there, i’m trying that script and visual studio give me error when put “using prime31;” hat can be the problem? im new at all of this

Thank you for the post furic. I am using visual scripting for my game and I have a lot of variables stored in the PlayerPrefs (like gold,combatants,gems, loot etc.), how can I do it with your code? They are all stored in
string data = PlayerPrefs.GetString(“savegameAUTO”);
I am a newbie in programming. Is there any way to integrate this?