赞
踩
在Unity中用于保存玩家数据到本地的方法有很多种,这是一套基于Newtonsoft.Json的存储系统,Newtonsoft.Json相对于JsonUtility可以直接序列化字典或者列表,但是相对的也多了其他的限制,这个会在下面有所提到
在游戏中,我们有很多的数据需要保存,这些数据在不同的类里面,为了存储这些类里面的数据,我们可以用一个接口将这个数据传到用于保存数据的父类中(里氏替换),然后在存储系统的业务中心中通过一个列表存储获得数据的接口,通过循环接口中获得的数据,将其序列化存储在本地。
public class Data
{
//后面需要存储什么数据在里面加
}
public interface ISavable
{
void RegisterSaveData()=> SaveLoadManager.RegisterSaveData(this);
void UnRegisterSaveData()=> SaveLoadManager.UnRegisterSaveData(this);
void GetSaveData(Data data);//获得数据的方法
void LoadData(Data data);//加载数据的方法
}
1、在C#中,我们在使用一个类时,需要new一个对象
2、存储时需要一个保存文件的路径
3、一个将数据传入savableList 的方法,一个将数据移出savableList
4、一个保存方法Save,一个加载方法Load,一个读取存储文件的方法ReadSaveData
public class SaveLoadManager : SingletonScript<SaveLoadManager> { public SaveLoadPanel saveLoadPanel; private List<ISavable> savableList = new List<ISavable>(); private Data saveData; private string jsonFolder; protected override void Awake() { base.Awake(); saveData = new Data(); jsonFolder = Application.persistentDataPath + SaveLoadPath.Save_Folder; ReadSavedData(); } public static void RegisterSaveData(ISavable savableSave) { if (!Instance.savableList.Contains(savableSave)) { Instance.savableList.Add(savableSave); Debug.Log("存档列表中的数据个数:" + Instance.savableList.Count); } } public static void UnRegisterSaveData(ISavable savable) { if (Instance.savableList.Contains(savable)) { Instance.savableList.Remove(savable); Debug.Log("移除存档列表成功:" + savable.ToString()); } } public static void Save() { try { foreach (var savable in Instance.savableList) { savable.GetSaveData(Instance.saveData);//循环接口获得的数据 } var saveData = JsonConvert.SerializeObject(Instance.saveData);//序列化数据为json文件 var resultPath = Instance.jsonFolder + SaveLoadPath.Save_File;//存储路径 Directory.CreateDirectory(Instance.jsonFolder);//创建文件夹 File.WriteAllText(resultPath, saveData);//将数据写入本地 Debug.Log("存档成功:" + resultPath); } catch (Exception ex) { Debug.LogError("存档失败" + ex.Message); } } public static void Load() { //读取的方法与加载类似,都是先循环ISavabel接口获得的数据 try { foreach (var savable in Instance.savableList) { savable.LoadData(Instance.saveData); Debug.Log("加载存档成功:" + savable.ToString()); } } catch (Exception ex) { Debug.LogError("加载失败" + ex.Message); } } public static void ReadSavedData()//在游戏启动时先读取是否有存档,如果有则将数据传给saveData ,然后通过Load方法读取 { var resultPath = Instance.jsonFolder + SaveLoadPath.Save_File;//存储路径 if (File.Exists(resultPath)) { var stringData = File.ReadAllText(resultPath); Instance.saveData = JsonConvert.DeserializeObject<Data>(stringData);//将数据传给saveData Debug.Log("读取存档成功" + resultPath); } } } public class SaveLoadPath { public const string Save_Folder = "/SAVE DATA/"; public const string Save_File = "data.save"; }
Tips:SingletonScript是一个泛型单例,不了解的朋友可以先去了解一下。
至此,这个存储系统实现了简单的数据存储加载功能(开始游戏,继续游戏),但它仍然不支持多存档功能,同一角色不同属性的保存功能,删除存档的功能
我们在游玩游戏时,如果打开它的存档面板就会发现,存档可以不止有一个,当我们选择某一存档选择加载时,便会加载对应的存档,接下来我们在这个存档系统中实现它。
1、搭建存档面板(这个大家自行发挥)
当我们点击生成存档时,会保存当前数据,然后生成一个存档按钮,但选择存档按钮时,点击加载存档,会加载当前所选择的存档,点击删除存档,会删除当前所选择的存档,点击清空存档,会清除所有存档,这么多的存档,据此我们需要一个专门保存存档目录的列表,在每次启动或者关闭游戏时自动调用这个方法,根据保存在存档目录中的信息将对应的存档按钮实例化出来
2、创建用于管理面板上组件的代码SaveLoadPanel
using UnityEngine; using UnityEngine.UI; public class SaveLoadPanel : MonoBehaviour { public Transform instanceSaveBtnPosParent;//生成存档按钮的父级 public SaveButton saveButtonPre;//存档按钮的预制体 public LoadButton loadButton;//加载按钮 public DeletButton deletButton;//删除按钮 public List<SaveButtonData> saveButtonDataList = new List<SaveButtonData>(); public void InstanceSaveBtn(string saveName,string savePath)//生成按钮的方法 { SaveLoadManager.Instance.saveDataCatalogue.saveButtonDataList = saveButtonDataList; SaveButton saveBtn = Instantiate(saveButtonPre, instanceSaveBtnPosParent); saveBtn.saveText.text = saveName; saveBtn.savePath = savePath; saveBtn.saveToggle.group = instanceSaveBtnPosParent.GetComponent<ToggleGroup>(); saveButtonDataList.Add(saveBtn.GetSaveButtonData()); } }
3、创建存档按钮预制体及代码SaveButton;
using UnityEngine; using UnityEngine.UI; public class SaveButton : MonoBehaviour { public Text saveText; public Toggle saveToggle; [Tooltip("生成按钮时传入的路径")]public string savePath; private void Awake() { saveToggle.onValueChanged.AddListener(OnClickSaveToggle); } private void OnClickSaveToggle(bool isSelected) { if(isSelected) { saveToggle.isOn = true; SaveLoadManager.Instance.saveLoadPanel.loadButton.savePath = null; SaveLoadManager.Instance.saveLoadPanel.loadButton.savePath = savePath;//将路径信息传递给LoadButton的savePath SaveLoadManager.Instance.saveLoadPanel.deletButton.savePath = null; SaveLoadManager.Instance.saveLoadPanel.deletButton.savePath = savePath; } Debug.Log("当前选择存档:" + saveText.text); } public SaveButtonData GetSaveButtonData() { return new SaveButtonData { saveName = this.saveText.text, savePath = this.savePath }; } } [System.Serializable] public class SaveButtonData { public string saveName; public string savePath; }
在这段代码中有一个存档的名字和一个存档路径,在业务中心SaveLoadManager中有一个用于读取路径的方法ReadSaveData,这是发现,我们只需要点击当前存档时将这个路径传给这个方法,然后加载时调用Load方法,就可以实现加载对应存档的功能,同样的删除所选存档的功能,也可以据此去实现。
Tips:Toggle这个组件的功能与Button类似,后续的使用中发现这个在做多选择时的选中效果时比Button好用,便替换成了此,使用Toggle时需要给父级设置一个ToggleGroup。可能会有人疑问为什么要新建一个类将数据SaveButton中的一些数据转换成这个类,这是因为UnityEngine.UI中的数据在使用Newtonsoft.Json序列化时,会出现循环自引用的问题,从而出现无法存档的问题,所以要进行一次数据的转换
1、因为存档目录与其他要保存的数据不同,它需要在游戏每次启动或者关闭游戏时自动加载,所以需要专门去处理这个功能。
在Data类中创建一个新类SaveDataCatalogue用于保存SaveButtonData
[System.Serializable]
public class SaveDataCatalogue
{
public List<SaveButtonData> saveButtonDataList;
}
2、在SaveLoadManager中new一个对象
public SaveDataCatalogue saveDataCatalogue;
protected override void Awake()
{
base.Awake();
saveData = new Data();
saveDataCatalogue = new SaveDataCatalogue();
}
3、创建生成目录的方法SaveCatalogue,在SaveLoadPath中加入一个目录文件的常量,用于当目录文件的名称
public class SaveLoadPath
{
public const string Save_Folder = "/SAVE DATA/";
public const string Save_File = "data.save";
public const string Save_Catalogue = "data.catalogue";
}
private static void SaveCatalogue()//推荐在Disable时生成存档目录
{
var resultPath = Instance.jsonFolder + SaveLoadPath.Save_Catalogue;
var saveDataCatalogue = JsonConvert.SerializeObject(Instance.saveDataCatalogue);
Directory.CreateDirectory(Instance.jsonFolder);
File.WriteAllText(resultPath, saveDataCatalogue);
Debug.Log("保存存档目录成功");
}
4、创建读取目录的方法LoadDataCatalogue并传入一个SaveDataCatalogue参数
private void LoadDataCatalogue(SaveDataCatalogue catalogueData)//启动时读取目录,在Start中执行 { var resultPath = Instance.jsonFolder + SaveLoadPath.Save_Catalogue; if (File.Exists(resultPath)) { var stringcatalogueData = File.ReadAllText(resultPath); catalogueData = JsonConvert.DeserializeObject<SaveDataCatalogue>(stringcatalogueData); if(catalogueData != null) { saveLoadPanel.saveButtonDataList = catalogueData.saveButtonDataList; saveDataCatalogue.saveButtonDataList = catalogueData.saveButtonDataList; if(saveDataCatalogue.saveButtonDataList != null)//需要确保反序列化后的数据存在saveButtonDataList,否则会报空 { SaveButton saveButton = null; for (int i = 0; i < saveDataCatalogue.saveButtonDataList.Count; i++) { saveButton = Instantiate(saveLoadPanel.saveButtonPre,saveLoadPanel.instanceSaveBtnPosParent); //确保父级包含ToggleGroup if(saveLoadPanel.instanceSaveBtnPosParent.GetComponent<ToggleGroup>() == null) { saveLoadPanel.instanceSaveBtnPosParent.AddComponent<ToggleGroup>(); saveButton.saveToggle.group = saveLoadPanel.instanceSaveBtnPosParent.GetComponent<ToggleGroup>(); } else { saveButton.saveToggle.group = saveLoadPanel.instanceSaveBtnPosParent.GetComponent<ToggleGroup>(); } saveButton.saveText.text = saveDataCatalogue.saveButtonDataList[i].saveName; saveButton.savePath = saveDataCatalogue.saveButtonDataList[i].savePath; } } } Debug.Log("读取存档目录成功" + resultPath); } }
5、优化ReadSaveData方法
public static void ReadSavedData(string savePath)
{
var resultPath = savePath;
if (File.Exists(resultPath))
{
var stringData = File.ReadAllText(resultPath);
Instance.saveData = JsonConvert.DeserializeObject<Data>(stringData);
Debug.Log("读取存档成功" + resultPath);
}
}
6、优化后ReadSaveData的方法需要在点击加载存档时传递过来的savePath,所以我们需要一个LoadButton类挂载在加载按钮上,用于专门处理点击事件,和处理点击SaveButton时传递过来的savePath
using UnityEngine; using UnityEngine.UI; public class LoadButton : MonoBehaviour { public Button loadBtn; public string savePath; private void Awake() { loadBtn.onClick.AddListener(OnLoad); } private void OnLoad() { SaveLoadManager.ReadSavedData(savePath); } }
7、优化Save方法
在保存时,需要一个当前存档的名字,我们可以用一个时间戳来表示,点击生成存档时,需要生成一个保存当前路径的按钮
public static void Save() { try { foreach (var savable in Instance.savableList) { savable.GetSaveData(Instance.saveData); } System.DateTime time = System.DateTime.Now; var resultPath = Instance.jsonFolder + time.ToString("yyyy_MMdd_HHmmss") + SaveLoadPath.Save_File; Instance.saveLoadPanel.InstanceSaveBtn(time.ToString("yyyy_MMdd_HHmmss"),resultPath);//以当前的时间戳为名 var saveData = JsonConvert.SerializeObject(Instance.saveData); Directory.CreateDirectory(Instance.jsonFolder); File.WriteAllText(resultPath, saveData); Debug.Log("存档成功:" + resultPath); } catch (Exception ex) { Debug.LogError("存档失败" + ex.Message); } }
至此,已经实现了多存档系统的存储加载,那么如何使用它?有两种常见的方法(事件中心,SO事件的方法),这里笔者选择的是利用的是后者。
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu(menuName = "Event/VoidEvent_SO")]
public class VoidEvent_SO : ScriptableObject
{
public UnityAction OneventRaised;
public void RaiseEvent()
{
OneventRaised?.Invoke();
}
}
1、在SaveLoadManager中获得者两个无返回值的SO
public VoidEvent_SO saveEvent;
public VoidEvent_SO loadEvent;
2、注册事件
private void OnEnable()
{
saveEvent.OneventRaised += Save;
loadEvent.OneventRaised += Load;
}
private void OnDisable()
{
saveEvent.OneventRaised -= Save;
loadEvent.OneventRaised -= Load;
SaveCatalogue();
}
这样便已经将Save和Load方法注册成了事件
3、赋值
创建两个SO文件,并赋值
4、使用
Save按钮直接使用
Load按钮在代码中先获取SO,然后在点击时使用
注意:由于所有要存储的数据都需要通过SaveLoadManager的注册和注销的方法获取和移除,所以这个类需要在其他类之前执行,有两种方法实现这种效果
1、[DefaultExecutionOrder(-100)]
在SaveLoadManager上加入这个特性
2、
这两种方法其实都是更改Unity中代码执行的优先级
好了,目前这个存档系统已经实现了多存档功能,至于删除指定存档和删除所以存档的功能,它的原理与读取时类似,留着各位读者去自己实现吧,另外如果在使用过程中发现无法存储的数据的很话(一般是Unity自身的数据),可以先试着自行转换,无法实现的话可以评论区讯问哦。
如果觉得文档太乱的话可以自行下载学习:https://github.com/immortal5205/SampleDialogue,这个里面不止有当前的存储系统,还有之前的对话系统已及任务系统,大家也可以对照着之前的文档学习。
如果想查看具体在游戏中的使用效果的话:https://www.bilibili.com/video/BV1wt421M7Lg/?vd_source=cbcb01112bcf2b5e16c5a97933138e45
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。