- Home /
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?
Answer by furic · Apr 27, 2016 at 08:06 AM
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
}
Answer by SaVe1418 · May 09, 2017 at 05:21 AM
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?
GameSave$$anonymous$$anager.GetString("savegameAUTO");
then you need to put string savegameAUTO in GameSave.cs;
Thanks a lot SweatyChair, I will give it a shot.
@SweatyChair , where exactly should I put the GameSave$$anonymous$$anager.GetString("savegameAUTO"); ?
Answer by Giampy_Normo · Jun 27, 2021 at 06:01 PM
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
Your answer
Follow this Question
Related Questions
Playerprefs Question 0 Answers
Checkpoint with PlayerPrefs 0 Answers
How to save progress in the game? 1 Answer
How to save an Audio Mixer's value wich is adjusted with a Slider? 1 Answer
Save/load playerprefs 2 Answers