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

news2024/10/6 2:26:41

前言

如何在 Unity 中正确制作一个保存和加载系统,该系统使用JSON 文件来处理保存配置文件,可以保存和加载任何类型对象!标题为什么叫小型游戏存储功能呢?因为该存储功能可能只适合存储数据比较单一的情况,它非常的方便快捷,且易于使用和理解。但是如果你的游戏要存储的内容有很多,比如很多怪物的状态,很多宝箱的状态,很多物品的状态,那么它可能就不适用了。但是不用担心,后续我还会针对大型游戏,出更加复杂和全面的存储系统,解决存储数据比较多的情况。

存储

新增SaveProfile保存配置文件的泛型类和SaveProfileData抽象基类

[System.Serializable]
// 保存配置文件的泛型类
public sealed class SaveProfile<T> where T : SaveProfileData
{
    // 保存配置文件的名称
    public string name;
    // 实际保存的数据
    public T saveData;

    // 私有的默认构造函数,防止无参数实例化
    private SaveProfile() { }

    // 公共构造函数,用于初始化名称和保存数据
    public SaveProfile(string name, T saveData)
    {
        this.name = name;
        this.saveData = saveData;
    }
}

// 抽象基类,用于保存数据
public abstract record SaveProfileData {}

新增SaveManager,定义读取和存储 删除存档文件方法

using System;
using System.IO;
using UnityEngine;
using Newtonsoft.Json;

public static class SaveManager
{
    // 文件保存的根目录路径
    private static readonly string saveFolder = Application.persistentDataPath + "/GameData";

    // 删除指定存档文件
    public static void Delete(string profileName)
    {
        if (!File.Exists($"{saveFolder}/{profileName}"))
            throw new Exception($"保存配置文件 {profileName} 未找到!");

        Debug.Log($"已成功删除 {saveFolder}/{profileName}");
        File.Delete($"{saveFolder}/{profileName}");
    }

    // 加载指定类型的存档文件
    public static SaveProfile<T> Load<T>(string profileName) where T : SaveProfileData
    {
        if (!File.Exists($"{saveFolder}/{profileName}"))
            throw new Exception($"保存配置文件 {profileName} 未找到!");

        // 读取文件内容为字符串
        var fileContents = File.ReadAllText($"{saveFolder}/{profileName}");
        // TODO:解密

        Debug.Log($"已成功加载 {saveFolder}/{profileName}");

        // 反序列化为指定类型的SaveProfile<T>对象并返回
        return JsonConvert.DeserializeObject<SaveProfile<T>>(fileContents);
    }

    // 保存指定类型的存档数据
    public static void Save<T>(SaveProfile<T> save) where T : SaveProfileData
    {
        if (File.Exists($"{saveFolder}/{save.name}")){
            // throw new Exception($"保存配置文件 {save.name} 未找到!");
            Delete(save.name);
        }

        // 将SaveProfile<T>对象序列化为JSON格式的字符串
        var jsonString = JsonConvert.SerializeObject(save, Formatting.Indented,
            new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
        // TODO:加密

        if (!Directory.Exists(saveFolder))
            Directory.CreateDirectory(saveFolder);

        // 将加密后的jsonString写入文件
        File.WriteAllText($"{saveFolder}/{save.name}", jsonString);
    }
}

使用

新增SaveData.cs,定义需要存储的数据,如果需要其他数据需要再添加

using UnityEngine;

// 玩家保存数据
public record PlayerSaveData : SaveProfileData
{
    // 玩家位置
    public Vector2 position;
    // 成就数组
    public int[] achievements;
}

// 世界保存数据
public record WorldSaveData : SaveProfileData
{
    // 方块二维数组
    public int[,] blocks;
}

新增Player ,保存测试数据和读取

public class Player : MonoBehaviour
{
    void Start()
    {
        Debug.Log(Application.persistentDataPath);

        // 保存玩家数据
        var playerSave = new PlayerSaveData
        {
            position = new Vector2(1f, 1.5f),
            achievements = new[] { 1, 2, 3, 4, 5 }
        };
        var saveProfile = new SaveProfile<PlayerSaveData>("playerSaveData", playerSave);
        SaveManager.Save(saveProfile);

        //保存世界数据
        var worldSave = new WorldSaveData
        {
            blocks =new[,] { {1,1}, {1,2}, {1,3}}        
        };
        var saveProfile2 = new SaveProfile<WorldSaveData>("WorldSaveData", worldSave);
        SaveManager.Save(saveProfile2);
    }

    void Update()
    {
        //读取数据
        if (Input.GetKeyDown(KeyCode.E))
        {
            Vector2 position = SaveManager.Load<PlayerSaveData>("playerSaveData").saveData.position;
            Debug.Log(position);
            transform.position = position;
        }
    }
}

运行之后,可以去查看保存的文件数据
在这里插入图片描述
在这里插入图片描述

运行按E成功加载数据
在这里插入图片描述
看到这里你应该就明白为什么叫小型游戏存储功能了吧!

数据加密

修改SaveManager,添加数据加密内容

using System;
using System.IO;
using UnityEngine;
using Newtonsoft.Json;

public static class SaveManager
{
    // 文件保存的根目录路径
    private static readonly string saveFolder = Application.persistentDataPath + "/GameData";

     // 加密:选择一些用于亦或操作的字符(注意保密)
    public static char[] keyChars = { 'a', 'b', 'c', 'd', 'e' };

    // 删除指定存档文件
    public static void Delete(string profileName)
    {
        if (!File.Exists($"{saveFolder}/{profileName}"))
            throw new Exception($"保存配置文件 {profileName} 未找到!");

        Debug.Log($"已成功删除 {saveFolder}/{profileName}");
        File.Delete($"{saveFolder}/{profileName}");
    }

    // 加载指定类型的存档文件
    public static SaveProfile<T> Load<T>(string profileName) where T : SaveProfileData
    {
        if (!File.Exists($"{saveFolder}/{profileName}"))
            throw new Exception($"保存配置文件 {profileName} 未找到!");

        // 读取文件内容为字符串
        var fileContents = File.ReadAllText($"{saveFolder}/{profileName}");
        // TODO:解密
        fileContents = Decrypt(fileContents);

        Debug.Log($"已成功加载 {saveFolder}/{profileName}");

        // 反序列化为指定类型的SaveProfile<T>对象并返回
        return JsonConvert.DeserializeObject<SaveProfile<T>>(fileContents);
    }

    // 保存指定类型的存档数据
    public static void Save<T>(SaveProfile<T> save) where T : SaveProfileData
    {
        if (File.Exists($"{saveFolder}/{save.name}")){
            // throw new Exception($"保存配置文件 {save.name} 未找到!");
            Delete(save.name);
        }

        // 将SaveProfile<T>对象序列化为JSON格式的字符串
        var jsonString = JsonConvert.SerializeObject(save, Formatting.Indented,
            new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
        // TODO:加密
        jsonString = Encrypt(jsonString);

        if (!Directory.Exists(saveFolder))
            Directory.CreateDirectory(saveFolder);

        // 将加密后的jsonString写入文件
        File.WriteAllText($"{saveFolder}/{save.name}", jsonString);
    }

    // 加密方法
    public static string Encrypt(string data)
    {
        char[] dataChars = data.ToCharArray();
        for (int i = 0; i < dataChars.Length; i++)
        {
            char dataChar = dataChars[i];
            char keyChar = keyChars[i % keyChars.Length];
            // 重点: 通过亦或得到新的字符
            char newChar = (char)(dataChar ^ keyChar);
            dataChars[i] = newChar;
        }
        return new string(dataChars);
    }

    // 解密方法
    public static string Decrypt(string data)
    {
        return Encrypt(data);
    }
}

存储加密内容
在这里插入图片描述
按E读取数据正常
在这里插入图片描述

完结

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

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

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1866637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 102.目标和

给定一个正整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 ‘’ &#xff0c;在 1 之前添加 ‘-’ &…

AIGC笔记--U-ViT的简单代码实现

1--前言 原论文&#xff1a;All are Worth Words: A ViT Backbone for Diffusion Models 完整可debug的代码&#xff1a;Simple_U-ViT 2--结构 3--简单代码 以视频作为输入&#xff0c;实现上图红色框的计算&#xff1a; import torch import torch.nn as nn from einops im…

6.2 通过构建情感分类器训练词向量

在上一节中&#xff0c;我们简要地了解了词向量&#xff0c;但并没有去实现它。在本节中&#xff0c;我们将下载一个名为IMDB的数据集(其中包含了评论)&#xff0c;然后构建一个用于计算评论的情感是正面、负面还是未知的情感分类器。在构建过程中&#xff0c;还将为 IMDB 数据…

分享一个 MySQL 简单快速进行自动备份和还原的脚本和方法

前言 数据备份和还原在信息技术领域中具有非常重要的作用&#xff0c;不论是人为误操作、硬件故障、病毒感染、自然灾害还是其他原因&#xff0c;数据丢失的风险都是存在的。如果没有备份&#xff0c;一旦数据丢失&#xff0c;可能对个人、企业甚至整个组织造成巨大的损失。 …

2-17 基于matlab的改进的遗传算法(IGA)对城市交通信号优化分析

基于matlab的改进的遗传算法&#xff08;IGA&#xff09;对城市交通信号优化分析。根据交通流量以及饱和流量&#xff0c;对城市道路交叉口交通信号灯实施合理优化控制&#xff0c;考虑到交通状况的动态变化&#xff0c;及每个交叉口的唯一性。通过实时监测交通流量&#xff0c…

部署企业级AI知识库最重要的是什么?✍

随着人工智能技术的迅猛发展&#xff0c;企业级AI知识库成为提升企业管理效率和信息获取能力的重要工具。那么&#xff0c;在部署企业级AI知识库时&#xff0c;最重要的是什么呢&#xff1f;本文将从数据质量、系统可扩展性、用户体验以及智能化这四个关键方面进行详细分析。 …

单片机是否有损坏,怎沫判断

目录 1、操作步骤&#xff1a; 2、单片机损坏常见原因&#xff1a; 3、 单片机不工作的原因&#xff1a; 参考&#xff1a;细讲寄存器读写与Bit位操作原理--单片机C语言编程Bit位的与或非屏蔽运算--洋桃电子大百科P019_哔哩哔哩_bilibili 1、操作步骤&#xff1a; 首先需要…

Objects and Classes (对象和类)

Objects and Classes [对象和类] 1. Procedural and Object-Oriented Programming (过程性编程和面向对象编程)2. Abstraction and Classes (抽象和类)2.1. Classes in C (C 中的类)2.2. Implementing Class Member Functions (实现类成员函数)2.3. Using Classes References O…

第 28 篇 : SSH秘钥登录

1 生成秘钥 ssh-keygen -t rsa ls -a ./.ssh/一直回车就行了 2. 修改配置 vi /etc/ssh/sshd_config放开注释 公钥的位置修改 关闭密码登录 PubkeyAuthentication yes AuthorizedKeysFile .ssh/id_rsa.pub PasswordAuthentication no3. 下载id_rsa私钥, 自行解决 注意…

Vue中数组的【响应式】操作

在 Vue.js 中&#xff0c;当你修改数组时&#xff0c;Vue 不能检测到以下变动的数组&#xff1a; 当你利用索引直接设置一个项时&#xff0c;例如&#xff1a;vm.items[indexOfItem] newValue当你修改数组的长度时&#xff0c;例如&#xff1a;vm.items.length newLength 为…

常见图像分割模型介绍:FCN、U-Net、SegNet、Mask R-CNN

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

AI助力校园安全:EasyCVR视频智能技术在校园欺凌中的应用

一、背景分析 近年来&#xff0c;各地深入开展中小学生欺凌行为治理工作&#xff0c;但有的地方学生欺凌事件仍时有发生&#xff0c;严重损害学生身心健康&#xff0c;引发社会广泛关注。为此&#xff0c;教育部制定了《防范中小学生欺凌专项治理行动工作方案》进一步防范和遏…

软件构造 | 期末查缺补漏

软件构造 | 期末查缺补漏 总体观 软件构造的三维度八度图是由软件工程师Steve McConnell提出的概念&#xff0c;用于描述软件构建过程中的三个关键维度和八个要素。这些维度和要素可以帮助软件开发团队全面考虑软件构建的方方面面&#xff0c;从而提高软件质量和开发效率。 下…

文件批量重命名001到100 最简单的数字序号递增的改名技巧

文件批量重命名001到100 最简单的数字序号递增的改名方法。最近看到很多人都在找怎么批量修改文件名称&#xff0c;还要按固定的ID需要递增&#xff0c;这个办法用F2或者右键改名是不能做到的。 这时候我们可以通过一个专业的文件批量重命名软件来批量处理这些文档。 芝麻文件…

A-8 项目开源 qt1.0

A-8 2024/6/26 项目开源 由于大家有相关的需求&#xff0c;就创建一个项目来放置相关的代码和项目 欢迎交流&#xff0c;QQ&#xff1a;963385291 介绍 利用opencascade和vulkanscene实现stp模型的查看器打算公布好几个版本的代码放在不同的分支下&#xff0c;用qt实现&am…

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区 1. 问题背景 有时我们需要获取特定 HIVE 库下所有分区表&#xff0c;或者所有分区表的所有分区&#xff0c;以便执行进一步的操作&#xff0c;比如通过 使用 HIVE 命令 MSCK REPAIR TABLE table_name sync partiti…

Redis实战—基于setnx的分布式锁与Redisson

本博客为个人学习笔记&#xff0c;学习网站与详细见&#xff1a;黑马程序员Redis入门到实战 P56 - P63 目录 分布式锁介绍 基于SETNX的分布式锁 SETNX锁代码实现 修改业务代码 SETNX锁误删问题 SETNX锁原子性问题 Lua脚本 编写脚本 代码优化 总结 Redisson 前言…

基于盲信号处理的人声分离

1.问题描述 在实际生活中&#xff0c;存在一种基本现象称为“鸡尾酒效应”&#xff0c;该效应指即使在非常嘈杂的环境中&#xff0c;人依然可以从噪声中提取出自己所感兴趣的声音。 在实际应用中&#xff0c;我们可能需要对混合的声音进行分离&#xff0c;此时已知的只有混合…

springcloud第4季 springcloud-alibaba之openfegin+sentinel整合案例

一 介绍说明 1.1 说明 1.1.1 消费者8081 1.1.2 openfegin接口 1.1.3 提供者9091 9091微服务满足&#xff1a; 1 openfegin 配置fallback逻辑&#xff0c;作为统一fallback服务降级处理。 2.sentinel访问触发了自定义的限流配置&#xff0c;在注解sentinelResource里面配置…