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
}