using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using UnityEngine; using UnityEngine.SceneManagement; namespace MoreMountains.Tools { /// /// Add this component to a scene and it'll let you save and load the state of objects that implement the IMMPersistent interface /// You can create your own classes that implement this interface, or use the MMPersistent class that comes with this package /// It will save their transform data (position, rotation, scale) and their active state /// Triggering save and load is done via events, and the manager also emits events every time data is loaded or saved /// public class MMPersistencyManager : MMPersistentSingleton, MMEventListener { [Header("Persistency")] /// A persistency ID used to identify the data associated to this manager. /// Usually you'll want to leave this to its default value. [Tooltip("A persistency ID used to identify the data associated to this manager. Usually you'll want to leave this to its default value.")] public string PersistencyID = "MMPersistency"; [Header("Events")] /// whether or not this manager should listen for save events. If you set this to false, you'll have to call SaveToMemory or SaveFromMemoryToFile manually [Tooltip("whether or not this manager should listen for save events. If you set this to false, you'll have to call SaveToMemory or SaveFromMemoryToFile manually")] public bool ListenForSaveEvents = true; /// whether or not this manager should listen for load events. If you set this to false, you'll have to call LoadFromMemory or LoadFromFileToMemory manually [Tooltip("whether or not this manager should listen for load events. If you set this to false, you'll have to call LoadFromMemory or LoadFromFileToMemory manually")] public bool ListenForLoadEvents = true; /// whether or not this manager should listen for save to memory events. If you set this to false, you'll have to call SaveToMemory manually [Tooltip("whether or not this manager should listen for save to memory events. If you set this to false, you'll have to call SaveToMemory manually")] public bool ListenForSaveToMemoryEvents = true; /// whether or not this manager should listen for load from memory events. If you set this to false, you'll have to call LoadFromMemory manually [Tooltip("whether or not this manager should listen for load from memory events. If you set this to false, you'll have to call LoadFromMemory manually")] public bool ListenForLoadFromMemoryEvents = true; /// whether or not this manager should listen for save to file events. If you set this to false, you'll have to call SaveFromMemoryToFile manually [Tooltip("whether or not this manager should listen for save to file events. If you set this to false, you'll have to call SaveFromMemoryToFile manually")] public bool ListenForSaveToFileEvents = true; /// whether or not this manager should listen for load from file events. If you set this to false, you'll have to call LoadFromFileToMemory manually [Tooltip("whether or not this manager should listen for load from file events. If you set this to false, you'll have to call LoadFromFileToMemory manually")] public bool ListenForLoadFromFileEvents = true; /// whether or not this manager should save data to file on save events [Tooltip("whether or not this manager should save data to file on save events")] public bool SaveToFileOnSaveEvents = true; /// whether or not this manager should load data from file on load events [Tooltip("whether or not this manager should load data from file on load events")] public bool LoadFromFileOnLoadEvents = true; /// the debug buttons below are only meant to be used at runtime [Header("Debug Buttons (Only at Runtime)")] [MMInspectorButton("SaveToMemory")] public bool SaveToMemoryButton; [MMInspectorButton("LoadFromMemory")] public bool LoadFromMemoryButton; [MMInspectorButton("SaveFromMemoryToFile")] public bool SaveToFileButton; [MMInspectorButton("LoadFromFileToMemory")] public bool LoadFromFileButton; [MMInspectorButton("DeletePersistencyFile")] public bool DeletePersistencyFileButton; public DictionaryStringSceneData SceneDatas; public static string _resourceItemPath = "Persistency/"; public static string _saveFolderName = "MMTools/"; public static string _saveFileExtension = ".persistency"; protected string _currentSceneName; #region INITIALIZATION /// /// On Awake we initialize our dictionary /// protected override void Awake() { base.Awake(); SceneDatas = new DictionaryStringSceneData(); } #endregion #region SAVE_AND_LOAD /// /// Saves data from objects that need saving to memory /// public virtual void SaveToMemory() { ComputeCurrentSceneName(); SceneDatas.Remove(_currentSceneName); MMPersistencySceneData sceneData = new MMPersistencySceneData(); sceneData.ObjectDatas = new DictionaryStringString(); IMMPersistent[] persistents = FindAllPersistentObjects(); foreach (IMMPersistent persistent in persistents) { if (persistent.ShouldBeSaved()) { sceneData.ObjectDatas.Add(persistent.GetGuid(), persistent.OnSave()); } } SceneDatas.Add(_currentSceneName, sceneData); MMPersistencyEvent.Trigger(MMPersistencyEventType.DataSavedToMemory, PersistencyID); } /// /// Loads data from memory and applies it to all objects that need it /// public virtual void LoadFromMemory() { ComputeCurrentSceneName(); if (!SceneDatas.TryGetValue(_currentSceneName, out MMPersistencySceneData sceneData)) { return; } if (sceneData.ObjectDatas == null) { return; } IMMPersistent[] persistents = FindAllPersistentObjects(); foreach (IMMPersistent persistent in persistents) { if (sceneData.ObjectDatas.TryGetValue(persistent.GetGuid(), out string data)) { persistent.OnLoad(sceneData.ObjectDatas[persistent.GetGuid()]); } } MMPersistencyEvent.Trigger(MMPersistencyEventType.DataLoadedFromMemory, PersistencyID); } /// /// Saves data from memory to a file /// public virtual void SaveFromMemoryToFile() { MMPersistencyManagerData saveData = new MMPersistencyManagerData(); saveData.PersistencyID = PersistencyID; saveData.SaveDate = DateTime.Now.ToString(); saveData.SceneDatas = SceneDatas; MMSaveLoadManager.Save(saveData, DetermineSaveName(), _saveFolderName); MMPersistencyEvent.Trigger(MMPersistencyEventType.DataSavedFromMemoryToFile, PersistencyID); } /// /// Loads data from file and stores it in memory /// public virtual void LoadFromFileToMemory() { MMPersistencyManagerData saveData = (MMPersistencyManagerData)MMSaveLoadManager.Load(typeof(MMPersistencyManagerData), DetermineSaveName(), _saveFolderName); if ((saveData != null) && (saveData.SceneDatas != null)) { SceneDatas = new DictionaryStringSceneData(); SceneDatas = saveData.SceneDatas; } MMPersistencyEvent.Trigger(MMPersistencyEventType.DataLoadedFromFileToMemory, PersistencyID); } /// /// On Save, we save to memory and to file if needed /// public virtual void Save() { SaveToMemory(); if (SaveToFileOnSaveEvents) { SaveFromMemoryToFile(); } } /// /// On Load, we load from memory and from file if needed /// public virtual void Load() { if (LoadFromFileOnLoadEvents) { LoadFromFileToMemory(); } LoadFromMemory(); } #endregion #region RESET /// /// Deletes all persistency data for the specified scene /// /// public virtual void DeletePersistencyMemoryForScene(string sceneName) { if (!SceneDatas.TryGetValue(_currentSceneName, out MMPersistencySceneData sceneData)) { return; } SceneDatas.Remove(sceneName); } /// /// Deletes persistency data from memory and on file for this persistency manager /// public virtual void ResetPersistency() { DeletePersistencyMemory(); DeletePersistencyFile(); } /// /// Deletes all persistency data stored in this persistency manager's memory /// public virtual void DeletePersistencyMemory() { SceneDatas = new DictionaryStringSceneData(); } /// /// Deletes the save file for this persistency manager /// public virtual void DeletePersistencyFile() { MMSaveLoadManager.DeleteSave(DetermineSaveName(), _saveFolderName); Debug.LogFormat("Persistency save file deleted"); } #endregion #region HELPERS /// /// Finds all objects in the scene that implement IMMPersistent and may need saving /// /// protected virtual IMMPersistent[] FindAllPersistentObjects() { return FindObjectsOfType(true).OfType().ToArray(); } /// /// Grabs the current scene's name and stores it /// protected virtual void ComputeCurrentSceneName() { _currentSceneName = SceneManager.GetActiveScene().name; } /// /// Determines the name of the file to write to store persistency data /// /// protected virtual string DetermineSaveName() { return gameObject.name + "_" + PersistencyID + _saveFileExtension; } #endregion #region EVENTS /// /// When we get a MMEvent, we filter on its name and invoke the appropriate methods if needed /// /// public virtual void OnMMEvent(MMGameEvent gameEvent) { if ((gameEvent.EventName == "Save") && ListenForSaveEvents) { Save(); } if ((gameEvent.EventName == "Load") && ListenForLoadEvents) { Load(); } if ((gameEvent.EventName == "SaveToMemory") && ListenForSaveToMemoryEvents) { SaveToMemory(); } if ((gameEvent.EventName == "LoadFromMemory") && ListenForLoadFromMemoryEvents) { LoadFromMemory(); } if ((gameEvent.EventName == "SaveToFile") && ListenForSaveToFileEvents) { SaveFromMemoryToFile(); } if ((gameEvent.EventName == "LoadFromFile") && ListenForLoadFromFileEvents) { LoadFromFileToMemory(); } } /// /// On enable, we start listening for MMGameEvents /// protected virtual void OnEnable() { this.MMEventStartListening(); } /// /// On enable, we stop listening for MMGameEvents /// protected virtual void OnDisable() { this.MMEventStopListening(); } #endregion } }