# 1. 创建一个基类用于存储数据
GameData.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[System.Serializable] public class GameData { public int souls; public string saveName; public string saveTime; public bool encryptData; public SerializableDictionary<string, int> inventory; public SerializableDictionary<string, int> equipment; public SerializableDictionary<string, bool> EXPTree;
public GameData() { this.souls = 0; inventory = new SerializableDictionary<string, int>(); equipment = new SerializableDictionary<string, int>(); EXPTree = new SerializableDictionary<string, bool>(); this.encryptData = true; this.saveTime = System.DateTime.Now.ToString("yyyyMMdd_HHmmss"); this.saveName = ""; Debug.Log("GameData Created!"); } }
|
由于字典类型无法序列化,所以创建一个类用于序列化字典类型数据
SerializableDictionary.cs1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| using System.Collections; using System.Collections.Generic; using UnityEngine;
[System.Serializable] public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver { [SerializeField] private List<TKey> keys = new List<TKey>(); [SerializeField] private List<TValue> values = new List<TValue>();
public void OnAfterDeserialize() { this.Clear(); if (keys.Count != values.Count) { Debug.LogError("keys and values count not equal"); } for (int i = 0; i < keys.Count; i++) { this.Add(keys[i], values[i]); } }
public void OnBeforeSerialize() { keys.Clear(); values.Clear(); foreach (KeyValuePair<TKey, TValue> pair in this) { keys.Add(pair.Key); values.Add(pair.Value); } } }
|
# 2. 创建一个管理类
我们需要集成一个管理类用于管理所有需要存档的数据
row1 2 3 4 5 6 7 8 9 10 11
| private void Awake() { if (instance != null) Destroy(gameObject); else { instance = this; DontDestroyOnLoad(gameObject); } SceneManager.sceneLoaded += OnSceneLoaded; }
|
由于我们在开始界面进入存档时若销毁此管理类,将会导致 gameData 丢失,所以需要将此类注册为不被销毁的类用于管理存档。
row1 2 3 4 5 6
| void Start() { saveManagers = GetSaveManagers(); saveFolder = Application.persistentDataPath + "/Saves"; }
|
将所有具有 save 和 load 方法的类统一管理
row1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public void LoadGame(GameData _gameData) { isSaveRequired = true; if (!Directory.Exists(saveFolder)) { Directory.CreateDirectory(saveFolder); }
if (_gameData == null || string.IsNullOrEmpty(_gameData.saveName)) { this.gameData = new GameData(); gameData.saveName = "Save_" + System.DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".json"; gameData.encryptData = encryptData; }
fileDataHandler = new FileDataHandler(Application.persistentDataPath + "/Saves", gameData.saveName, gameData.encryptData); gameData = fileDataHandler.Load(gameData);
foreach (ISaveManager saveManager in saveManagers) { saveManager.LoadData(gameData); } }
|
Load 的具体实现在另一个 FileDataHandler 中实现,这边后面再说
初始化存档并将数据注入各个具体存档实现的地方
row1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void SaveGame() { if (!isSaveRequired) { return; }
foreach (ISaveManager saveManager in saveManagers) { saveManager.SaveData(ref gameData); }
gameData.saveTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); fileDataHandler.Save(gameData); }
private void OnApplicationQuit() { SaveGame(); }
|
然后同理,在游戏退出时将所有存档实现类的数据都通过方法类中的 save 注入 JSON 文档,并存储在本地。
row1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| private List<ISaveManager> GetSaveManagers() { IEnumerable<ISaveManager> saveManagers = FindObjectsOfType<MonoBehaviour>().OfType<ISaveManager>(); return new List<ISaveManager>(saveManagers); }
public List<GameData> GetSaveList() { List<GameData> saveList = new List<GameData>(); if (string.IsNullOrEmpty(saveFolder)) { saveFolder = Application.persistentDataPath + "/Saves"; }
string[] saveFiles = Directory.GetFiles(saveFolder, "*.json"); foreach (string filePath in saveFiles) { string fileName = Path.GetFileName(filePath); FileDataHandler _fileDataHandler = new FileDataHandler(saveFolder, fileName, gameData.encryptData); saveList.Add(_fileDataHandler.Load()); }
return saveList; }
|
由于在开始界面需要获取本地的所有存档,所以用此方法来返回所有的存档列表并供以操作。
row1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void UseOnToggle() { saveManagers = GetSaveManagers(); saveFolder = Application.persistentDataPath + "/Saves"; LoadGame(gameData); }
private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (scene.name == "StartMenu") { return; } UseOnToggle(); }
|
最后添加提供外部载入数据的方法,在开始界面点击存档后加载存档。
# 3.load 和 save 方法类实现
row1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.IO;
public class FileDataHandler { private string dataDirPath; private string dataFileName; private bool encryptData = false; private string codeWord = "1234";
public FileDataHandler(string _dataDirPath, string _dataFileName, bool _encryptData = false, string _codeWord = "1234") { if (string.IsNullOrEmpty(_dataDirPath)) { dataDirPath = Application.persistentDataPath + "/Saves"; } else { dataDirPath = _dataDirPath; } dataFileName = _dataFileName; encryptData = _encryptData; codeWord = _codeWord; }
public void Save(GameData _data) { string fullPath = Path.Combine(dataDirPath, dataFileName); try { Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); string dataToStore = JsonUtility.ToJson(_data); Debug.Log("Save data: " + dataToStore);
if (encryptData) { dataToStore = EncryDecrypt(dataToStore); }
using (FileStream stream = new FileStream(fullPath, FileMode.Create)) { using (StreamWriter writer = new StreamWriter(stream)) { writer.Write(dataToStore); } } } catch (Exception e) { Debug.LogError("Error in saving data path: " + fullPath + " Error: " + e.Message); } }
public GameData Load(GameData _gameData = null) { string fullPath = Path.Combine(dataDirPath, dataFileName); try { string dataToLoad = string.Empty;
if (File.Exists(fullPath)) { using (FileStream stream = new FileStream(fullPath, FileMode.Open)) { using (StreamReader reader = new StreamReader(stream)) { dataToLoad = reader.ReadToEnd(); } }
if (encryptData) { dataToLoad = EncryDecrypt(dataToLoad); }
return JsonUtility.FromJson<GameData>(dataToLoad); } } catch (Exception e) { Debug.LogError("Error in loading data path: " + fullPath + " Error: " + e.Message); }
if (_gameData == null) { _gameData = new GameData(); } return _gameData; }
public void Delete() { string fullPath = Path.Combine(dataDirPath, dataFileName); if (File.Exists(fullPath)) { File.Delete(fullPath); } }
private string EncryDecrypt(string _data) { string result = string.Empty; for (int i = 0; i < _data.Length; i++) { result += (char)(_data[i] ^ codeWord[i % codeWord.Length]); } return result; } }
|
# 4. 通过接口类管理需要存档的数据
ISaveManager.cs1 2 3 4 5
| public interface ISaveManager { void LoadData(GameData _data); void SaveData(ref GameData _data); }
|
在每个需要存储数据的模块都进行了实现,这边以 Inventory 进行举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public void LoadData(GameData _data) { foreach (KeyValuePair<string, int> pair in _data.inventory) { foreach (ItemData item in GetItemDataBase()) { if (item != null && item.itemID == pair.Key) { InventoryItem itemToLoad = new InventoryItem(item); itemToLoad.stackSize = pair.Value; loadItems.Add(itemToLoad); } } }
foreach (KeyValuePair<string, int> pair in _data.equipment) { foreach (ItemData item in GetItemDataBase()) { if (item != null && item.itemID == pair.Key) { InventoryItem equipmentToLoad = new InventoryItem(item); equipmentToLoad.stackSize = pair.Value; equipItems.Add(equipmentToLoad); } } } }
public void SaveData(ref GameData _data) { _data.inventory.Clear(); _data.equipment.Clear();
foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary) { _data.equipment.Add(item.Key.itemID, item.Value.stackSize); }
foreach (KeyValuePair<ItemData, InventoryItem> item in GetAllItems()) { _data.inventory.Add(item.Key.itemID, item.Value.stackSize); } }
|
由于我舍弃了 Inventory 的使用,所以我需要创建一个方法获取所有的物品,用以存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public Dictionary<ItemData, InventoryItem> GetAllItems() { Dictionary<ItemData, InventoryItem> allItems = new Dictionary<ItemData, InventoryItem>();
foreach (var category in categories) { switch (category.Key) { case ItemType.Equipment: AddEquipmentItemsToDictionary(category.Value as EquipmentCategory, allItems); break; default: AddCategoryItemsToDictionary(category.Value as InventoryCategory, allItems); break; } }
return allItems; }
private void AddEquipmentItemsToDictionary(EquipmentCategory equipmentCategory, Dictionary<ItemData, InventoryItem> allItems) { if (equipmentCategory == null) return;
foreach (var item in equipmentCategory.GetTypeToItems()) { foreach (InventoryItem inventoryItem in item.Value) { if (!allItems.ContainsKey(inventoryItem.data)) { allItems.Add(inventoryItem.data, inventoryItem); } } } }
private void AddCategoryItemsToDictionary(InventoryCategory inventoryCategory, Dictionary<ItemData, InventoryItem> allItems) { if (inventoryCategory == null) return;
foreach (InventoryItem item in inventoryCategory.GetItems()) { if (!allItems.ContainsKey(item.data)) { allItems.Add(item.data, item); } } }
|
两个辅助类用于分类管理多种类型的物品,最终返回所有物品及状态给存档
# 5. 加载存档与新的开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void Start() { LoadSaveList(); }
void LoadSaveList() { List<GameData> saveList = SaveManager.instance.GetSaveList();
foreach (GameData save in saveList) { GameObject button = Instantiate(saveButtonPrefab, saveListContainer); button.GetComponentInChildren<TextMeshProUGUI>().text = save.saveName + " - " + save.saveTime;
button.GetComponent<Button>().onClick.AddListener(() => { LoadSave(save); }); } }
|
获取存档列表并通过预制件加到 UI 当中去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void LoadSave(GameData _gameData) { Time.timeScale = 1f; StartCoroutine(LoadSaveCoroutine(_gameData, 1.5f)); }
private IEnumerator LoadSaveCoroutine(GameData _gameData, float delay) { DarkScreen.SetActive(true); yield return new WaitForSeconds(delay);
SaveManager.instance.gameData = _gameData; SceneManager.LoadScene("GameScene"); }
|
然后通过场景切换与画布过场动画进行跳转