前言
- 本内容为本人原创提供
- 本内特为学习者提供原创完整的工程源码下载,可参考学习或其他用途。
- 本内容从地图相关属性出发,对数据存取操作,一步一步推进,能直观感受。
- 如果后续有其他的存取游戏数据的方式,将继续提供给大家借鉴参考,希望能协助大家完成自己项目。
- 本文涉及地图属性的难度等级,消耗变量,地图元素坐标等数据的存取。
目录
一、什么是Binary存取
binary 是二进制,也就是0/1计数方法,是字节序列。
binary 存取是可用于存储非传统数据,如图像、音频和视频文件、程序可执行文件等。也可以用于与字符串类似的目的(例如,Word格式存储文档),这种特性也可以让我们可以用于储存游戏数据。
二、BinaryWriter和BinaryReader认识
1、BinaryWriter
是将值写入当前流。
Write(String) | 将有长度前缀的字符串按 BinaryWriter 的当前编码写入此流,并根据所使用的编码和写入流的特定字符,提升流的当前位置。 |
Write(Int64) | 将 8 字节带符号整数写入当前流,并将流的位置提升 8 个字节。 |
Write(Int32) | 将 4 字节带符号整数写入当前流,并将流的位置提升 4 个字节。 |
Write(Int16) | 将 2 字节无符号整数写入当前流,并将流的位置提升 2 个字节。 |
Write(Boolean) | 将单字节 Boolean 值写入当前流,其中 0 表示 false,1 表示 true。 |
Write(Byte[], Int32, Int32) | 将字节数组区域写入当前流。 |
Write(Char[]) | 将字符数组写入当前流,并根据所使用的 Encoding 和向流中写入的特定字符,提升流的当前位置。 |
... | ... |
内容还是比较多,不做具体罗列了,本文主要采用 Write(String),Write(Int32),保存地图属性数据。
2、BinaryReader
从基础流中读取字节,并将流的当前位置前移。
Read() | 从基础流中读取字符,并根据所使用的 Encoding 和从流中读取的特定字符,提升流的当前位置。 |
Read(Span<Byte>) | 从当前流读取字节序列,并将流中的位置向前移动读取的字节数。 |
Read(Span<Char>) | 从当前流中读取与提供的缓冲区长度相同的字符数,将其写入提供的缓冲区,然后根据所使用的 Encoding 和从流中读取的特定字符,将当前位置前移。 |
Read(Byte[], Int32, Int32) | 从字节数组中的指定点开始,从流中读取指定的字节数。 |
Read(Char[], Int32, Int32) | 从字符数组中的指定点开始,从流中读取指定的字符数。 |
本文主要采用 Read() 读取地图属性数据。
注:以上表格数据引用于微软文档。
三、unity实战
本次实战采用地图属性单元展开,对地图属性进行存档和读档操作。
1、c#对二进制数据存取操作的起始工作
1.1、地图属性:
首先,创建一个地图属性类,把地图内容属性规范好,当然也能创建地图属性的结构体。如果数据多,并且比较复杂,可以选择类:
public class Map
{
public int map_id;
public int map_lv;
public string map_name;
public int map_stone;
public int map_general;
public string map_bgstr;
public List<string> map_monsteridList;
public Dictionary<string, Vector3> map_objectDic;
...
}
类中的变量需要显示在 inspector 面板上,就不能用 get; set
1.2、构造Map函数
构造map函数,并给地图的参数带入其中
/// <summary>
/// 结构化
/// </summary>
/// <param name="id"></param>
/// <param name="monsterList"><怪物文本集合/param>
/// <param name="mapbossList">地图名文本集合</param>
/// <param name="mapnameList">需要生成的源obj</param>
/// <param name="map_objectmeshList">源物体</param>
/// <param name="meshFilter">物体的meshFilter</param>
public Map(int id, List<string> monsterList, List<string> mapbossList, List<string> mapnameList, List<GameObject> map_objectmeshList, MeshFilter meshFilter)
{
...
}
设置map属性
//数据设置
map_id = id;
map_lv = 1;
map_name = mapnameList[UnityEngine.Random.Range(0, mapnameList.Count)];
map_stone = UnityEngine.Random.Range(1, 10);
map_general = UnityEngine.Random.Range(0, mapbossList.Count);
map_bgstr = string.Empty;
从第一个参数,monsterList 中随机获取 count 个不重复元素(GetListRandomElements 方法本文后面查阅)
int count = UnityEngine.Random.Range(1, monsterList.Count - 3);
map_monsteridList = new List<string>(monsterList.GetListRandomElements(count));
从第二个参数,map_objectmeshList 中随机获取 6 个不重复元素(具体数量自己定)
int dicount = 6;
List<GameObject> map_objectmeshlist =
new List<GameObject>(map_objectmeshList.GetListRandomElements(dicount)) ;
获取mesh的顶点坐标集合,并将地表的 vertices 坐标转为世界坐标 ,坐标一定要转世界坐标,这样是为了放置地图元素,比如树木,石头等,需要利用世界坐标。
List<Vector3> vector3list = new List<Vector3>();
Mesh mesh = meshFilter.mesh;
Vector3[] vertices = new Vector3[mesh.vertices.Length];
for (int i = 0; i < mesh.vertices.Length; i++)
{
vertices[i] = meshFilter.transform.TransformPoint(mesh.vertices[i]);
}
从 vertices 中随机获取 dicount 个不重复元素,并根据物体的名字和坐标,添加到字典map_objectDic 中
vector3list = new List<Vector3>(vertices.ToList().GetListRandomElements(dicount));
map_objectDic = new Dictionary<string, Vector3>();
for (int i = 0; i < dicount; i++)
{
map_objectDic.Add(map_objectmeshlist[i].name, vector3list[i]);
}
整个map属性类就完毕了。
2、其他准备
2.1、添加引用
using System.IO;
using System.Linq;
System.IO,对数据流进行操作。
System.Linq,数据增,添,删,查等操作。
2.2、文件路径设置
private string binaryPath = Application.dataPath + "/Save/gameBinary.map";
文件格式定为 .map
2.3、创建游戏管理类
创建一个管理游戏的类,并且单例它
private static Gamemanager _instance;
public static Gamemanager Instance
{
get
{
if (_instance == null)
{
_instance = new Gamemanager();
}
return _instance;
}
}
3、保存数据
将地图数据以二进制形式进行保存
2.1、按路径取文件流
FileStream steam = File.Open(binaryPath, FileMode.Create);
2.2、二进制取流文件
//二进制取流文件
using (BinaryWriter header = new BinaryWriter(steam))
{
...
}
保存map类中的声明的所有变量数据
header.Write(Gamemanager.Instance.mapList.Count);
for (int i = 0; i < Gamemanager.Instance.mapList.Count; i++)
{
header.Write(Gamemanager.Instance.mapList[i].map_id);
header.Write(Gamemanager.Instance.mapList[i].map_lv);
header.Write(Gamemanager.Instance.mapList[i].map_name);
header.Write(Gamemanager.Instance.mapList[i].map_stone);
header.Write(Gamemanager.Instance.mapList[i].map_general);
header.Write(Gamemanager.Instance.mapList[i].map_bgstr);
header.Write(Gamemanager.Instance.mapList[i].map_monsteridList.Count);
for (int j = 0; j < Gamemanager.Instance.mapList[i].map_monsteridList.Count; j++)
{
header.Write(Gamemanager.Instance.mapList[i].map_monsteridList[j]);
}
header.Write(Gamemanager.Instance.mapList[i].map_objectDic.Count);
for (int j = 0; j < Gamemanager.Instance.mapList[i].map_objectDic.Count; j++)
{
KeyValuePair<string, Vector3> pair = Gamemanager.Instance.mapList[i].map_objectDic.ElementAt(j);
header.Write(pair.Key);
header.Write(pair.Value.ToString());
}
}
- 保存数组、字典(List,Array,Dictionary)数据,一定记得遍历前保存它们的 count 长度
- 字典 key 和 value 分别保存
- 保存数据一定要根据声明的变量,逐一保存
4、读取数据
FileStream steam = File.Open(binaryPath, FileMode.Open);
using (BinaryReader reader = new BinaryReader(steam, Encoding.UTF8))
{
int count = reader.ReadInt32();
for (int i = 0; i < count; i++)
{
Gamemanager.Instance.mapList[i].map_id = reader.ReadInt32();
Gamemanager.Instance.mapList[i].map_lv = reader.ReadInt32();
Gamemanager.Instance.mapList[i].map_name = reader.ReadString();
Gamemanager.Instance.mapList[i].map_stone = reader.ReadInt32();
Gamemanager.Instance.mapList[i].map_general = reader.ReadInt32();
Gamemanager.Instance.mapList[i].map_bgstr = reader.ReadString();
int count2 = reader.ReadInt32();
Gamemanager.Instance.mapList[i].map_monsteridList = new List<string>();
for (int j = 0; j < count2; j++)
{
Gamemanager.Instance.mapList[i].map_monsteridList.Add(reader.ReadString());
}
int count3 = reader.ReadInt32();
Gamemanager.Instance.mapList[i].map_objectDic = new Dictionary<string, Vector3>();
for (int j = 0; j < count3; j++)
{
string key = reader.ReadString();
string value = reader.ReadString();
//字符转坐标
string[] sarry = value.Replace("(", string.Empty).Replace(")", string.Empty).Split(',');
Vector3 v3 = new Vector3(float.Parse(sarry[0]), float.Parse(sarry[1]), float.Parse(sarry[2]));
//加入到字典中
Gamemanager.Instance.mapList[i].map_objectDic.Add(key, v3);
}
}
}
- 读取数组,字典时,优先读取它们的长度
- 读取的方法 ReadInt32 ,ReadString 不要搞错了,不然会报错
- 读取后,如果有特殊变量,比如坐标,记得字符转坐标,(string转Vector3)
5、删除数据
判断文件是否存在
//判断文件是否存在
public bool IsHaveBinaryDoc()
{
return File.Exists(binaryPath);
}
删除文件
//删除文件
public void DeleteBinaryDoc()
{
if (IsHaveBinaryDoc())
File.Delete(binaryPath);
}
四 、游戏中其他函数方法
4.1、初始化
初始化是游戏中经常需要的方法,帮我们准备一些进入游戏的初始数据和视觉展示,这些视觉包括,UI,模型,shader渲染等。
初始化游戏中的模型,创建模型数据集合
//从playerfab LIST 中生成地图实体
public void CreateMapObj()
{
map_objlist = new List<GameObject>();
for (int i = 0; i < map_objectmeshList.Count; i++)
{
GameObject obj = Instantiate(map_objectmeshList[i], mapObj.transform);
obj.name = obj.name.Replace("(Clone)", string.Empty);
map_objlist.Add(obj);
obj.SetActive(false);
}
map_objlist.GetListRandomElements(mapList.Count);
}
接着初始化
//初始化
public void InitGame()
{
mapattributeList = FindObjectsOfType<Mapattribute>().ToList();
mapattributeList.Sort((x, y) => x.mapMent.map_id.CompareTo(y.mapMent.map_id));
mapList = new List<Map>();
int tempId = 0;
foreach (var item in mapattributeList)
{
item.terrainMeshf.gameObject.SetActive(true);
item.mapMent = new Map(tempId, monsterList, mapbossList, mapnameList, map_objectmeshList, item.terrainMeshf.GetComponent<MeshFilter>());
item.terrainMeshf.gameObject.SetActive(false);
mapList.Add(item.mapMent);
tempId++;
}
chooseMap = mapList[chooseIndex];
CreateMapObj();
}
本文主要讲解二进制数据存取,这里就不对这些细讲,提供源码,可以自己下载看看,希望能给你学习提供帮助。
五、运行图示
六、总结
- 与 xml/jsong/excel 存取数据,二进制存取执行效率是其中最好。
- 二进制读写进制不方便直观查看游戏数据,也不利于临时修改存档中的数据,但是可以用二进制存取 复杂的,庞大的数据,比如地图数据。
- 如果需要保存数组和字典数据,必须先存它们的长度,读取的时候要先读取长度,否则报 错。
- 保存的顺序和读取的顺序必须保持一致,否则报错。
写着写着就这么多了,可能不是特别全,不介意费时就看看吧。有时间还会接着更新。