【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解

devtools/2024/10/18 11:37:59/

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 存储位置信息
  • 存储更多数据
  • 存储场景信息
  • 持久化存储数据
  • 完结

前言

前面写过小型游戏存储功能:
unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解(包含数据安全处理方案的加密解密)

这次做一个针对大型游戏,更加复杂和全面的存储系统,解决存储数据比较多的情况。其实这个功能在之前的实战项目中已经做过,在这里我只是单独提取出来,感兴趣可以回头去看看:
【制作100个unity游戏之26】unity2d横版卷轴动作类游戏1(附带项目源码)

存储位置信息

新增VoidEventSO,定义保存数据事件ScriptableObject

[CreateAssetMenu(menuName = "Event/VoidEventSO")]
public class VoidEventSO : ScriptableObject {public UnityAction OnEventRaised;public void RaiseEvent(){OnEventRaised?.Invoke();}
}

新增Data

public class Data
{/// <summary>/// 存储角色位置信息的字典,键为角色名称,值为对应的位置坐标(Vector3)。/// </summary>public Dictionary<string, Vector3> characterPosDict = new Dictionary<string, Vector3>();
}

新增DataManager,为了保证Data Manager可以优先其他代码执行,为它添加特性[DefaultExecutionOrder(-100)]。很多小伙伴没有留意后面会提到的这个内容,发现有ISaveable的注册报错。[DefaultExecutionOrder(-100)] 是 Unity 中的一个属性,用于指定脚本的默认执行顺序。参数 -100 表示该脚本的执行顺序优先级,数值越小,优先级越高,即越先执行。

新输入系统获取键盘的输入,按下L按键读取一下进度。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;//指定脚本的默认执行顺序,数值越小,优先级越高
[DefaultExecutionOrder(-100)]
public class DataManager : MonoBehaviour
{public static DataManager instance;[Header("事件监听")]public VoidEventSO saveDataEvent; // 保存数据事件/// <summary>/// 存储需要保存数据的 ISaveable 实例的列表。/// </summary>private List<ISaveable> saveableList = new List<ISaveable>();/// <summary>/// 保存数据到 Data 对象中。/// </summary>private Data saveData;private void Awake(){if (instance == null){instance = this;}else{Destroy(gameObject);}saveData = new Data();}private void Update(){// 按L 加载测试if(Keyboard.current.lKey.wasPressedThisFrame){Debug.Log("加载");Load();}}/// <summary>/// 注册需要保存数据的 ISaveable 实例。/// </summary>/// <param name="saveable">需要保存数据的 ISaveable 实例。</param>public void RegisterSaveData(ISaveable saveable){if (!saveableList.Contains(saveable)){saveableList.Add(saveable);}}public void UnRegisterSaveData(ISaveable saveable){if (saveableList.Contains(saveable)){// 如果在,就从列表中移除saveableList.Remove(saveable);}}private void OnEnable(){saveDataEvent.OnEventRaised += Save; // 监听保存数据事件}private void OnDisable(){saveDataEvent.OnEventRaised -= Save; // 取消监听保存数据事件}/// <summary>/// 保存数据。/// </summary>public void Save(){foreach (var saveable in saveableList){saveable.GetSaveData(saveData);}}/// <summary>/// 加载数据并应用到相应的 ISaveable 实例中。/// </summary>public void Load(){foreach (var saveable in saveableList){saveable.LoadData(saveData);}}
}

挂载配置
在这里插入图片描述

新增接口ISaveable

public interface ISaveable
{DataDefination GetDataID();/// <summary>/// 将该实例注册到数据管理器以便保存数据。/// </summary>void RegisterSaveData() => DataManager.instance.RegisterSaveData(this);/// <summary>/// 将该实例从数据管理器中注销,停止保存数据。/// </summary>void UnRegisterSaveData() => DataManager.instance.UnRegisterSaveData(this);/// <summary>/// 获取需要保存的数据并存储到指定的 Data 对象中。/// </summary>/// <param name="data">保存数据的 Data 对象。</param>void GetSaveData(Data data);/// <summary>/// 从指定的 Data 对象中加载数据并应用到该实例中。/// </summary>/// <param name="data">包含加载数据的 Data 对象。</param>void LoadData(Data data);
}

那么如果有三个野猪的名字完全一样,我们怎么区分每一只野猪具体存储的位置呢,所以接下来我们要创建一个唯一的标识,我们可以直接使用c#为我们设置好的全局唯一标识符,GUID就是个16位的串码,保证它的唯一性
在这里插入图片描述
新增枚举

/// <summary>
/// 指示数据定义的持久化类型。
/// </summary>
public enum PersistentType
{/// <summary>/// 可读写的持久化类型,数据会被持久化保存。/// </summary>ReadWrite,/// <summary>/// 不持久化类型,数据不会被持久化保存。/// </summary>DoNotPerst
}

新增DataDefination

public class DataDefination : MonoBehaviour
{/// <summary>/// 持久化类型,指示数据定义的持久化方式。/// </summary>public PersistentType persistentType;/// <summary>/// 数据定义的唯一标识符。/// </summary>public string ID;/// <summary>/// 当编辑器中的属性值发生更改时调用,用于自动设置默认的ID值。/// </summary>private void OnValidate(){if (persistentType == PersistentType.ReadWrite){if (ID == string.Empty){ID = System.Guid.NewGuid().ToString();}}else{ID = string.Empty;}}
}

配置挂载脚本,比如我们放在人物身上,生成唯一的UID,记得每个UID都要唯一,如果是复制出来的对象记得刷新一下UID
在这里插入图片描述
修改PlayerController,调用接口

public class PlayerController : MonoBehaviour, ISaveable
{//...private void OnEnable(){ISaveable saveable = this;saveable.RegisterSaveData();}private void OnDisable(){ISaveable saveable = this;saveable.UnRegisterSaveData();}// 获取数据ID,用于唯一标识当前对象的位置信息public DataDefination GetDataID(){return GetComponent<DataDefination>();}// 将对象的位置信息保存到数据中public void GetSaveData(Data data){// 检查数据中是否已经存在当前对象的位置信息if (data.characterPosDict.ContainsKey(GetDataID().ID)){// 如果已经存在,则更新位置信息data.characterPosDict[GetDataID().ID] = transform.position;}else{// 如果不存在,则添加新的位置信息data.characterPosDict.Add(GetDataID().ID, transform.position);}}// 从数据中加载对象的位置信息public void LoadData(Data data){// 检查数据中是否存在当前对象的位置信息if (data.characterPosDict.ContainsKey(GetDataID().ID)){// 如果存在,则将位置信息设置为对应的数值transform.position = data.characterPosDict[GetDataID().ID];}}
}

修改SavePoint,调用存储数据

public class SavePoint : MonoBehaviour, IInteractable {private SpriteRenderer spriteRenderer;public Sprite darkSprite;public Sprite lightSprite;public bool isDone;public VoidEventSO saveDataEvent; // 保存数据事件private void Awake() {spriteRenderer = GetComponent<SpriteRenderer>();    }private void OnEnable() {spriteRenderer.sprite = isDone ? lightSprite : darkSprite;}public void TriggerAction(){if(!isDone){Save();spriteRenderer.sprite = lightSprite;GetComponent<Collider2D>().enabled = false;isDone = true;}}//存储数据private void Save(){Debug.Log("存储数据");saveDataEvent.RaiseEvent();}
}

效果,按L测试读取数据,角色回到存储的位置
在这里插入图片描述

存储更多数据

修改Data,定义通用的float的类型,所有和float相关的类型都可用它保存

public class Data
{//...public Dictionary<string, float> floatSaveData = new Dictionary<string, float>();
}

但是如何区分是人物的血条还是能量呢?我们可以加入不同的后缀,修改PlayerController

// 将对象的位置信息保存到数据中
public void GetSaveData(Data data)
{// 检查数据中是否已经存在当前对象的位置信息if (data.characterPosDict.ContainsKey(GetDataID().ID)){// 如果已经存在,则更新位置信息data.characterPosDict[GetDataID().ID] = transform.position;data.floatSaveData[GetDataID().ID + "Health"] = GetComponent<Character>().currentHealth;data.floatSaveData[GetDataID().ID + "Power"] = GetComponent<Character>().currentPower;}else{// 如果不存在,则添加新的位置信息data.characterPosDict.Add(GetDataID().ID, transform.position);//存储玩家血量和能量data.floatSaveData.Add(GetDataID().ID + "Health", GetComponent<Character>().currentHealth);data.floatSaveData.Add(GetDataID().ID + "Power", GetComponent<Character>().currentPower);}
}// 从数据中加载对象的位置信息
public void LoadData(Data data)
{// 检查数据中是否存在当前对象的位置信息if (data.characterPosDict.ContainsKey(GetDataID().ID)){// 如果存在,则将位置信息设置为对应的数值transform.position = data.characterPosDict[GetDataID().ID];GetComponent<Character>().currentHealth = data.floatSaveData[GetDataID().ID + "Health"];GetComponent<Character>().currentPower = data.floatSaveData[GetDataID().ID + "Power"];//更新血条能量UIGetComponent<Character>().OnHealthChanged?.Invoke(GetComponent<Character>());}
}

效果
在这里插入图片描述
同理你可以存储其他的比如宝箱,野猪等信息

存储场景信息

修改Data,将场景信息转为json数据进行存取

public string sceneToSave;public void SaveGameScene(SceneField savedScene){sceneToSave = JsonUtility.ToJson(savedScene);
}public SceneField GetSavedScene(){SceneField loadedData = JsonUtility.FromJson<SceneField>(sceneToSave);return loadedData;
}

修改SavePoint,存储场景信息

public SceneField currentLoadedScene;public class SavePoint : MonoBehaviour, IInteractable, ISaveable
{//...public DataDefination GetDataID(){return null;}public void GetSaveData(Data data){data.SaveGameScene(currentLoadedScene);//存储场景}public void LoadData(Data data){}
}

配置当前场景
在这里插入图片描述

修改DataManager,我们希望加载存储场景完成后,再进行其他的LoadData操作,所以加载存储场景的操作我们就不放在LoadData里执行了。可以加入场景过渡渐变,让效果更好,这里我就不加了

/// <summary>
/// 加载数据并应用到相应的 ISaveable 实例中。
/// </summary>
public void Load()
{//获取存储的场景var scence = saveData.GetSavedScene();if (scence != null){// 获取当前活动的场景Scene activeScene = SceneManager.GetActiveScene();// 获取所有加载的场景for (int i = 0; i < SceneManager.sceneCount; i++){Scene loadedScene = SceneManager.GetSceneAt(i);Debug.Log("Loaded Scene " + i + ": " + loadedScene.name);if (activeScene.name != loadedScene.name) SceneManager.UnloadSceneAsync(loadedScene.name); // 异步卸载所有非主场景}//加载scence场景SceneManager.LoadSceneAsync(scence.SceneName, LoadSceneMode.Additive).completed += operation =>{if (operation.isDone){//获取相机边界方法cameraControl.GetNewCameraBounds();//加载其他数据foreach (var saveable in saveableList){saveable.LoadData(saveData);}}};//控制按钮的显示隐藏sceneLoadTrigger.StartMenu();}
}

效果
在这里插入图片描述

持久化存储数据

具体可以看我这篇文章:【unity小技巧】Unity存储存档保存——PlayerPrefs、JsonUtility和MySQL数据库的使用

修改DataManager

using Newtonsoft.Json;String savePath = "test.json";/// <summary>
/// 保存数据。
/// </summary>
public void Save()
{//。。。//持久化存储数据String jsonData = JsonConvert.SerializeObject(saveData);File.WriteAllText(savePath, jsonData);
}/// <summary>
/// 加载数据并应用到相应的 ISaveable 实例中。
/// </summary>
public void Load()
{//读取数据string jsonData = File.ReadAllText(savePath);//将JSON数据反序列化为游戏数据对象Data saveData = JsonConvert.DeserializeObject<Data>(jsonData);//。。。
}

查看存储的test.json数据
在这里插入图片描述
在这里插入图片描述

效果
在这里插入图片描述

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述


http://www.ppmy.cn/devtools/56861.html

相关文章

求字符串中所有整数的最小和(100%用例) C卷(JavaPythonC++Node.jsC语言)

输入字符串s,输出s中包含所有整数的最小和 说明 1. 字符串s,只包含 a-z A-Z +- ; 2. 合法的整数包括 1) 正整数 一个或者多个0-9组成,如 0 2 3 002 102 2)负整数 负号 - 开头,数字部分由一个或者多个0-9组成,如 -0 -012 -23 -00023 输入描述: 包含数字的字符串 …

PyTorch学习之 torch.squeeze 函数

PyTorch学习之 torch.squeeze 函数 一、功能 torch.squeeze 的主要作用是从给定的张量 input 中移除所有尺寸为1的维度。 二、基本语法 torch.squeeze(input, dimNone)三、参数说明 input (Tensor): 输入的张量。dim (int, 可选): 指定要移除的尺寸为1的维度 如果未指定&am…

cv2.connectedComponentsWithStats中连通性和输出图像标签的类型

cv2.connectedComponentsWithStats中连通性和输出图像标签的类型 文章目录 cv2.connectedComponentsWithStats中连通性和输出图像标签的类型连通性4连通性8连通性使用场景使用方法代码示例 输出图像标签的类型CV_32S (32-bit signed integer)CV_16U (16-bit unsigned integer)选…

开发一套java语言的智能导诊需要什么技术?java+ springboot+ mysql+ IDEA互联网智能3D导诊系统源码

开发一套java语言的智能导诊需要什么技术&#xff1f;java springboot mysql IDEA互联网智能3D导诊系统源码 医院导诊系统是一种基于互联网和3D人体的智能化服务系统&#xff0c;旨在为患者提供精准、便捷的医院就诊咨询服务。该系统整合了医院的各种医疗服务资&#xff1b;智慧…

【AIGC半月报】AIGC大模型启元:2024.07(上)

AIGC大模型启元&#xff1a;2024.07&#xff08;上&#xff09; (1) AIGVBench-T2V&#xff08;文生视频基准测评&#xff09; (1) AIGVBench-T2V&#xff08;文生视频基准测评&#xff09; 2024.07.02 目前已经存在一些英文的文生视频基准&#xff0c;如VBench、FETV和EvalCr…

Vue+ElementUi实现录音播放上传及处理getUserMedia报错问题

1.Vue安装插件 npm install --registryhttps://registry.npmmirror.com 2.Vue页面使用 <template><div class"app-container"><!-- header --><el-header class"procedureHeader" style"height: 20px;"><el-divid…

解决expand-change第一次展开无数据显示与实现

最近写需求时用到了expand-change表格展开回调&#xff0c;但我发现第一次展开后并没有展示任何数据&#xff0c;但我的返回值是存在的&#xff0c;当第二次展开时发现数据就有了。此原因是因为获取数据的接口是异步的&#xff0c;导致Dom渲染不同步导致的&#xff0c;其实解决…

谷歌网站SEO服务有哪些?

Seo其实说来说去就包含三样&#xff0c;网站本身技术优化&#xff0c;内容以及外链&#xff0c;而这三样里&#xff0c;网站的技术优化是前提本身&#xff0c;确保网站符合谷歌搜索规范&#xff0c;包括调整网站的结构、速度和移动设备兼容性&#xff0c;以提高用户体验和搜索引…