换工作了,这下是纯C#开发了,偏单机游戏,所以又要研究一下C#的存档做法。经过一阵时间的解决各种问题,现在已经稳定,需要的老铁可以参考一下。

1.导入ProtoBuf

https://github.com/protocolbuffers/protobuf/releases/
下载需要的语言,解压后导入到自己的目录中。

2.协议声明

[ProtoContract]public class DataRoot{[ProtoMember(1)]public int sA;[ProtoMember(2)]public string sB;[ProtoMember(3)]public float[] sC;[ProtoMember(4)]public DataA sD;[ProtoMember(5)]public List<DataA> sE;}[ProtoContract]public class DataA{[ProtoMember(1)]public int sA1;[ProtoMember(2)]public int sA2;}
  • ProtoContract 声明要序列化的类
  • ProtoMember 声明要序列化的成员

3.存档

数据生成

    DataRoot data = new DataRoot(){sA = 0,sB = "AAA",sC = new float[2] { 1, 2 },sE = null,};data.sD = new DataA();Debug.Log("保存存档完成");

封包与保存

    //将内容进行封包File.Delete(Application.persistentDataPath + "/Save.dat");FileStream stream = File.OpenWrite(Application.persistentDataPath + "/Save.dat");Serializer.Serialize(stream, data);stream.Dispose();
  • ProtoBuf.Serializer.Serialize protobuf自带的序列化方法 将数据转为byte[]
  • protobuf的写入方式是将bytes.length覆盖写入到文件中,最后用ProtoReader.TO_EOF作为结束符添加到之后的一位。也就是说文件的bytes.length永远保持为最大的长度。在项目中,不知道为什么,存档的byte[]变短之后,这个结束符一直有问题,导致存档读取出错,所以我干脆在每次存档时将原存档文件删除。

4.读档

读取与解包

    private void TryDeserialize(out DataRoot data){if (File.Exists(Application.persistentDataPath + "/Save.dat")){//↓↓↓这一部分比较特殊 根据需要判断是否要添加↓↓↓Support.SetInstanceFunc((Type type) =>{if (Type.GetType(type.FullName) == null){return null;}return Activator.CreateInstance(type
#if !(CF || SILVERLIGHT || WINRT || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4), nonPublic: true
#endif);});//↑↑↑这一部分比较特殊 根据需要判断是否要添加↑↑↑//对内容进行解包FileStream stream = File.OpenRead(Application.persistentDataPath + "/Save.dat");data = Serializer.Deserialize<DataRoot>(stream);stream.Dispose();}else{data = null;}//↓↓↓这一部分比较特殊 根据需要判断是否要添加↓↓↓Support.SetInstanceFunc(null);//↑↑↑这一部分比较特殊 根据需要判断是否要添加↑↑↑}
  • ProtoBuf.Serializer.Deserialize<T> protobuf自带的反序列化方法 将byte[]转为数据
    private static Func<Type, object> instanceFunc;public static void SetInstanceFunc(Func<Type, object> func){instanceFunc = func;}public static object CreateInstance(Type type){if (instanceFunc != null){object obj = instanceFunc(type);if (obj != null){return obj;}}// ReSharper disable once AssignNullToNotNullAttributeif (Type.GetType(type.FullName) == null){return _appDomain.Instantiate(type.FullName);}return Activator.CreateInstance(type
#if !(CF || SILVERLIGHT || WINRT || PORTABLE || NETSTANDARD1_3 || NETSTANDARD1_4), nonPublic: true
#endif);
  • 因为我们的项目涉及C#代码的热更新,所以存档管理器在热更新的程序集里,而protobuf因为其他内容的需要,是放在更深层的程序集里,而这个程序集是没有引用热更新的程序集的,所以在解包的时候,找不到热更新的程序集里的类。我只好在ProtoBuf.Support的类中添加了一个方法,在Support的CreateInstance方法中,把实例化的方法勾出来,解包的时候从热更新的程序集中实例化。

数据恢复

    public void Load(){TryDeserialize(out DataRoot data);if (data != null){int _sA = data.sA;string _sB = data.sB;float[] _sC = data.sC;DataA _sD = data.sD;List<DataA> _sE = data.sE;if (_sE != null){//TODO}Debug.Log("加载存档完成");}else{//TODO 没有存档,可以执行如注册或新手引导之类的方法}}
  • List和ArrayList,如果在存档时数据的长度为0,解包出来的数据是null,注意判断这个情况。

5.加密

AESCryptionUtility

这里提供一份使用AES加密解密的代码,支持string和byte[]的加密解密。加密解密时需要传入一个长度为32的字符串作为密钥。

    /// <summary>/// AES加解密字符串/// </summary>public static class AESCryptionUtility{/// <summary>/// AES加密 String/// </summary>/// <param name="str">被加密的明文</param>/// <param name="key">密钥</param>/// <returns>密文</returns>public static string Encrypt(string str, string key){MemoryStream mStream = new MemoryStream();RijndaelManaged aes = new RijndaelManaged();byte[] plainbytes = Encoding.UTF8.GetBytes(str);byte[] bKey = new byte[32];Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);aes.Mode = CipherMode.ECB;aes.Padding = PaddingMode.PKCS7;aes.KeySize = 128;//aes.Key = _key;aes.Key = bKey;//aes.IV = _iV;CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateEncryptor(), CryptoStreamMode.Write);try{cryptoStream.Write(plainbytes, 0, plainbytes.Length);cryptoStream.FlushFinalBlock();return Convert.ToBase64String(mStream.ToArray());}finally{cryptoStream.Close();mStream.Close();aes.Clear();}}/// <summary>/// AES加密 Bytes/// </summary>/// <param name="bytes">被加密的明文bytes</param>/// <param name="key">密钥</param>/// <returns>密文</returns>public static byte[] Encrypt(byte[] bytes, string key){MemoryStream mStream = new MemoryStream();RijndaelManaged aes = new RijndaelManaged();byte[] bKey = new byte[32];Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);aes.Mode = CipherMode.ECB;aes.Padding = PaddingMode.PKCS7;aes.KeySize = 128;//aes.Key = _key;aes.Key = bKey;//aes.IV = _iV;CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateEncryptor(), CryptoStreamMode.Write);try{cryptoStream.Write(bytes, 0, bytes.Length);cryptoStream.FlushFinalBlock();return mStream.ToArray();}finally{cryptoStream.Close();mStream.Close();aes.Clear();}}/// <summary>/// AES解密 String/// </summary>/// <param name="str">被加密的明文</param>/// <param name="key">密钥</param>/// <returns>明文</returns>public static string Decrypt(string str, string key){byte[] encryptedbytes = Convert.FromBase64String(str);byte[] bKey = new byte[32];Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);MemoryStream mStream = new MemoryStream(encryptedbytes);//mStream.Write( encryptedbytes, 0, encryptedbytes.Length );//mStream.Seek( 0, SeekOrigin.Begin );RijndaelManaged aes = new RijndaelManaged();aes.Mode = CipherMode.ECB;aes.Padding = PaddingMode.PKCS7;aes.KeySize = 128;aes.Key = bKey;//aes.IV = _iV;CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateDecryptor(), CryptoStreamMode.Read);try{byte[] tmp = new byte[encryptedbytes.Length + 32];int len = cryptoStream.Read(tmp, 0, encryptedbytes.Length + 32);byte[] ret = new byte[len];Array.Copy(tmp, 0, ret, 0, len);return Encoding.UTF8.GetString(ret);}finally{cryptoStream.Close();mStream.Close();aes.Clear();}}/// <summary>/// AES解密 Bytes/// </summary>/// <param name="bytes">被加密的明文bytes</param>/// <param name="key">密钥</param>/// <returns>明文</returns>public static byte[] Decrypt(byte[] bytes, string key){byte[] bKey = new byte[32];Array.Copy(Encoding.UTF8.GetBytes(key.PadRight(bKey.Length)), bKey, bKey.Length);MemoryStream mStream = new MemoryStream(bytes);//mStream.Write( encryptedbytes, 0, encryptedbytes.Length );//mStream.Seek( 0, SeekOrigin.Begin );RijndaelManaged aes = new RijndaelManaged();aes.Mode = CipherMode.ECB;aes.Padding = PaddingMode.PKCS7;aes.KeySize = 128;aes.Key = bKey;//aes.IV = _iV;CryptoStream cryptoStream = new CryptoStream(mStream, aes.CreateDecryptor(), CryptoStreamMode.Read);try{byte[] tmp = new byte[bytes.Length + 32];int len = cryptoStream.Read(tmp, 0, bytes.Length + 32);byte[] ret = new byte[len];Array.Copy(tmp, 0, ret, 0, len);return ret;}finally{cryptoStream.Close();mStream.Close();aes.Clear();}}}

相关参数

    private readonly string cryptionKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";private bool isCrtption;
  • cryptionKey AES密钥,长度为32位的随机字符
  • isCrtption 是否加密,作为存档Debug使用

存档加密

 //加密if (isCrtption){//将内容进行封包FileStream stream = File.OpenWrite(Application.persistentDataPath + "/tempS");Serializer.Serialize(stream, data);stream.Dispose();//加载文件BytesFileStream reader = new FileStream(Application.persistentDataPath + "/tempS", FileMode.Open, FileAccess.Read);byte[] fileBytes = new byte[reader.Length];reader.Read(fileBytes, 0, fileBytes.Length);reader.Dispose();//将内容加密fileBytes = AESCryptionUtility.Encrypt(fileBytes, cryptionKey);//保存存档File.Delete(Application.persistentDataPath + "/Save.dat");FileStream writer = new FileStream(Application.persistentDataPath + "/Save.dat", FileMode.OpenOrCreate, FileAccess.Write);writer.Write(fileBytes, 0, fileBytes.Length);writer.Dispose();//清理File.Delete(Application.persistentDataPath + "/tempS");}else{//将内容进行封包File.Delete(Application.persistentDataPath + "/Save.dat");FileStream stream = File.OpenWrite(Application.persistentDataPath + "/Save.dat");Serializer.Serialize(stream, data);stream.Dispose();}
  1. 先将数据序列化并存到一个临时文件中,以防止对同一个文件进行操作时的问题。
  2. 读取临时文件中的byte[]
  3. 对内容进行加密
  4. 将加密之后的byte[]保存到真正的存档文件中
  5. 删除临时文件

存档解密

//解密if (isCrtption){//加载文件BytesFileStream reader = new FileStream(Application.persistentDataPath + "/Save.dat", FileMode.Open, FileAccess.Read);byte[] fileBytes = new byte[reader.Length];reader.Read(fileBytes, 0, fileBytes.Length);reader.Dispose();//将内容解密fileBytes = AESCryptionUtility.Decrypt(fileBytes, cryptionKey);//写道临时文件中FileStream writer = new FileStream(Application.persistentDataPath + "/tempS", FileMode.OpenOrCreate, FileAccess.Write);writer.Write(fileBytes, 0, fileBytes.Length);writer.Dispose();//对内容进行解包FileStream stream = File.OpenRead(Application.persistentDataPath + "/tempS");data = Serializer.Deserialize<DataRoot>(stream);stream.Dispose();//清理File.Delete(Application.persistentDataPath + "/tempS");}else{//对内容进行解包FileStream stream = File.OpenRead(Application.persistentDataPath + "/Save.dat");data = Serializer.Deserialize<DataRoot>(stream);stream.Dispose();}
  1. 先读取已加密的byte[]
  2. 对内容进行解密
  3. 将解密之后的byte[]写入到临时文件中。因为如果没有再进行存档的话,要保证原存档的正确,所以不能把内容覆盖到原存档中。
  4. 对解密之后的byte[]进行反序列化为真正的数据
  5. 删除临时文件

【Unity】C#存档与加密相关推荐

  1. 文件存档、加密和解密

    第六章 文件存档.加密和解密 前一章中我们学习了如何处理文件.目录和数据.我们还学习了tarfile模块.本章中,我们将学习文件的存档.加密和解密.存档在管理文件.目录和数据中扮演重要的角色.但首先什 ...

  2. Unity游戏存档与读档

    目前unity常见存档和读档有几种方式,也就是常见的存储数据的方式(注意存档和读档都是针对单机游戏而言的,角色信息,道具信息,关卡情况等) Unity存档的方式大概分为这两大类 图片来源自siki学院 ...

  3. Unity游戏存档的四种方式

    [转载]http://blog.csdn.net/a237653639/article/details/50076755 游戏存档 在Unity中游戏存档有如下四种方式: PlayerPrefs c# ...

  4. unity 编辑器存档_Unity教程 | 自制简易的游戏存档系统

    原标题:Unity教程 | 自制简易的游戏存档系统 本文将为大家分享如何在Unity中实现简单的游戏存档系统,其中不会包含太多实际的代码,仅介绍在制作过程中需要考虑与解决的问题.该系统由一个学生团队为 ...

  5. Unity TileMap 存档 保存地图

    关注公众号,获取更多干货. 下面是正文: 最近在恶补Unity的一些模块,前几天朋友推荐我看一下Unity2017.2以上才支持的TileMap,他说用这个做2D关卡贼方便,所以我就去看了一眼,的确很 ...

  6. [Unity存档系统]简单介绍Unity常见存档系统二JSON以及使用方法

    学习目标: 如果你和我同样苦恼于游戏相关的数据怎么存储与读取,那么不妨看看这个up主有关Unity存档系统的教程.[Unity] 存档系统 Part 1 | PlayerPrefs | Unity初学 ...

  7. 如何对自己的Unity项目代码进行加密混淆?

    加密混淆的目的是让不怀好意者更难对你的项目进行逆向工程,我们可以使用Obfuscator 插件. 该插件的好处是与Unity构建过程无缝链接,源文件的内容保持不变,而混淆只针对于已编译的程序集. 一. ...

  8. Unity 游戏存档框架实现

    最近重构了一下我的存档框架.我在这里对实现方法进行简单的解析.注意这里主要演示算法,所以,效率上并不是最佳.一个游戏中,可能有成百上千个物体需要存储,而且有几十种类型,接下来就用一个简单的例子来解释. ...

  9. unity 游戏存档

    对于简单的存档,可以利用Playerpref永久保存,通过保存游戏角色的位置来实现,然后当点击继续游戏时通过start初始化得到保存的数据,重新开始游戏则不读取数据.大体思路如下:界面上有退出游戏按钮 ...

最新文章

  1. 基于Spark ML 聚类分析实战的KMeans
  2. 构造函数,析构函数,对象连的简单应用
  3. Linux快速复制T级数据或删除大量小文件
  4. Winform 导出成Excel打印代码
  5. 3 tables in management a company
  6. Pytho学习笔记:电子邮件1
  7. tf.placeholder函数的用法
  8. 计算机概论在线阅读,计算器概论 or 计算机概论
  9. 淘汰率最高的腾讯产品面试题
  10. 基础知识—循环语句-for
  11. C#判断联网状态检查电脑联网状态
  12. UVA 839 Not so Mobile 数据结构
  13. 使用迁移学习后使用微调再次提高模型训练的准确率
  14. 人工势场法--路径规划--原理--matlab代码
  15. 《别做正常的傻瓜》后续笔记—幸福的准则
  16. Survey of Aspect-based Sentiment Analysis Datasets
  17. 在Debian系统下使用自带的Fcitx配置中文输入法
  18. android 键盘将底部视图顶起,android 弹出软键盘将底部视图顶起问题
  19. 谈思生物直播课|辛格迪副总裁“细胞治疗数字化解决方案”
  20. LCD1602液晶显示屏应用

热门文章

  1. 计算机识别不了佳能打印机,佳能IP4200打印机“无法识别墨水盒”解决办法
  2. 数加平台在数据挖掘项目中的实践
  3. Python 800 道习题 (°ー°〃) 测试你学废了嘛
  4. [Java]观察者模式和中介者模式改造机场
  5. python - re
  6. MR详细运行原理及过程
  7. HQL 报 return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask 错误解决方案
  8. 廊坊通岭计算机学校校长,计算机科学与技术S021-S024班校友荣归母校为母校捐赠文化石...
  9. 前端笔记之移动端响应式(中)视口百分比布局弹性盒模型remfillpage
  10. 阿里巴巴牵头发起对雅虎的250亿美元并购