【小松教你手游开发】【系统模块开发】unity 数据储存到本地为二进制文件(聊天记录本地储存)

unity游戏开发中有很多需要把数据储存到本地,官方有好几个方式可以使用,下面简单介绍一下。

一、Stream::Write,Stream::WriteLine

这个方法是打开数据流就开始写字符串,可以指定长度写,也可以一行一行的写。具体参考http://blog.csdn.net/dingxiaowei2013/article/details/19084859和雨松大神的http://www.xuanyusong.com/archives/1069

这种方法最简单,一行一行的写,一行一行的读,都已String的形式写下来,可以中午可以英文。

缺点是不是二进制文件,不好做数据截断获取,比如你想把一个数据包保存下来,中间的各种数据需要你在字符串中写标记符进行切分。

二、[System.Serializable]标记,BinaryFormatter或xml写入

把自己的数据类型标记成可序列化数据二进制文件,用BinaryFormatter来进行写入读取操。具体参考http://bbs.9ria.com/thread-417373-1-1.html

这种方法也比较简单,数据按数据结构存放,数据不用做解析直接按数据结构使用

缺点是只能存放一个数据,每次写入读取都是一个数据,并不适合大多数情况(有一种用哈希表的形式进行拓展,不好用不说据说在ios上还有问题)

三、自己做数据解析,BinaryWriter写入

可以定义一个数据结构,这个结构里面的确定这个数据结构里的每个变量的数据类型,如果有字符串还需要获取字符串长度放在字符串前面

也就是一般网络传输的方式。

我现在项目用的就是这个方法,具体我会在后面贴上代码。

这种方法比较麻烦,需要自己定义数据结构和确定好每个数据结构的变量数据类型,字符串需要计算长度,但是这种方法可以适合任何场景。并且数据不需要换行只管一直写入,

而且这种方面解决上面两个方式的缺点

这个方法还有一个缺点是如果数据结构改了需要改读取和写入数据的方法。

因为想着写入本地序列化不只是我一个需求,需要可拓展性。

跟进上面这些我对类结构进行了一些安排

下面是代码和解释

我写的是一个保存聊天记录的功能

数据结构基类(暂时里面为空。。。虽然没有东西但是总觉得以后需要所以建了个基类)

[csharp] view plain copy

  1. using UnityEngine;
  2. using System.Collections;
  3. public class SerializeDataBase
  4. {
  5. }

聊天数据的数据结构

[csharp] view plain copy

  1. using UnityEngine;
  2. using System.Collections;
  3. public class ChatSerializeData : SerializeDataBase
  4. {
  5. public long timeStamp;
  6. public int senderID;
  7. public int receiverID;
  8. //public short contentLength //这里有16位代表下面的字符串长度
  9. public string content;
  10. }

序列化的基类,写成单例

因为是io,所有写到一个线程里,用队列的存储,读写都在同一个线程中

[csharp] view plain copy

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Threading;
  5. public class SerializeClassBase<T, S> where T : SerializeDataBase where S : new()
  6. {
  7. public delegate void LocalDataReadDelegate(List<T> dataList);
  8. Queue<LocalDataReadDelegate> m_onReadDoneDelegateQueue = new Queue<LocalDataReadDelegate>();
  9. Thread m_thread;
  10. bool m_threadStart = true;
  11. Queue<T> m_writeThreadQueue = new Queue<T>();
  12. Queue<string> m_readThreadQueue = new Queue<string>();
  13. private static S m_instance;
  14. public static S Instance
  15. {
  16. get
  17. {
  18. if (m_instance == null)
  19. m_instance = new S();
  20. return m_instance;
  21. }
  22. }
  23. public SerializeClassBase()
  24. {
  25. m_thread = new Thread(ThreadAsync);
  26. m_thread.Start();
  27. }
  28. protected virtual void WriteFile(T data)
  29. {
  30. }
  31. protected virtual List<T> ReadFile(string arg)
  32. {
  33. return null;
  34. }
  35. public void SaveData(T data)
  36. {
  37. m_writeThreadQueue.Enqueue(data);
  38. }
  39. public void GetData(string arg,LocalDataReadDelegate del)
  40. {
  41. m_readThreadQueue.Enqueue(arg);
  42. m_onReadDoneDelegateQueue.Enqueue(del);
  43. }
  44. void ThreadAsync()
  45. {
  46. while (m_threadStart)
  47. {
  48. while(m_readThreadQueue.Count > 0)
  49. {
  50. m_onReadDoneDelegateQueue.Dequeue()(ReadFile(m_readThreadQueue.Dequeue()));
  51. }
  52. while (m_writeThreadQueue.Count > 0)
  53. {
  54. WriteFile(m_writeThreadQueue.Dequeue());
  55. }
  56. Thread.Sleep(1000);
  57. }
  58. }
  59. public void StopThread()
  60. {
  61. m_threadStart = false;
  62. }
  63. }

因为写入的时候通常不需要知道是否完成,但是读取的时候需要知道什么时候读取完成,

所有这里读取写的是通过回调的形式返回

聊天存储的类,二进制读写的子类

[csharp] view plain copy

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System;
  6. using System.Runtime.Serialization.Formatters.Binary;
  7. using System.Text;
  8. using System.Threading;
  9. public class ChatSerializeClass : SerializeClassBase<ChatSerializeData, ChatSerializeClass>
  10. {
  11. int MaximumFileCount = 5;
  12. string folderName = Application.persistentDataPath + "//" + "Chat";
  13. //Queue<string> m_localFileNameList = new Queue<string>();
  14. Dictionary<int, Queue<string>> m_localFileNameList = new Dictionary<int, Queue<string>>();
  15. public ChatSerializeClass()
  16. {
  17. if (!Directory.Exists(folderName))
  18. {
  19. Directory.CreateDirectory(folderName);
  20. }
  21. }
  22. protected override void WriteFile(ChatSerializeData data)
  23. {
  24. //按聊天的好友id创建文件夹
  25. int targetID;
  26. if (data.senderID != LoginManager.Instance.PlayerData.CharID)
  27. targetID = data.senderID;
  28. else
  29. targetID = data.receiverID;
  30. string folderNameWithID = folderName + "/" + targetID;
  31. if (!Directory.Exists(folderNameWithID))
  32. {
  33. Directory.CreateDirectory(folderNameWithID);
  34. }
  35. //储存文本
  36. if (data == null)
  37. return;
  38. string fileName = new DateTime(data.timeStamp).ToString("yyyyMMdd");
  39. FileStream fs = new FileStream(folderNameWithID + "/" + fileName, FileMode.Append);
  40. BinaryWriter bw = new BinaryWriter(fs);
  41. bw.Write(data.timeStamp);
  42. bw.Write(data.senderID);
  43. bw.Write(data.receiverID);
  44. bw.Write(Encoding.UTF8.GetByteCount(data.content));
  45. bw.Write(Encoding.UTF8.GetBytes(data.content));
  46. bw.Close();
  47. fs.Close();
  48. fs.Dispose();
  49. CDebug.Log("senderID:" + data.senderID + "--------");
  50. }
  51. protected override List<ChatSerializeData> ReadFile(string fileName)
  52. {
  53. List<ChatSerializeData> dataList = new List<ChatSerializeData>();
  54. FileStream fs = new FileStream(fileName, FileMode.Open);
  55. BinaryReader br = new BinaryReader(fs);
  56. try
  57. {
  58. while (true)
  59. {
  60. ChatSerializeData data = new ChatSerializeData();
  61. data.timeStamp = br.ReadInt64();
  62. data.senderID = br.ReadInt32();
  63. data.receiverID = br.ReadInt32();
  64. int contentLength = br.ReadInt32();
  65. data.content = Encoding.UTF8.GetString(br.ReadBytes(contentLength));
  66. dataList.Add(data);
  67. }
  68. }
  69. catch (Exception)
  70. {
  71. CDebug.Log("ReadFile:" + fileName + "----done!");
  72. }
  73. br.Close();
  74. fs.Close();
  75. fs.Dispose();
  76. return dataList;
  77. }
  78. /// <summary>
  79. /// 通过好友id找到聊天记录,一次获取一天的聊天信息
  80. /// </summary>
  81. /// <param name="id"></param>
  82. public void GetDataByID(int id, LocalDataReadDelegate del)
  83. {
  84. string folderNameWithID = folderName + "/" + id;
  85. //判读是否有该id的聊天数据
  86. if (!Directory.Exists(folderNameWithID))
  87. {
  88. if (del != null)
  89. del(null);
  90. return;
  91. }
  92. //判断是否已经读取该id的文件列表,读取并排序
  93. if(!m_localFileNameList.ContainsKey(id))
  94. {
  95. Queue<string> list = new Queue<string>();
  96. string[] fileList = Directory.GetFiles(folderNameWithID);
  97. for (int i = 0; i < fileList.Length; i++)
  98. {
  99. list.Enqueue(fileList[i]);
  100. }
  101. list.Sorted<string>();
  102. //如果超过5个删除
  103. if(list.Count > MaximumFileCount)
  104. {
  105. int count = list.Count;
  106. for (int i =0; i < count - MaximumFileCount; i++)
  107. {
  108. File.Delete(list.Dequeue());//写上删除逻辑
  109. }
  110. }
  111. list.Reversed<string>();
  112. m_localFileNameList.Add(id, list);
  113. }
  114. //读取文件
  115. if(m_localFileNameList[id].Count >0)
  116. GetData(m_localFileNameList[id].Dequeue(), del);
  117. }
  118. }

由于我的聊天读写功能再稍微复杂一点,需要按id按天数来保存,并且删除超过5天以上的聊天记录,所以实际逻辑并不需要这么多

实际上只需要继承并重写

[csharp] view plain copy

  1. </pre></p><p><pre name="code" class="csharp">    protected virtual void WriteFile(T data)
  2. {
  3. }
  4. protected virtual List<T> ReadFile(string arg)
  5. {
  6. return null;
  7. }

读写函数,因为这个涉及到你的数据应该怎么个读写法,你的数据规则,所以这个需要自己去重写。

使用的方法就是

ChatSerialieClass.Instance.SaveData();

ChatSerialieClass.Instance.GetData();

只有像我聊天那样比较复杂的需求才需要另外写接口再调用上面的函数。

这样就可以做到需要的需求和方便其他人拓展

时间: 2024-10-10 06:08:21

【小松教你手游开发】【系统模块开发】unity 数据储存到本地为二进制文件(聊天记录本地储存)的相关文章

【小松教你手游开发】【unity实用技能】角色头部跟随镜头旋转

这个在端游上比较场景,在角色展示的时候,当摄像头在角色身边上下左右旋转时,角色头部跟随镜头旋转.如天涯明月刀等. 这个在手游上比较少见,不过实现也没什么区别. 首先一般情况下,找到模型的头部节点,直接用lookAt指向camera就可以了,不过一般需求不会这么简单. 比如说,超过头部扭动极限,头部需要插值回到原始点:当镜头从外部回到极限内,需要插值回来.这时候lookat就没法使用. 更有情况,头部本身坐标系不在世界坐标轴上, 可能旋转了90多或者输出的prefab就是歪的等等,这些情况都没办法

【小松教你手游开发】【unity系统模块开发】热更

现在的手游项目如果没个热更新迭代根本跟不上, 特别是像我们项目做mmo的更是需要经常改动代码. 而现在的项目一般会选择用lua的方式实现热更新 不过我们项目由于历史原因没有使用,用的是另外一种方案 在项目里的所有GameObject都不挂脚本(NGUI脚本就通过代码的方式挂上),自己写的脚本都不继承Mono并打成dll,然后通过一个启动脚本去打开这些dll. 不过这样就有个问题,ios不能热更... 不管怎么样,先来讲讲这种方案要怎么做. 首先有两部分,一部分是打包,一部分是解包. 而包又分为资

【小松教你手游开发】【unity实用技能】根据上一个GameObject坐标生成的tips界面

开发游戏,特别是mmo手游的时候经常需要开发的一个需求是,点击某个装备,在它附近的位置生成一个tips界面,介绍装备功能和各种信息. 像上面红色框框里的这个. 这个主要的问题是 根据点击的GameObject对应生成这个详情界面时,详情界面位置需要合理摆放(不能显示不到,不能遮挡等) 基本的思路是, 首先找到GameObject的position, 把手机屏幕大概分成四个象限,知道这个GameObject大概在这个屏幕的哪个象限(左上,左下,右上,右下) 根据象限来判断详情界面应该在GameOb

【小松教你手游开发】【unity系统模块开发】Unity Assetbundle打包笔记

*最近项目更新了Unity5.5.2,顺便更新了项目的ui打包,也更新一下这边的笔记 首先打包分为两部分,一部分是打包成Assetbundle包,一部分是从Assetbundle包解包出来成为可用的资源. 首先说第一部分 打包 所有资源都可以打包,甚至不是资源(一些数据)也可以打包,只要你需要. 打包出来的东西都可以直接用,一个字体,一个Texture,一个Prefab,一个场景,都是一打出来成Assetbundle包就可以直接用,但是为什么大家还是要各自开发自己的打包流程呢? 最重要的原因就是

【小松教你手游开发】【系统模块开发】做一个3d旋转菜单

在unity做一个3d旋转菜单,像乱斗西游2的这种: 暂时有两种方法可以实现: 一.当做是2d界面,通过定义几个固定点的坐标.大小.透明度,还有每个点的panel depth大小,把数据存储下来,在手机滑动的过程中计算滑动划过的距离和这个panel大小的比值,乘以两个点之间的距离,获得坐标点移动的距离,通过改变x轴改变位置,同理改变大小和透明度. 这个方法我自己做2d游戏的时候实现过,做起来比较简单,没有什么可拓展性可言,并且会有很多限制,比如拖动过程中很难转变方向.要自己实现运动中的弹性(这里

【小松教你手游开发】【系统模块开发】ngui做聊天系统

用ngui做聊天系统有个简单的方法是用教程Exampl12里的TextList来做聊天系统. 但显然一个UILabel做的聊天系统拓展性不高,并且要做特殊点击事件会变得很麻烦. 所以我们还是用一个UIScrollView下挂一个UITable,把UILabel和其他东西封装成一个Prefab一个个加载到UITable来实现. 如果不考虑其他因素就是一个简单的UIScrollView相信大家都没什么问题. 不过项目中会有两个技术难点. 1.要实现图文混排 2.如果对话多的话(比如世界频道,每个玩家

【小松教你手游开发】【unity系统模块开发】unity网络层读写

在unity做强联网游戏的时候一般会选择用tcp来做通信(据说有一种udp的传输具有更高的效率),而接收信息的方法上一般会选择新建一个线程循环读取. 今天在我们项目上看到另外的一种方法.这里记录一下. 首先建立tcp连接 #using System.Net.Sockets; TcpClient tcpClient = new TcpClient(); tcpClient .BeginConnect(address,port,new AsyncCallback(this.OnConnect),re

【小松教你手游开发】【unity系统模块开发】Unity5.5.2UI打包AssetBundle

之前已经有几篇文章写打包AssetBundle,但毕竟没有实际在项目中写过都写的比较浅. 刚好最近项目更新Unity5.5.2就顺便由我来更新ui打包流程 这里就把这次的经验写一下 这里还是稍微解释一下打包的基本目的: 打包ui就是把你做的界面打包出来成assetbundle包,讲道理你就把每个界面打成bundle包在游戏中你就可以直接加载来用,但是这样子的话你的每个bundle包就会非常的大,为什么呢,是因为这样子每个界面的bundle包里都包含这个界面用到的字体,贴图atlas,textur

【小松教你手游开发】【unity实用技能】拓展函数(给系统代码添加可直接使用的接口)

拓展函数的意思是给一些没有源码的脚本添加上你自己写的接口并可以直接调用. using UnityEngine; using System.Collections; namespace ExtensionMethods { public static class MyExtensions { public static void SetLocalPositionX(this Transform transform, float x) { Vector3 newPosition = new Vect