ProjectDDD/Assets/Plugins/Pixel Crushers/Common/Scripts/Save System/SaveSystem.cs
2025-05-30 18:12:31 +09:00

1144 lines
42 KiB (Stored with Git LFS)
C#

// Copyright (c) Pixel Crushers. All rights reserved.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace PixelCrushers
{
/// <summary>
/// This is the main Save System class. It runs as a singleton MonoBehaviour
/// and provides static methods to save and load games.
/// </summary>
[AddComponentMenu("")] // Use wrapper instead.
public class SaveSystem : MonoBehaviour
{
public const int NoSceneIndex = -1;
/// <summary>
/// Stores an int indicating the slot number of the most recently saved game.
/// </summary>
public const string LastSavedGameSlotPlayerPrefsKey = "savedgame_lastSlotNum";
[Tooltip("Optional saved game version number of your choosing. Version number is included in saved game files.")]
[SerializeField]
private int m_version = 0;
[Tooltip("When loading a game, load the scene that the game was saved in.")]
[SerializeField]
private bool m_saveCurrentScene = true;
[Tooltip("Highest save slot number allowed.")]
[SerializeField]
private int m_maxSaveSlot = 99999;
[Tooltip("When loading a game/scene, wait this many frames before applying saved data to allow other scripts to initialize first.")]
[SerializeField]
private int m_framesToWaitBeforeApplyData = 0;
[Tooltip("Log debug info.")]
[SerializeField]
private bool m_debug = false;
private bool m_isLoadingAdditiveScene = false;
private static SaveSystem m_instance = null;
private static HashSet<Saver> m_savers = new HashSet<Saver>();
private static List<Saver> m_tmpSavers = new List<Saver>();
private static SavedGameData m_savedGameData = new SavedGameData();
private static DataSerializer m_serializer = null;
private static SavedGameDataStorer m_storer = null;
private static SceneTransitionManager m_sceneTransitionManager = null;
private static bool m_allowNegativeSlotNumbers = false;
private static GameObject m_playerSpawnpoint = null;
private static int m_currentSceneIndex = NoSceneIndex;
private static List<string> m_addedScenes = new List<string>();
private static bool m_autoUnloadAdditiveScenes = false;
private static AsyncOperation m_currentAsyncOperation = null;
#if USE_ADDRESSABLES
private static UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<UnityEngine.ResourceManagement.ResourceProviders.SceneInstance> m_currentAsyncOperationHandle;
#endif
private static int m_framesToWaitBeforeSaveDataAppliedEvent = 0;
private static bool m_isQuitting = false;
#if UNITY_2019_3_OR_NEWER && UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void InitStaticVariables()
{
m_instance = null;
m_savers = new HashSet<Saver>();
m_tmpSavers = new List<Saver>();
m_savedGameData = new SavedGameData();
m_serializer = null;
m_storer = null;
m_sceneTransitionManager = null;
m_playerSpawnpoint = null;
m_currentSceneIndex = NoSceneIndex;
m_addedScenes = new List<string>();
m_currentAsyncOperation = null;
m_framesToWaitBeforeSaveDataAppliedEvent = 0;
m_isQuitting = false;
}
#endif
/// <summary>
/// Optional saved game version number of your choosing. Version number is included in saved game files.
/// </summary>
public static int version
{
get
{
return (m_instance != null) ? m_instance.m_version : 0;
}
set
{
if (m_instance != null) m_instance.m_version = value;
}
}
/// <summary>
/// When loading a game, load the scene that the game was saved in.
/// </summary>
public static bool saveCurrentScene
{
get
{
return (m_instance != null) ? m_instance.m_saveCurrentScene : true;
}
set
{
if (m_instance != null) m_instance.m_saveCurrentScene = value;
}
}
/// <summary>
/// Highest save slot number allowed.
/// </summary>
public static int maxSaveSlot
{
get
{
return (m_instance != null) ? m_instance.m_maxSaveSlot : int.MaxValue;
}
set
{
if (m_instance != null) m_instance.m_maxSaveSlot = value;
}
}
/// <summary>
/// When loading a game/scene, wait this many frames before applying saved data to allow other scripts to initialize first.
/// </summary>
public static int framesToWaitBeforeApplyData
{
get
{
return (m_instance != null) ? m_instance.m_framesToWaitBeforeApplyData : 1;
}
set
{
if (m_instance != null) m_instance.m_framesToWaitBeforeApplyData = value;
}
}
/// <summary>
/// If a saver requires additional frames after ApplyData() before the saveDataApplied() event
/// should be called, set this property.
///
/// Note: This value is reset to zero after every call to ApplySavedGameData.
/// </summary>
public static int framesToWaitBeforeSaveDataAppliedEvent
{
get { return m_framesToWaitBeforeSaveDataAppliedEvent; }
set { m_framesToWaitBeforeSaveDataAppliedEvent = value; }
}
public static bool debug
{
get
{
return (m_instance != null) ? m_instance.m_debug && Debug.isDebugBuild : false;
}
set
{
if (m_instance != null) m_instance.m_debug = value;
}
}
/// <summary>
/// Checks if an instance already exists, without also implicitly creating one.
/// </summary>
public static bool hasInstance
{
get { return m_instance != null; }
}
public static SaveSystem instance
{
get
{
if (m_instance == null && !m_isQuitting)
{
m_instance = PixelCrushers.GameObjectUtility.FindFirstObjectByType<SaveSystem>();
if (m_instance == null)
{
m_instance = new GameObject("Save System", typeof(SaveSystem)).GetComponent<SaveSystem>();
}
}
return m_instance;
}
}
/// <summary>
/// Reference to the DataSerializer in the SaveSystem's hierarchy.
/// SaveSystem will use it to serialize and deserialize saved game data.
/// </summary>
public static DataSerializer serializer
{
get
{
if (m_serializer == null)
{
m_serializer = instance.GetComponent<DataSerializer>();
if (m_serializer == null && !m_isQuitting)
{
Debug.Log("Save System: No DataSerializer found on " + instance.name + ". Adding JsonDataSerializer.", instance);
m_serializer = instance.gameObject.AddComponent<JsonDataSerializer>();
}
}
return m_serializer;
}
}
/// <summary>
/// Reference to the SavedGameDataStorer in the SaveSystem's hierarchy.
/// SaveSystem will use it to store and retrieve saved game data.
/// </summary>
public static SavedGameDataStorer storer
{
get
{
if (m_storer == null)
{
m_storer = instance.GetComponent<SavedGameDataStorer>();
if (m_storer == null && !m_isQuitting)
{
Debug.Log("Save System: No SavedGameDataStorer found on " + instance.name + ". Adding PlayerPrefsSavedGameDataStorer.", instance);
m_storer = instance.gameObject.AddComponent<PlayerPrefsSavedGameDataStorer>();
}
}
return m_storer;
}
}
/// <summary>
/// Reference to the SceneTransitionManager in the SaveSystem's hierarchy, if present.
/// </summary>
public static SceneTransitionManager sceneTransitionManager
{
get
{
if (m_sceneTransitionManager == null)
{
m_sceneTransitionManager = instance.GetComponentInChildren<SceneTransitionManager>();
}
return m_sceneTransitionManager;
}
}
/// <summary>
/// Allow the use of negative slot numbers.
/// </summary>
public bool allowNegativeSlotNumbers
{
get { return m_allowNegativeSlotNumbers; }
set { m_allowNegativeSlotNumbers = value; }
}
/// <summary>
/// Scenes that have been loaded additively.
/// </summary>
public static List<string> addedScenes { get { return m_addedScenes; } }
/// <summary>
/// When changing scenes, automatically unload all additively-loaded scenes.
/// </summary>
public static bool autoUnloadAdditiveScenes
{
get { return m_autoUnloadAdditiveScenes; }
set { m_autoUnloadAdditiveScenes = value; }
}
/// <summary>
/// Current asynchronous scene load operation, or null if none. Loading scenes can use this
/// value to update a progress bar.
/// </summary>
public static AsyncOperation currentAsyncOperation
{
get { return m_currentAsyncOperation; }
set { m_currentAsyncOperation = value; }
}
/// <summary>
/// The saved game data recorded by the last call to SaveToSlot,
/// LoadScene, or RecordSavedGameData.
///
/// Note: This saved game data stays in memory until you clear it by using
/// RestartGame() or ResetGameState(), or by loading a saved game.
/// </summary>
public static SavedGameData currentSavedGameData
{
get { return m_savedGameData; }
set { m_savedGameData = value; }
}
/// <summary>
/// Where the player should spawn in the current scene.
/// </summary>
public static GameObject playerSpawnpoint
{
get { return m_playerSpawnpoint; }
set { m_playerSpawnpoint = value; }
}
/// <summary>
/// Build index of the current scene.
/// </summary>
public static int currentSceneIndex
{
get
{
if (m_currentSceneIndex == NoSceneIndex) m_currentSceneIndex = GetCurrentSceneIndex();
return m_currentSceneIndex;
}
}
public delegate string ValidateSceneNameDelegate(string sceneName, SceneValidationMode sceneValidationMode);
/// <summary>
/// Invoked before loading a scene by name. Should return the sceneName, or a different
/// scene if the sceneName isn't valid (e.g., was renamed or removed from build settings),
/// or a blank string to not load any scene.
/// </summary>
public static ValidateSceneNameDelegate validateNameScene = null;
public delegate void SceneLoadedDelegate(string sceneName, int sceneIndex);
/// <summary>
/// Invoked after a scene has been loaded.
/// </summary>
public static event SceneLoadedDelegate sceneLoaded = delegate { };
/// <summary>
/// Invoked when starting to save a game. If assigned, waits one frame before
/// starting the save to allow UIs to update.
/// </summary>
public static event System.Action saveStarted = delegate { };
/// <summary>
/// Invoked when finished saving a game.
/// </summary>
public static event System.Action saveEnded = delegate { };
/// <summary>
/// Invoked when starting to load a game. If assigned, waits one frame before
/// starting the load to allow UIs to update.
/// </summary>
public static event System.Action loadStarted = delegate { };
/// <summary>
/// Invoked when finished loading a game.
/// </summary>
public static event System.Action loadEnded = delegate { };
/// <summary>
/// Invoked after ApplyData() has been called on all savers.
/// </summary>
public static event System.Action saveDataApplied = delegate { };
private void Awake()
{
if (m_instance == null)
{
m_instance = this;
#if UNITY_EDITOR
if (Application.isPlaying)
{ // If GameObject is hidden in Scene view, DontDestroyOnLoad will report (harmless) error.
UnityEditor.SceneVisibilityManager.instance.Show(gameObject, true);
}
#endif
if (transform.parent != null) transform.SetParent(null);
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
private void OnApplicationQuit()
{
m_isQuitting = true;
BeforeSceneChange();
}
private void OnEnable()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
}
public void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
{
FinishedLoadingScene(scene.name, scene.buildIndex);
}
public static string GetCurrentSceneName()
{
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
}
public static int GetCurrentSceneIndex()
{
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex;
}
public static bool IsSceneInBuildSettings(string sceneName)
{
for (var n = 0; n < UnityEngine.SceneManagement.SceneManager.sceneCountInBuildSettings; ++n)
{
var scenePath = UnityEngine.SceneManagement.SceneUtility.GetScenePathByBuildIndex(n);
if (string.IsNullOrEmpty(scenePath)) continue;
if (string.Equals(System.IO.Path.GetFileNameWithoutExtension(scenePath), sceneName, System.StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
private static void SceneManagerOrAddressablesLoadScene(string sceneName)
{
if (IsSceneInBuildSettings(sceneName))
{
UnityEngine.SceneManagement.SceneManager.LoadScene(sceneName);
return;
}
#if USE_ADDRESSABLES
// If not in build settings, try loading an Addressable scene:
m_currentAsyncOperationHandle = UnityEngine.AddressableAssets.Addressables.LoadSceneAsync(sceneName);
#else
Debug.LogError("Can't load scene. Scene is not in build settings: " + sceneName);
#endif
}
private static void SceneManagerOrAddressablesLoadSceneAsync(string sceneName)
{
m_currentAsyncOperation = null;
if (IsSceneInBuildSettings(sceneName))
{
m_currentAsyncOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(sceneName);
return;
}
#if USE_ADDRESSABLES
// If not in build settings, try loading an Addressable scene:
m_currentAsyncOperationHandle = UnityEngine.AddressableAssets.Addressables.LoadSceneAsync(sceneName);
#else
Debug.LogError("Can't load scene. Scene is not in build settings: " + sceneName);
#endif
}
private static IEnumerator SceneManagerOrAddressablesLoadSceneAdditiveAsync(string sceneName)
{
if (IsSceneInBuildSettings(sceneName))
{
yield return UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(sceneName, UnityEngine.SceneManagement.LoadSceneMode.Additive);
}
else
{
#if USE_ADDRESSABLES
// If not in build settings, try loading an Addressable scene:
m_currentAsyncOperationHandle = UnityEngine.AddressableAssets.Addressables.LoadSceneAsync(sceneName, UnityEngine.SceneManagement.LoadSceneMode.Additive);
while (!m_currentAsyncOperation.isDone)
{
yield return null;
}
#else
Debug.LogError("Can't load additive scene. Scene is not in build settings: " + sceneName);
#endif
}
}
private static IEnumerator LoadSceneInternal(string sceneName, SceneValidationMode sceneValidationMode)
{
m_addedScenes.Clear();
if (sceneTransitionManager == null)
{
if (sceneName.StartsWith("index:"))
{
var index = SafeConvert.ToInt(sceneName.Substring("index:".Length));
UnityEngine.SceneManagement.SceneManager.LoadScene(index);
}
else
{
if (validateNameScene != null) sceneName = validateNameScene(sceneName, sceneValidationMode);
if (string.IsNullOrEmpty(sceneName))
{
if (debug) Debug.LogWarning("Scene '" + sceneName + "' is not a valid scene to load.");
yield break;
}
SceneManagerOrAddressablesLoadScene(sceneName);
}
yield break;
}
else
{
yield return instance.StartCoroutine(LoadSceneInternalTransitionCoroutine(sceneName, sceneValidationMode));
}
}
private static IEnumerator LoadSceneInternalTransitionCoroutine(string sceneName, SceneValidationMode sceneValidationMode)
{
m_addedScenes.Clear();
yield return instance.StartCoroutine(sceneTransitionManager.LeaveScene());
if (sceneName.StartsWith("index:"))
{
var index = SafeConvert.ToInt(sceneName.Substring("index:".Length));
m_currentAsyncOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(index);
}
else
{
if (validateNameScene != null) sceneName = validateNameScene(sceneName, sceneValidationMode);
if (string.IsNullOrEmpty(sceneName))
{
if (debug) Debug.LogWarning("Scene '" + sceneName + "' is not a valid scene to load.");
yield break;
}
SceneManagerOrAddressablesLoadSceneAsync(sceneName);
}
if (m_currentAsyncOperation != null)
{
while (m_currentAsyncOperation != null && !m_currentAsyncOperation.isDone)
{
sceneTransitionManager.OnLoading(m_currentAsyncOperation.progress);
yield return null;
}
}
#if USE_ADDRESSABLES
else
{
while (!m_currentAsyncOperationHandle.IsDone)
{
sceneTransitionManager.OnLoading(m_currentAsyncOperationHandle.PercentComplete);
yield return null;
}
}
#endif
sceneTransitionManager.OnLoading(1);
m_currentAsyncOperation = null;
instance.StartCoroutine(sceneTransitionManager.EnterScene());
}
public static IEnumerator LoadAdditiveSceneInternal(string sceneName, SceneValidationMode sceneValidationMode)
{
if (validateNameScene != null) sceneName = validateNameScene(sceneName, sceneValidationMode);
if (string.IsNullOrEmpty(sceneName)) yield break;
yield return SceneManagerOrAddressablesLoadSceneAdditiveAsync(sceneName);
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
if (!scene.IsValid()) yield break;
var rootGOs = scene.GetRootGameObjects();
for (int i = 0; i < rootGOs.Length; i++)
{
RecursivelyApplySavers(rootGOs[i].transform);
}
}
public static void UnloadAdditiveSceneInternal(string sceneName)
{
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
if (scene.IsValid())
{
var rootGOs = scene.GetRootGameObjects();
for (int i = 0; i < rootGOs.Length; i++)
{
var rootGO = rootGOs[i].transform;
RecursivelyRecordSavers(rootGO, scene.buildIndex);
RecursivelyInformBeforeSceneChange(rootGO);
}
}
UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(sceneName);
}
/// <summary>
/// Records the data of all saver components on the transform and its children.
/// </summary>
public static void RecursivelyRecordSavers(Transform t, int sceneIndex)
{
if (t == null) return;
var saver = t.GetComponent<Saver>();
if (saver != null) currentSavedGameData.SetData(saver.key, saver.saveAcrossSceneChanges ? -1 : sceneIndex, saver.RecordData());
foreach (Transform child in t)
{
RecursivelyRecordSavers(child, sceneIndex);
}
}
/// <summary>
/// Tells all saver components on the transform and its children to retrieve their states from the current saved game data.
/// </summary>
/// <param name="t"></param>
public static void RecursivelyApplySavers(Transform t)
{
if (t == null) return;
var saver = t.GetComponent<Saver>();
if (saver != null) saver.ApplyData(currentSavedGameData.GetData(saver.key));
foreach (Transform child in t)
{
RecursivelyApplySavers(child);
}
}
/// <summary>
/// Calls BeforeSceneChange on all saver components on the transform and its children.
/// Used when unloading an additive scene.
/// </summary>
/// <param name="t"></param>
public static void RecursivelyInformBeforeSceneChange(Transform t)
{
if (t == null) return;
var saver = t.GetComponent<Saver>();
if (saver != null) saver.OnBeforeSceneChange();
foreach (Transform child in t)
{
RecursivelyInformBeforeSceneChange(child);
}
}
/// <summary>
/// If slotNumber is negative and allowNegativeSlotNumbers is false,
/// choose an empty positive slot up to maxSlots. If none are empty,
/// return false;
/// </summary>
private static bool SanitizeSlotNumberForSave(int slotNumber, out int sanitizedSlotNumber)
{
if (slotNumber >= 0 || m_instance == null || m_instance.allowNegativeSlotNumbers)
{
sanitizedSlotNumber = slotNumber;
return true;
}
for (int i = 0; i <= maxSaveSlot; i++)
{
if (!HasSavedGameInSlot(i))
{
sanitizedSlotNumber = i;
return true;
}
}
sanitizedSlotNumber = 0;
return false;
}
/// <summary>
/// Saves a game into a slot using the storage provider on the
/// Save System GameObject.
/// </summary>
/// <param name="slotNumber">Slot in which to store saved game data.</param>
public void SaveGameToSlot(int slotNumber)
{
SaveToSlot(slotNumber);
}
/// <summary>
/// Loads a game from a slot using the storage provider on the
/// Save System GameObject.
/// </summary>
/// <param name="slotNumber"></param>
public void LoadGameFromSlot(int slotNumber)
{
LoadFromSlot(slotNumber);
}
/// <summary>
/// Loads a scene, optionally positioning the player at a
/// specified spawnpoint.
/// </summary>
/// <param name="sceneNameAndSpawnpoint">
/// A string containing the name of the scene to load, optionally
/// followed by "@spawnpoint" where "spawnpoint" is the name of
/// a GameObject in that scene. The player will be spawned at that
/// GameObject's position.
/// </param>
public void LoadSceneAtSpawnpoint(string sceneNameAndSpawnpoint)
{
LoadScene(sceneNameAndSpawnpoint);
}
/// <summary>
/// Returns true if there is a saved game in the specified slot.
/// </summary>
public static bool HasSavedGameInSlot(int slotNumber)
{
return storer.HasDataInSlot(slotNumber);
}
/// <summary>
/// Deletes the saved game in the specified slot.
/// </summary>
public static void DeleteSavedGameInSlot(int slotNumber)
{
storer.DeleteSavedGameData(slotNumber);
}
/// <summary>
/// Saves the current game to a slot.
/// </summary>
public static void SaveToSlot(int slotNumber)
{
instance.StartCoroutine(SaveToSlotCoroutine(slotNumber));
}
private static IEnumerator SaveToSlotCoroutine(int slotNumber)
{
if (!SanitizeSlotNumberForSave(slotNumber, out slotNumber))
{
Debug.LogError("Can't save game. Invalid save slot: " + slotNumber);
yield break;
}
saveStarted();
yield return null;
PlayerPrefs.SetInt(LastSavedGameSlotPlayerPrefsKey, slotNumber);
yield return storer.StoreSavedGameDataAsync(slotNumber, RecordSavedGameData());
saveEnded();
}
/// <summary>
/// Saves the current game to a slot synchronously and immediately.
/// </summary>
public static void SaveToSlotImmediate(int slotNumber)
{
if (!SanitizeSlotNumberForSave(slotNumber, out slotNumber))
{
Debug.LogError("Can't save game. Invalid save slot: " + slotNumber);
return;
}
saveStarted();
PlayerPrefs.SetInt(LastSavedGameSlotPlayerPrefsKey, slotNumber);
storer.StoreSavedGameData(slotNumber, RecordSavedGameData());
saveEnded();
}
/// <summary>
/// Loads a game from a slot.
/// </summary>
public static void LoadFromSlot(int slotNumber)
{
if (!HasSavedGameInSlot(slotNumber))
{
if (Debug.isDebugBuild) Debug.LogWarning("Save System: LoadFromSlot(" + slotNumber + ") but there is no saved game in this slot.");
return;
}
if (loadStarted.GetInvocationList().Length > 1)
{
instance.StartCoroutine(LoadFromSlotCoroutine(slotNumber));
}
else
{
LoadFromSlotNow(slotNumber);
}
}
private static IEnumerator LoadFromSlotCoroutine(int slotNumber)
{
loadStarted();
yield return null;
LoadFromSlotNow(slotNumber);
}
private static void NotifyLoadEndedWhenSceneLoaded(string sceneName, int sceneIndex)
{
sceneLoaded -= NotifyLoadEndedWhenSceneLoaded;
loadEnded();
}
private static void LoadFromSlotNow(int slotNumber)
{
sceneLoaded += NotifyLoadEndedWhenSceneLoaded;
LoadGame(storer.RetrieveSavedGameData(slotNumber));
}
public static void RegisterSaver(Saver saver)
{
if (saver == null || m_savers.Contains(saver)) return;
m_savers.Add(saver);
}
public static void UnregisterSaver(Saver saver)
{
m_savers.Remove(saver);
}
/// <summary>
/// Clears the SaveSystem's internal saved game data cache.
/// </summary>
public static void ClearSavedGameData()
{
m_savedGameData = new SavedGameData();
}
/// <summary>
/// Records the current scene's savers' data into the SaveSystem's
/// internal saved game data cache.
/// </summary>
/// <returns></returns>
public static SavedGameData RecordSavedGameData()
{
m_savedGameData.version = version;
m_savedGameData.sceneName = GetCurrentSceneName();
foreach (var saver in m_savers)
{
try
{
m_savedGameData.SetData(saver.key, GetSaverSceneIndex(saver), saver.RecordData());
}
catch (System.Exception e)
{
Debug.LogException(e);
}
}
return m_savedGameData;
}
private static int GetSaverSceneIndex(Saver saver)
{
return (saver == null || !saver.saveAcrossSceneChanges) ? currentSceneIndex : NoSceneIndex;
}
/// <summary>
/// Updates the SaveSystem's internal saved game data cache with data for a
/// specific saver.
/// </summary>
/// <param name="saver"></param>
/// <param name="data"></param>
public static void UpdateSaveData(Saver saver, string data)
{
m_savedGameData.SetData(saver.key, GetSaverSceneIndex(saver), data);
}
/// <summary>
/// Applies the saved game data to the savers in the current scene.
/// </summary>
/// <param name="savedGameData">Saved game data.</param>
public static void ApplySavedGameData(SavedGameData savedGameData)
{
if (savedGameData != null)
{
m_savedGameData = savedGameData;
if (m_savers.Count > 0)
{
m_tmpSavers.Clear();
m_tmpSavers.AddRange(m_savers); // Make a copy in case a saver ends up removing multiple savers.
for (int i = m_tmpSavers.Count - 1; i >= 0; i--) // A saver may remove itself from list during apply.
{
try
{
if (0 <= i && i < m_tmpSavers.Count)
{
var saver = m_tmpSavers[i];
if (saver != null) saver.ApplyData(savedGameData.GetData(saver.key));
}
}
catch (System.Exception e)
{
Debug.LogException(e);
}
}
}
}
if (framesToWaitBeforeSaveDataAppliedEvent == 0 || instance == null)
{
saveDataApplied();
}
else
{
instance.StartCoroutine(DelayedSaveDataAppliedCoroutine(framesToWaitBeforeSaveDataAppliedEvent));
framesToWaitBeforeSaveDataAppliedEvent = 0;
}
}
protected static IEnumerator DelayedSaveDataAppliedCoroutine(int frames)
{
for (int i = 0; i < frames; i++)
{
yield return null;
}
yield return CoroutineUtility.endOfFrame;
saveDataApplied();
}
/// <summary>
/// Applies the most recently recorded saved game data.
/// </summary>
public static void ApplySavedGameData()
{
ApplySavedGameData(m_savedGameData);
}
/// <summary>
/// If changing scenes manually, calls before changing scenes to inform components
/// that listen for OnDestroy messages that they're being destroyed because of the
/// scene change.
/// </summary>
public static void BeforeSceneChange()
{
// Notify savers:
var savers = new List<Saver>(m_savers);
for (int i = savers.Count - 1; i >= 0; i--)
{
var saver = savers[i];
if (saver == null) continue;
try
{
saver.OnBeforeSceneChange();
}
catch (System.Exception e)
{
Debug.LogException(e);
}
}
// Notify SceneNotifier:
try
{
SceneNotifier.NotifyWillUnloadScene(m_currentSceneIndex);
}
catch (System.Exception e)
{
Debug.LogException(e);
}
}
/// <summary>
/// Loads the scene recorded in the saved game data (if saveCurrentScene is true) and
/// applies the saved game data to it.
/// </summary>
/// <param name="savedGameData"></param>
public static void LoadGame(SavedGameData savedGameData)
{
if (savedGameData == null)
{
if (Debug.isDebugBuild) Debug.LogWarning("SaveSystem.LoadGame received null saved game data. Not loading.");
}
else if (saveCurrentScene)
{
instance.StartCoroutine(LoadSceneCoroutine(savedGameData, null, SceneValidationMode.LoadingSavedGame));
}
else
{
ApplySavedGameData(savedGameData);
}
}
/// <summary>
/// Loads a scene, optionally moving the player to a specified spawnpoint.
/// If the scene name starts with "index:" followed by an index number, this
/// method loads the scene by build index number.
/// </summary>
/// <param name="sceneNameAndSpawnpoint">Scene name, followed by an optional spawnpoint separated by '@'.</param>
public static void LoadScene(string sceneNameAndSpawnpoint)
{
if (string.IsNullOrEmpty(sceneNameAndSpawnpoint)) return;
string sceneName = sceneNameAndSpawnpoint;
string spawnpointName = string.Empty;
if (sceneNameAndSpawnpoint.Contains("@"))
{
var strings = sceneNameAndSpawnpoint.Split('@');
sceneName = strings[0];
spawnpointName = (strings.Length > 1) ? strings[1] : null;
}
var savedGameData = RecordSavedGameData();
savedGameData.sceneName = sceneName;
instance.StartCoroutine(LoadSceneCoroutine(savedGameData, spawnpointName, SceneValidationMode.LoadingScene));
}
private static IEnumerator LoadSceneCoroutine(SavedGameData savedGameData, string spawnpointName, SceneValidationMode sceneValidationMode)
{
if (savedGameData == null) yield break;
if (debug) Debug.Log("Save System: Loading scene " + savedGameData.sceneName +
(string.IsNullOrEmpty(spawnpointName) ? string.Empty : " [spawn at " + spawnpointName + "]"));
m_savedGameData = savedGameData;
BeforeSceneChange();
if (autoUnloadAdditiveScenes) UnloadAllAdditiveScenes();
yield return LoadSceneInternal(savedGameData.sceneName, sceneValidationMode);
ApplyDataImmediate();
// Allow other scripts to spin up scene first:
for (int i = 0; i < framesToWaitBeforeApplyData; i++)
{
yield return null;
}
yield return CoroutineUtility.endOfFrame;
m_playerSpawnpoint = !string.IsNullOrEmpty(spawnpointName) ? GameObject.Find(spawnpointName) : null;
if (!string.IsNullOrEmpty(spawnpointName) && m_playerSpawnpoint == null) Debug.LogWarning("Save System: Can't find spawnpoint '" + spawnpointName + "'. Is spelling and capitalization correct?");
ApplySavedGameData(savedGameData);
}
// Calls ApplyDataImmediate on all savers.
private static void ApplyDataImmediate()
{
if (m_savers.Count > 0)
{
m_tmpSavers.Clear();
m_tmpSavers.AddRange(m_savers); // Make a copy in case a saver ends up removing multiple savers.
for (int i = m_tmpSavers.Count - 1; i >= 0; i--) // A saver may remove itself from list during apply.
{
try
{
if (0 <= i && i < m_tmpSavers.Count)
{
var saver = m_tmpSavers[i];
if (saver != null) saver.ApplyDataImmediate();
}
}
catch (System.Exception e)
{
Debug.LogException(e);
}
}
}
}
private void FinishedLoadingScene(string sceneName, int sceneIndex)
{
m_currentSceneIndex = sceneIndex;
if (!m_isLoadingAdditiveScene)
{ // Don't delete other non-cross-scene data if loading additive scene:
m_savedGameData.DeleteObsoleteSaveData(sceneIndex);
}
m_isLoadingAdditiveScene = false;
sceneLoaded(sceneName, sceneIndex);
}
/// <summary>
/// Additively loads another scene.
/// </summary>
/// <param name="sceneName">Scene to additively load.</param>
public static void LoadAdditiveScene(string sceneName)
{
if (string.IsNullOrEmpty(sceneName) || m_addedScenes.Contains(sceneName)) return;
m_addedScenes.Add(sceneName);
instance.m_isLoadingAdditiveScene = true;
instance.StartCoroutine(LoadAdditiveSceneInternal(sceneName, SceneValidationMode.LoadingScene));
}
/// <summary>
/// Unloads a previously additively-loaded scene.
/// </summary>
/// <param name="sceneName">Scene to unload</param>
public static void UnloadAdditiveScene(string sceneName)
{
if (!m_addedScenes.Contains(sceneName)) return;
m_addedScenes.Remove(sceneName);
UnloadAdditiveSceneInternal(sceneName);
}
/// <summary>
/// Unloads all previously additively-loaded scenes.
/// </summary>
public static void UnloadAllAdditiveScenes()
{
for (int i = m_addedScenes.Count - 1; i >= 0; i--)
{
UnloadAdditiveScene(m_addedScenes[i]);
}
}
/// <summary>
/// Clears the SaveSystem's saved game data cache and loads a
/// starting scene. Same as ResetGameState except loads a starting scene.
/// </summary>
/// <param name="startingSceneName"></param>
public static void RestartGame(string startingSceneName)
{
ResetGameState();
instance.StartCoroutine(LoadSceneInternal(startingSceneName, SceneValidationMode.RestartingGame));
}
/// <summary>
/// Clears the SaveSystem's saved game data cache. Same as
/// RestartGame except it doesn't load a scene after resetting.
/// </summary>
/// <param name="startingSceneName"></param>
public static void ResetGameState()
{
ClearSavedGameData();
BeforeSceneChange();
SaversRestartGame();
}
/// <summary>
/// Calls OnRestartGame on all savers.
/// </summary>
public static void SaversRestartGame()
{
if (m_savers.Count <= 0) return;
foreach (var saver in m_savers.ToList()) // A saver may remove itself from list during restart.
{
try
{
if (saver != null) saver.OnRestartGame();
}
catch (System.Exception e)
{
Debug.LogException(e);
}
}
}
/// <summary>
/// Returns a serialized version of an object using whatever serializer is
/// assigned to the SaveSystem (JSON by default).
/// </summary>
public static string Serialize(object data)
{
return serializer.Serialize(data);
}
/// <summary>
/// Deserializes a previously-serialized string representation of an object
/// back into an object. Uses whatever serializer is assigned to the
/// SaveSystem (JSON by default).
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="s">The object's serialized data.</param>
/// <param name="data">Optional preallocated object to serialize data into.</param>
/// <returns>The deserialized object, or null if it couldn't be deserialized.</returns>
public static T Deserialize<T>(string s, T data = default(T))
{
return serializer.Deserialize<T>(s, data);
}
}
}