• unity save load系统 快速搭建


    我的最终目标是快读建立一个关卡数据自动读入储存功能:

    1. 每个关卡有自己的编号,如果没有自定义该关卡,则读取默认编号的初始布局,如果有自定义该关卡,则读取新定义的关卡。

    2.在游戏中如果对布局做出了更改,随时储存新的修改。

    3.save和load系统与玩法系统耦合度低,无需管理。

    小试牛刀-soundmanager

    先从一个简单的soundmanager开始学习。

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. namespace BBG
    5. {
    6. public class SaveManager : SingletonComponent<SaveManager>
    7. {
    8. #region Member Variables
    9. private List saveables;
    10. private JSONNode loadedSave;
    11. #endregion
    12. #region Properties
    13. ///
    14. /// Path to the save file on the device
    15. ///
    16. public string SaveFilePath { get { return Application.persistentDataPath + "/save.json"; } }
    17. ///
    18. /// List of registered saveables
    19. ///
    20. private List Saveables
    21. {
    22. get
    23. {
    24. if (saveables == null)
    25. {
    26. saveables = new List();
    27. }
    28. return saveables;
    29. }
    30. }
    31. #endregion
    32. #if UNITY_EDITOR
    33. [UnityEditor.MenuItem("Tools/Bizzy Bee Games/Delete Save Data")]
    34. public static void DeleteSaveData()
    35. {
    36. if (!System.IO.File.Exists(SaveManager.Instance.SaveFilePath))
    37. {
    38. UnityEditor.EditorUtility.DisplayDialog("Delete Save File", "There is no save file.", "Ok");
    39. return;
    40. }
    41. bool delete = UnityEditor.EditorUtility.DisplayDialog("Delete Save File", "Delete the save file located at " + SaveManager.Instance.SaveFilePath, "Yes", "No");
    42. if (delete)
    43. {
    44. System.IO.File.Delete(SaveManager.Instance.SaveFilePath);
    45. #if BBG_MT_IAP || BBG_MT_ADS
    46. System.IO.Directory.Delete(BBG.MobileTools.Utils.SaveFolderPath, true);
    47. #endif
    48. UnityEditor.EditorUtility.DisplayDialog("Delete Save File", "Save file has been deleted.", "Ok");
    49. }
    50. }
    51. #endif
    52. #region Unity Methods
    53. private void Start()
    54. {
    55. Debug.Log("Save file path: " + SaveFilePath);
    56. }
    57. private void OnDestroy()
    58. {
    59. Save();
    60. }
    61. private void OnApplicationPause(bool pause)
    62. {
    63. if (pause)
    64. {
    65. Save();
    66. }
    67. }
    68. #endregion
    69. #region Public Methods
    70. ///
    71. /// Registers a saveable to be saved
    72. ///
    73. public void Register(ISaveable saveable)
    74. {
    75. Saveables.Add(saveable);
    76. }
    77. ///
    78. /// Loads the save data for the given saveable
    79. ///
    80. public JSONNode LoadSave(ISaveable saveable)
    81. {
    82. return LoadSave(saveable.SaveId);
    83. }
    84. ///
    85. /// Loads the save data for the given save id
    86. ///
    87. public JSONNode LoadSave(string saveId)
    88. {
    89. // Check if the save file has been loaded and if not try and load it
    90. if (loadedSave == null && !LoadSave(out loadedSave))
    91. {
    92. return null;
    93. }
    94. // Check if the loaded save file has the given save id
    95. if (!loadedSave.AsObject.HasKey(saveId))
    96. {
    97. return null;
    98. }
    99. // Return the JSONNode for the save id
    100. return loadedSave[saveId];
    101. }
    102. #endregion
    103. #region Private Methods
    104. ///
    105. /// Saves all registered saveables to the save file
    106. ///
    107. private void Save()
    108. {
    109. Dictionary<string, object> saveJson = new Dictionary<string, object>();
    110. for (int i = 0; i < saveables.Count; i++)
    111. {
    112. saveJson.Add(saveables[i].SaveId, saveables[i].Save());
    113. }
    114. System.IO.File.WriteAllText(SaveFilePath, Utilities.ConvertToJsonString(saveJson));
    115. }
    116. ///
    117. /// Tries to load the save file
    118. ///
    119. private bool LoadSave(out JSONNode json)
    120. {
    121. json = null;
    122. if (!System.IO.File.Exists(SaveFilePath))
    123. {
    124. return false;
    125. }
    126. json = JSON.Parse(System.IO.File.ReadAllText(SaveFilePath));
    127. return json != null;
    128. }
    129. #endregion
    130. }
    131. }

    以上代码中的Register函数很重要,其他的需要储存数据的模块,比如soundmanager,就需要继承Isavable,并且在初始化时register自己给savemanager:

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. namespace BBG
    5. {
    6. public class SoundManager : SingletonComponent<SoundManager>, ISaveable
    7. {
    8. #region Classes
    9. [System.Serializable]
    10. private class SoundInfo
    11. {
    12. public string id = "";
    13. public AudioClip audioClip = null;
    14. public SoundType type = SoundType.SoundEffect;
    15. public bool playAndLoopOnStart = false;
    16. [Range(0, 1)] public float clipVolume = 1;
    17. }
    18. private class PlayingSound
    19. {
    20. public SoundInfo soundInfo = null;
    21. public AudioSource audioSource = null;
    22. }
    23. #endregion
    24. #region Enums
    25. public enum SoundType
    26. {
    27. SoundEffect,
    28. Music
    29. }
    30. #endregion
    31. #region Inspector Variables
    32. [SerializeField] private List soundInfos = null;
    33. #endregion
    34. #region Member Variables
    35. private List playingAudioSources;
    36. private List loopingAudioSources;
    37. public string SaveId { get { return "sound_manager"; } }
    38. #endregion
    39. #region Properties
    40. public bool IsMusicOn { get; private set; }
    41. public bool IsSoundEffectsOn { get; private set; }
    42. #endregion
    43. #region Unity Methods
    44. protected override void Awake()
    45. {
    46. base.Awake();
    47. SaveManager.Instance.Register(this);
    48. playingAudioSources = new List();
    49. loopingAudioSources = new List();
    50. if (!LoadSave())
    51. {
    52. IsMusicOn = true;
    53. IsSoundEffectsOn = true;
    54. }
    55. }
    56. private void Start()
    57. {
    58. for (int i = 0; i < soundInfos.Count; i++)
    59. {
    60. SoundInfo soundInfo = soundInfos[i];
    61. if (soundInfo.playAndLoopOnStart)
    62. {
    63. Play(soundInfo.id, true, 0);
    64. }
    65. }
    66. }
    67. private void Update()
    68. {
    69. for (int i = 0; i < playingAudioSources.Count; i++)
    70. {
    71. AudioSource audioSource = playingAudioSources[i].audioSource;
    72. // If the Audio Source is no longer playing then return it to the pool so it can be re-used
    73. if (!audioSource.isPlaying)
    74. {
    75. Destroy(audioSource.gameObject);
    76. playingAudioSources.RemoveAt(i);
    77. i--;
    78. }
    79. }
    80. }
    81. #endregion
    82. #region Public Methods
    83. ///
    84. /// Plays the sound with the give id
    85. ///
    86. public void Play(string id)
    87. {
    88. Play(id, false, 0);
    89. }
    90. ///
    91. /// Plays the sound with the give id, if loop is set to true then the sound will only stop if the Stop method is called
    92. ///
    93. public void Play(string id, bool loop, float playDelay)
    94. {
    95. SoundInfo soundInfo = GetSoundInfo(id);
    96. if (soundInfo == null)
    97. {
    98. Debug.LogError("[SoundManager] There is no Sound Info with the given id: " + id);
    99. return;
    100. }
    101. if ((soundInfo.type == SoundType.Music && !IsMusicOn) ||
    102. (soundInfo.type == SoundType.SoundEffect && !IsSoundEffectsOn))
    103. {
    104. return;
    105. }
    106. AudioSource audioSource = CreateAudioSource(id);
    107. audioSource.clip = soundInfo.audioClip;
    108. audioSource.loop = loop;
    109. audioSource.time = 0;
    110. audioSource.volume = soundInfo.clipVolume;
    111. if (playDelay > 0)
    112. {
    113. audioSource.PlayDelayed(playDelay);
    114. }
    115. else
    116. {
    117. audioSource.Play();
    118. }
    119. PlayingSound playingSound = new PlayingSound();
    120. playingSound.soundInfo = soundInfo;
    121. playingSound.audioSource = audioSource;
    122. if (loop)
    123. {
    124. loopingAudioSources.Add(playingSound);
    125. }
    126. else
    127. {
    128. playingAudioSources.Add(playingSound);
    129. }
    130. }
    131. ///
    132. /// Stops all playing sounds with the given id
    133. ///
    134. public void Stop(string id)
    135. {
    136. StopAllSounds(id, playingAudioSources);
    137. StopAllSounds(id, loopingAudioSources);
    138. }
    139. ///
    140. /// Stops all playing sounds with the given type
    141. ///
    142. public void Stop(SoundType type)
    143. {
    144. StopAllSounds(type, playingAudioSources);
    145. StopAllSounds(type, loopingAudioSources);
    146. }
    147. ///
    148. /// Sets the SoundType on/off
    149. ///
    150. public void SetSoundTypeOnOff(SoundType type, bool isOn)
    151. {
    152. switch (type)
    153. {
    154. case SoundType.SoundEffect:
    155. if (isOn == IsSoundEffectsOn)
    156. {
    157. return;
    158. }
    159. IsSoundEffectsOn = isOn;
    160. break;
    161. case SoundType.Music:
    162. if (isOn == IsMusicOn)
    163. {
    164. return;
    165. }
    166. IsMusicOn = isOn;
    167. break;
    168. }
    169. // If it was turned off then stop all sounds that are currently playing
    170. if (!isOn)
    171. {
    172. Stop(type);
    173. }
    174. // Else it was turned on so play any sounds that have playAndLoopOnStart set to true
    175. else
    176. {
    177. PlayAtStart(type);
    178. }
    179. }
    180. #endregion
    181. #region Private Methods
    182. ///
    183. /// Plays all sounds that are set to play on start and loop and are of the given type
    184. ///
    185. private void PlayAtStart(SoundType type)
    186. {
    187. for (int i = 0; i < soundInfos.Count; i++)
    188. {
    189. SoundInfo soundInfo = soundInfos[i];
    190. if (soundInfo.type == type && soundInfo.playAndLoopOnStart)
    191. {
    192. Play(soundInfo.id, true, 0);
    193. }
    194. }
    195. }
    196. ///
    197. /// Stops all sounds with the given id
    198. ///
    199. private void StopAllSounds(string id, List playingSounds)
    200. {
    201. for (int i = 0; i < playingSounds.Count; i++)
    202. {
    203. PlayingSound playingSound = playingSounds[i];
    204. if (id == playingSound.soundInfo.id)
    205. {
    206. playingSound.audioSource.Stop();
    207. Destroy(playingSound.audioSource.gameObject);
    208. playingSounds.RemoveAt(i);
    209. i--;
    210. }
    211. }
    212. }
    213. ///
    214. /// Stops all sounds with the given type
    215. ///
    216. private void StopAllSounds(SoundType type, List playingSounds)
    217. {
    218. for (int i = 0; i < playingSounds.Count; i++)
    219. {
    220. PlayingSound playingSound = playingSounds[i];
    221. if (type == playingSound.soundInfo.type)
    222. {
    223. playingSound.audioSource.Stop();
    224. Destroy(playingSound.audioSource.gameObject);
    225. playingSounds.RemoveAt(i);
    226. i--;
    227. }
    228. }
    229. }
    230. private SoundInfo GetSoundInfo(string id)
    231. {
    232. for (int i = 0; i < soundInfos.Count; i++)
    233. {
    234. if (id == soundInfos[i].id)
    235. {
    236. return soundInfos[i];
    237. }
    238. }
    239. return null;
    240. }
    241. private AudioSource CreateAudioSource(string id)
    242. {
    243. GameObject obj = new GameObject("sound_" + id);
    244. obj.transform.SetParent(transform);
    245. return obj.AddComponent();;
    246. }
    247. #endregion
    248. #region Save Methods
    249. public Dictionary<string, object> Save()
    250. {
    251. Dictionary<string, object> json = new Dictionary<string, object>();
    252. json["is_music_on"] = IsMusicOn;
    253. json["is_sound_effects_on"] = IsSoundEffectsOn;
    254. return json;
    255. }
    256. public bool LoadSave()
    257. {
    258. JSONNode json = SaveManager.Instance.LoadSave(this);
    259. if (json == null)
    260. {
    261. return false;
    262. }
    263. IsMusicOn = json["is_music_on"].AsBool;
    264. IsSoundEffectsOn = json["is_sound_effects_on"].AsBool;
    265. return true;
    266. }
    267. #endregion
    268. }
    269. }

    如上所述的soundmanager,里面有两个内容是告知savemanager如何自动储存信息的

    1. public string SaveId { get { return "sound_manager"; } }
    2. public Dictionary<string, object> Save()
    3. {
    4. Dictionary<string, object> json = new Dictionary<string, object>();
    5. json["is_music_on"] = IsMusicOn;
    6. json["is_sound_effects_on"] = IsSoundEffectsOn;
    7. return json;
    8. }

    另外,观察soundmanager可知,它在初始化时,去做了一次loadsave函数,也就是去找savemanager要数据,如果要到了,怎样设置,如果没有要到,怎样设置。

    1. public bool LoadSave()
    2. {
    3. JSONNode json = SaveManager.Instance.LoadSave(this);
    4. if (json == null)
    5. {
    6. return false;
    7. }
    8. IsMusicOn = json["is_music_on"].AsBool;
    9. IsSoundEffectsOn = json["is_sound_effects_on"].AsBool;
    10. return true;
    11. }

    实战—关卡储存管理

    先看一下这个gamemanager中与储存相关的代码

    1. public Dictionary<string, object> Save()
    2. {
    3. Dictionary<string, object> json = new Dictionary<string, object>();
    4. json["num_stars_earned"] = SaveNumStarsEarned();
    5. json["last_completed"] = SaveLastCompleteLevels();
    6. json["level_statuses"] = SaveLevelStatuses();
    7. json["level_save_datas"] = SaveLevelDatas();
    8. json["star_amount"] = StarAmount;
    9. json["hint_amount"] = HintAmount;
    10. json["num_levels_till_ad"] = NumLevelsTillAd;
    11. return json;
    12. }
    13. private List<object> SaveNumStarsEarned()
    14. {
    15. List<object> json = new List<object>();
    16. foreach (KeyValuePair<string, int> pair in packNumStarsEarned)
    17. {
    18. Dictionary<string, object> packJson = new Dictionary<string, object>();
    19. packJson["pack_id"] = pair.Key;
    20. packJson["num_stars_earned"] = pair.Value;
    21. json.Add(packJson);
    22. }
    23. return json;
    24. }
    25. private List<object> SaveLastCompleteLevels()
    26. {
    27. List<object> json = new List<object>();
    28. foreach (KeyValuePair<string, int> pair in packLastCompletedLevel)
    29. {
    30. Dictionary<string, object> packJson = new Dictionary<string, object>();
    31. packJson["pack_id"] = pair.Key;
    32. packJson["last_completed_level"] = pair.Value;
    33. json.Add(packJson);
    34. }
    35. return json;
    36. }
    37. private List<object> SaveLevelStatuses()
    38. {
    39. List<object> json = new List<object>();
    40. foreach (KeyValuePair<string, Dictionary<int, int>> pair in packLevelStatuses)
    41. {
    42. Dictionary<string, object> packJson = new Dictionary<string, object>();
    43. packJson["pack_id"] = pair.Key;
    44. string levelStr = "";
    45. foreach (KeyValuePair<int, int> levelPair in pair.Value)
    46. {
    47. if (!string.IsNullOrEmpty(levelStr)) levelStr += "_";
    48. levelStr += levelPair.Key + "_" + levelPair.Value;
    49. }
    50. packJson["level_statuses"] = levelStr;
    51. json.Add(packJson);
    52. }
    53. return json;
    54. }
    55. private List<object> SaveLevelDatas()
    56. {
    57. List<object> savedLevelDatas = new List<object>();
    58. foreach (KeyValuePair<string, LevelSaveData> pair in levelSaveDatas)
    59. {
    60. Dictionary<string, object> levelSaveDataJson = pair.Value.Save();
    61. levelSaveDataJson["id"] = pair.Key;
    62. savedLevelDatas.Add(levelSaveDataJson);
    63. }
    64. return savedLevelDatas;
    65. }
    66. private bool LoadSave()
    67. {
    68. JSONNode json = SaveManager.Instance.LoadSave(this);
    69. if (json == null)
    70. {
    71. return false;
    72. }
    73. LoadNumStarsEarned(json["num_stars_earned"].AsArray);
    74. LoadLastCompleteLevels(json["last_completed"].AsArray);
    75. LoadLevelStatuses(json["level_statuses"].AsArray);
    76. LoadLevelSaveDatas(json["level_save_datas"].AsArray);
    77. StarAmount = json["star_amount"].AsInt;
    78. HintAmount = json["hint_amount"].AsInt;
    79. NumLevelsTillAd = json["num_levels_till_ad"].AsInt;
    80. return true;
    81. }
    82. private void LoadNumStarsEarned(JSONArray json)
    83. {
    84. for (int i = 0; i < json.Count; i++)
    85. {
    86. JSONNode childJson = json[i];
    87. string packId = childJson["pack_id"].Value;
    88. int numStarsEarned = childJson["num_stars_earned"].AsInt;
    89. packNumStarsEarned.Add(packId, numStarsEarned);
    90. }
    91. }
    92. private void LoadLastCompleteLevels(JSONArray json)
    93. {
    94. for (int i = 0; i < json.Count; i++)
    95. {
    96. JSONNode childJson = json[i];
    97. string packId = childJson["pack_id"].Value;
    98. int lastCompletedLevel = childJson["last_completed_level"].AsInt;
    99. packLastCompletedLevel.Add(packId, lastCompletedLevel);
    100. }
    101. }
    102. private void LoadLevelStatuses(JSONArray json)
    103. {
    104. for (int i = 0; i < json.Count; i++)
    105. {
    106. JSONNode childJson = json[i];
    107. string packId = childJson["pack_id"].Value;
    108. string[] levelStatusStrs = childJson["level_statuses"].Value.Split('_');
    109. Dictionary<int, int> levelStatuses = new Dictionary<int, int>();
    110. for (int j = 0; j < levelStatusStrs.Length; j += 2)
    111. {
    112. int levelIndex = System.Convert.ToInt32(levelStatusStrs[j]);
    113. int status = System.Convert.ToInt32(levelStatusStrs[j + 1]);
    114. levelStatuses.Add(levelIndex, status);
    115. }
    116. packLevelStatuses.Add(packId, levelStatuses);
    117. }
    118. }
    119. ///
    120. /// Loads the game from the saved json file
    121. ///
    122. private void LoadLevelSaveDatas(JSONArray savedLevelDatasJson)
    123. {
    124. // Load all the placed line segments for levels that have progress
    125. for (int i = 0; i < savedLevelDatasJson.Count; i++)
    126. {
    127. JSONNode savedLevelDataJson = savedLevelDatasJson[i];
    128. JSONArray savedPlacedLineSegments = savedLevelDataJson["placed_line_segments"].AsArray;
    129. JSONArray savedHints = savedLevelDataJson["hints"].AsArray;
    130. List> placedLineSegments = new List>();
    131. for (int j = 0; j < savedPlacedLineSegments.Count; j++)
    132. {
    133. placedLineSegments.Add(new List());
    134. for (int k = 0; k < savedPlacedLineSegments[j].Count; k += 2)
    135. {
    136. placedLineSegments[j].Add(new CellPos(savedPlacedLineSegments[j][k].AsInt, savedPlacedLineSegments[j][k + 1].AsInt));
    137. }
    138. }
    139. List<int> hintLineIndices = new List<int>();
    140. for (int j = 0; j < savedHints.Count; j++)
    141. {
    142. hintLineIndices.Add(savedHints[j].AsInt);
    143. }
    144. string levelId = savedLevelDataJson["id"].Value;
    145. int numMoves = savedLevelDataJson["num_moves"].AsInt;
    146. LevelSaveData levelSaveData = new LevelSaveData();
    147. levelSaveData.placedLineSegments = placedLineSegments;
    148. levelSaveData.numMoves = numMoves;
    149. levelSaveData.hintLineIndices = hintLineIndices;
    150. levelSaveDatas.Add(levelId, levelSaveData);
    151. }
    152. }
    153. #endregion

    我们发现,因为数据较为复杂,无论是load还是save,都针对不同数据有自己的辅助函数。

    gamemanager中,有一个startlevel,它需要一个packinfo(总关卡信息,可暂时忽略),以及一个leveldata.

    这里拿到的leveldata,是制作者本身就默认写好的值,

    如果这个leveldata的id,已经存在于levelsavedata的字典中,就说明这个leveldata经过了修改,因此要读取的是新的levelsavedata中的配置数据。

    如果这个leveldata的id没有存在于levelsavedata的字典中,就说明这次是第一次打开这个level,那么需要新建一个savedata:

    下面的代码记录了这个功能。

    1. ///
    2. /// Starts the level.
    3. ///
    4. public void StartLevel(PackInfo packInfo, LevelData levelData)
    5. {
    6. ActivePackInfo = packInfo;
    7. ActiveLevelData = levelData;
    8. // Check if the lvel has not been started and if there is loaded save data for it
    9. if (!levelSaveDatas.ContainsKey(levelData.Id))
    10. {
    11. levelSaveDatas[levelData.Id] = new LevelSaveData();
    12. }
    13. gameGrid.SetupLevel(levelData, levelSaveDatas[levelData.Id]);
    14. UpdateHintAmountText();
    15. UpdateLevelButtons();
    16. GameEventManager.Instance.SendEvent(GameEventManager.EventId_LevelStarted);
    17. ScreenManager.Instance.Show("game");
    18. // Check if it's time to show an interstitial ad
    19. if (NumLevelsTillAd <= 0)
    20. {
    21. NumLevelsTillAd = numLevelsBetweenAds;
    22. #if BBG_MT_ADS
    23. BBG.MobileTools.MobileAdsManager.Instance.ShowInterstitialAd();
    24. #endif
    25. }
    26. }

    其他的功能基本上和soundmanager一样:

    比如,在初始化时,注册自己,并试图loadsave.

    比如,在游戏中断时,进行保存

    1. protected override void Awake()
    2. {
    3. base.Awake();
    4. GameEventManager.Instance.RegisterEventHandler(GameEventManager.EventId_ActiveLevelCompleted, OnActiveLevelComplete);
    5. SaveManager.Instance.Register(this);
    6. packNumStarsEarned = new Dictionary<string, int>();
    7. packLastCompletedLevel = new Dictionary<string, int>();
    8. packLevelStatuses = new Dictionary<string, Dictionary<int, int>>();
    9. levelSaveDatas = new Dictionary<string, LevelSaveData>();
    10. if (!LoadSave())
    11. {
    12. HintAmount = startingHints;
    13. NumLevelsTillAd = numLevelsBetweenAds;
    14. }
    15. gameGrid.Initialize();
    16. if (startingStars > 0)
    17. {
    18. StarAmount = startingStars;
    19. }
    20. }
    21. private void OnDestroy()
    22. {
    23. Save();
    24. }
    25. private void OnApplicationPause(bool pause)
    26. {
    27. if (pause)
    28. {
    29. Save();
    30. }
    31. }

    至此,重点结束。

    然后,其他功能的脚本,可以通过获得currentlevelsavedata的方式去修改其数据,方便在关闭界面时进行数据更新。

    1. ///
    2. /// Sets the numMoves and updates the Text UI
    3. ///
    4. private void SetNumMoves(int amount)
    5. {
    6. currentLevelSaveData.numMoves = amount;
    7. moveAmountText.text = currentLevelSaveData.numMoves.ToString();
    8. }

    围绕这个功能,还可以方便设计undo/redo功能

  • 相关阅读:
    gin 统一响应结果
    Linux基础——ELK Stack
    2022算能生态合作伙伴大会,英码科技应邀出席共同探讨生态合作和发展问题
    用Git上传项目gitLab(简单笔记)
    C++——异常
    visionTransformer window平台下报错
    金仓数据库KStudio使用手册(6. PLSQL调试)
    Hadoop3:MapReduce中的Reduce Join和Map Join
    Shell 脚本特殊变量列表
    Python+requests编写的自动化测试项目
  • 原文地址:https://blog.csdn.net/killian0213/article/details/133988943