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

最终效果

在这里插入图片描述

文章目录

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

前言

前面写过小型游戏存储功能:
【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.rhkb.cn/news/361767.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

告别数据线!轻松实现iOS和安卓设备间的文件共享

用 AirDroid 的附近传输功能&#xff0c;完全免费&#xff0c;几十个G的文件也可以相互传输。不限制iPhone和iPad数量&#xff0c;多个设备同时登录也不会强迫下线。 当你要在苹果手机和安卓手机之间传输文件&#xff0c;请将AirDroid安装到两台手机上&#xff0c;然后登录同一…

搞定求职难题:工作岗位列表+简历制作工具 | 开源专题 No.75

SimplifyJobs/New-Grad-Positions Stars: 8.5k License: NOASSERTION 这个项目是一个用于分享和跟踪美国、加拿大或远程职位的软件工作机会列表。该项目的核心优势和关键特点如下&#xff1a; 自动更新新岗位信息便捷地提交问题进行贡献提供一键申请选项 BartoszJarocki/cv…

从0到1实现LLM学习笔记附录B(GPT-4o翻译版)

来源&#xff1a;https://github.com/rasbt/LLMs-from-scratch?tabreadme-ov-file https://www.manning.com/books/build-a-large-language-model-from-scratch

通过混合栅极技术改善p-GaN功率HEMTs的ESD性能

来源&#xff1a;Improved Gate ESD Behaviors of p-GaN PowerHEMTs by Hybrid Gate Technology&#xff08;ISPSD 24年&#xff09; 摘要 本工作中&#xff0c;首次证明了混合栅极技术在不增加额外面积和寄生效应的前提下&#xff0c;能有效提升p-GaN HEMTs的栅极静电放电(E…

5G赋能安防视频监控:EasyCVR视频汇聚融合创新技术,共筑多场景安全防线

随着科技的快速发展&#xff0c;第五代移动通信技术&#xff08;5G&#xff09;已逐渐成为我们生活中的重要组成部分。其中&#xff0c;5G技术以其超高速、低延迟、大连接数的特点&#xff0c;正在深刻改变着我们的生活方式和社会运行模式。安防监控领域作为社会安全的重要组成…

Web Worker 学习及使用

了解什么是 Web Worker 提供了可以在后台线程中运行 js 的方法。可以不占用主线程&#xff0c;不干扰用户界面&#xff0c;可以用来执行复杂、耗时的任务。 在worker中运行的是另一个全局上下文&#xff0c;不能直接获取 Window 全局对象。不同的 worker 可以分为专用和共享&…

Golang | Leetcode Golang题解之第200题岛屿数量

题目&#xff1a; 题解&#xff1a; func numIslands(grid [][]byte) int {res : 0for i : 0; i < len(grid); i {for j : 0; j < len(grid[i]); j {if grid[i][j] 1 {resdfs(grid, i, j)}}}return res }func dfs(grid [][]byte, r, c int) {h, w : len(grid), len(gri…

ElasticSearch索引架构与存储

关于ES官网的介绍: Elasticsearch provides near real-time search and analytics for all types of data. Whether you have structured or unstructured text, numerical data, or geospatial data, Elasticsearch can efficiently store and index it in a way that support…

python--序列化模块json与pickle

什么叫序列化&#xff1f; 将原本的字典、列表等内容转换成一个字符串的过程就 叫做序列化。 多用的两个序列化模块&#xff1a;json与pickle json&#xff0c;用于字符串 和 python数据类型间进行转换 pickle&#xff0c;用于python特有的类型 和 python的数据类型间进行转换 …

springcloud-sentinel 限流组件中文文档

快速开始 欢迎来到 Sentinel 的世界&#xff01;这篇新手指南将指引您快速入门 Sentinel。 Sentinel 的使用可以分为两个部分: 核心库&#xff08;Java 客户端&#xff09;&#xff1a;不依赖任何框架/库&#xff0c;能够运行于 Java 8 及以上的版本的运行时环境&#xff0c…

星坤Type-A连接器:创新快充技术,引领电子连接!

快速发展的电子时代&#xff0c;消费者对电子设备的性能和便利性有着更高的要求。特别是在充电和数据传输方面&#xff0c;快充技术和高速传输已成为市场的新宠。中国星坤公司推出的Type-A连接器系列&#xff0c;以其卓越的性能和创新的设计&#xff0c;满足了市场对高效、稳定…

Java数据脱敏

数据脱敏 敏感数据在存储过程中为是否为明文, 分为两种 落地脱敏: 存储的都是明文, 返回之前做脱敏处理不落地脱敏: 存储前就脱敏, 使用时解密, 即用户数据进入系统, 脱敏存储到数据库中, 查询时反向解密 落地脱敏 这里指的是数据库中存储的是明文数据, 返回给前端的时候脱…

电阻屏和电容屏

目录 一、电阻屏 1.欧姆定律 2.电阻屏原理 &#xff08;1&#xff09;测量 X 坐标 &#xff08;2&#xff09;测量 Y 坐标 3.电阻屏数据 二、电阻屏 1.原理 2.电容屏数据 &#xff08;1&#xff09;Type A &#xff08;2&#xff09;Type B 3.电容屏的实验数据 一、…

Jira实践案例分享:小米集团如何通过API请求优化、数据治理与AI智能客服等,实现Jira系统的高效运维

日前&#xff0c;Atlassian中国合作伙伴企业日活动在上海成功举办。活动以“AI协同 创未来——如何利用人工智能提升团队协作&#xff0c;加速产品交付”为主题&#xff0c;深入探讨了AI技术在团队协作与产品交付中的创新应用与实践&#xff0c;吸引了众多业内专家、企业客户及…

文心智能体平台介绍和应用:制作你的智能体(运维小帮手)

这是我自己制作的智能体 大家可以了解一下&#xff01; 运维小帮手&#xff01;https://mbd.baidu.com/ma/s/tE19dqvr 文心智能体平台官网首页 点击跳转&#xff01;https://agents.baidu.com/ 什么是智能体平台&#xff1f; 文心智能体平台&#xff08;Wenxin Intelligen…

关于Vite+Vue+Ts WebStorm路径别名的问题

一、准备一个项目 二、在 vite.config.js 中添加 resolve: {alias: {: /src}} 三、tsconfig.app.json中添加代码 //添加代码"baseUrl": ".","paths": {"/*": ["src/*"]}把src的一个文件修改路径为开头 四、安装插件 npm i …

第 11 课:组件介绍与自定义开发

本讲主要介绍了隐语的组件标准、已有的组件能力以及进一步的自定义开发流程。经过本讲的学习&#xff0c;可以为将隐语集成到任意调度系统&#xff0c;基于Kusica/SecretPad进行二次开发&#xff0c;以及参与隐语开放标准共建建立基础。 一、隐语开放标准 隐语提出的适用于隐私…

仓库管理系统

摘 要 随着电子商务的快速发展和物流行业的蓬勃发展&#xff0c;仓库管理成为了企业重要的一环。仓库管理涉及到商品的入库、出库、库存管理等一系列操作&#xff0c;对于企业的运营效率和成本控制具有重要影响。传统的仓库管理方式往往依赖于人工操作和纸质记录&#xff0c;存…

【Windows 常用工具系列 17 -- windows bat 脚本多参数处理】

请阅读【嵌入式开发学习必备专栏】 文章目录 bat 脚本命令行参数使用示例多参数处理使用示例遍历所有参数 bat 脚本命令行参数 在Windows批处理&#xff08;.bat&#xff09;脚本中接收命令行参数是一个常见的需求&#xff0c;这样的脚本能够根据提供的参数执行不同的操作。命…

Flutter页面状态保留策略

目的: 防止每次点击底部按钮都进行一次页面渲染和网络请求 1. 使用IndexedStack 简单,只需要把被渲染的组件外部套一层IndexedStack即可 缺点: 在应用启动的时候,所有需要保存状态的页面都会直接被渲染,保存起来. 对性能有影响 2. 使用PageController 实现较为复杂,但是不用…