不同于unity自带的PlayerPrefs的字典式存储,本文主要说明三种文件存储的具体实现方式(项目原型基于siki案例–存档与读档,类似于打砖块的小游戏),即:二进制方法、XML以及JSON。
一、二进制方式存储:
存储简单,但基本没有可读性。
二、XML:
可读性强,但文件庞大,冗余信息多。
三、JSON:
数据格式比较简单,易于读写,但是不直观,可读性比XML差。
首先得创建一个Save类存储相关数据并标记为可序列化,本例具体代码如下:
(新建一个C#脚本,将继承类删除即可开始编写Save类,Save类用于存储需要存储的数据)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Save
{
public List<int> activeMonsterPosition = new List<int>();
public List<int> activeMonsterType = new List<int>();
public int shootNum;
public int score;
}
需要添加两个命名空间:
1、using System.IO; //用于文件的读写
2、using System.Runtime.Serialization.Formatters.Binary; //用于创建二进制格式化程序
private void SaveByBin()
{
//创建存储类对象
Save save = CreateSaveGo();
//创建二进制格式化程序
BinaryFormatter binaryFormatter = new BinaryFormatter();
//创建文件流(create参数为存储路径,注意路径前的"/"以及标明文件名称和格式)
FileStream fileStream = File.Create(Application.dataPath + "/StreamingFile" + "/byBin.txt");
//用二进制格式化程序的序列化方法来序列化save对象
binaryFormatter.Serialize(fileStream, save);
//关闭文件流
fileStream.Close();
}
private void LoadByBin()
{
if (File.Exists(Application.dataPath + "/StreamingFile" + "/byBin.txt")) //检测是否存在读档信息
{
//创建二进制格式化程序
BinaryFormatter binaryFormatter = new BinaryFormatter();
//创建文件流打开读档信息
FileStream fileStream = File.Open(Application.dataPath + "/StreamingFile" + "/byBin.txt", FileMode.Open);
//调用二进制格式化程序的反序列化方法,将文件流转化为Save对象
Save save = (Save)binaryFormatter.Deserialize(fileStream); //返回值为object类型,需要强制转换为需要的类型
//关闭文件流
fileStream.Close();
//将读取到的游戏属性设置为游戏当前属性(根据游戏需要自行编写)
SetGame(save);
}
}
需要添加两个命名空间:
1、using System.IO; //用于文件的读写
2、using LitJson;//用于json数据的转换(需要自行导入LitJson.dll文件)
private void SaveByJson()
{
//保存当前游戏状态,创建save对象(CreateSaveGo函数根据保存数据需要编写)
Save save = CreateSaveGo();
//保存文件路径(美化代码)
string filePath = Application.dataPath + "/StreamingFile" + "/byJson.json";//文件类型可以时.json也可以是.txt
//通过JsonMapper.ToJson方法将save对象中的数据转化为json字符串保存下来
string saveJsonStr = JsonMapper.ToJson(save);//using LitJson;
//创建StreamWriter写入流(注意需要一个string参数代表路径)
StreamWriter streamWriter = new StreamWriter(filePath);
//将转化后的字符串写入目标文件
streamWriter.Write(saveJsonStr);
//关闭StreamWriter
streamWriter.Close();
}
读档代码逻辑就是存档的反向逻辑。
private void LoadByJson()
{
//string保存路径(美化代码)
string filePath = Application.dataPath + "/StreamingFile" + "/byJson.json";
//创建文件读取流(注意new对象时需要string类型参数代表路径)
StreamReader streamReader = new StreamReader(filePath);
//读取文件中所有的字符串(ReadToEnd代表读取到最后,即读取所有)
string jsonStr = streamReader.ReadToEnd();
//将读取到的字符串通过JsonMapper.ToObject转换为Save类型的对象(JsonMapper.ToObject<类型>(路径);)
Save save = JsonMapper.ToObject<Save>(jsonStr);//using LitJson;
//设置游戏状态,根据游戏需要自行编写
SetGame(save);
}
需要添加两个命名空间:
1、using System.IO; //用于文件的读写
2、using System.Xml; //用于创建XML文档
xml虽然有系统自带的类库,但是所有的结点需要自己添加,相比其它两种方法要麻烦,但过程逻辑不复杂,了解自己编写的Save类即可轻松编写。
总体思路是 XmlDocument ( root ( target( …… ) – shootNum – score ) ) 【括号代表层级关系,"–"代表同级】
private void SaveByXml()
{
//保存当前游戏状态,创建save对象(CreateSaveGo函数根据保存数据需要编写)
Save save = CreateSaveGo();
//保存文件路径(美化代码)
string filePath = Application.dataPath + "/StreamingFile" + "/byXml.txt";
//总思路:XmlDocument ( root ( target( …… ) -- shootNum -- score ) )
//括号代表层级关系,"--"代表同级
//创建Xml文档
XmlDocument xmlDocument = new XmlDocument();
//创建xml根节点
XmlElement root = xmlDocument.CreateElement("save");
//设置根结点的值(名字的名称 ,名字)
root.SetAttribute("name", "saveFile1");
//创建xml元素(每个元素都包含名称 和 内部包含的值)
XmlElement target;
XmlElement targetPosition;
XmlElement monsterType;
//循环赋值
for (int i = 0; i < save.activeMonsterPosition.Count; i++)
{
//设置xml元素的名称
target = xmlDocument.CreateElement("target");
targetPosition = xmlDocument.CreateElement("targetPosition");
monsterType = xmlDocument.CreateElement("monsterType");
//设置xml元素内部包含的值(即存储的字符串)
targetPosition.InnerText = save.activeMonsterPosition[i].ToString();
monsterType.InnerText = save.activeMonsterType[i].ToString();
//设置层级关系(root -> target -> ( targetPosition -- monsterType ) )
root.AppendChild(target);
target.AppendChild(targetPosition);
target.AppendChild(monsterType);
}
//创建xml元素并设置名称
XmlElement shootNum = xmlDocument.CreateElement("shootNum");
XmlElement score = xmlDocument.CreateElement("score");
//设置xml元素内部包含的值(即存储的字符串)
shootNum.InnerText = save.shootNum.ToString();
score.InnerText = save.score.ToString();
//设置层级关系(xmlDocument -> root -> ( shootNum -- score ))
root.AppendChild(shootNum);
root.AppendChild(score);
xmlDocument.AppendChild(root);
//将xml文档保存到指定路径
xmlDocument.Save(filePath);
}
总体思路: XmlDocument -> XmlNodeList -> XmlNode ( -> XmlNode .ChildNodes[……]) -> InnerText
private void LoadByXml()
{
//保存文件路径(美化代码)
string filePath = Application.dataPath + "/StreamingFile" + "/byXml.txt";
//当保存文件存在时进行读档
if (File.Exists(filePath))
{
//创建Save保存类对象
Save save = new Save();
//创建xml文档
XmlDocument xmlDocument = new XmlDocument();
//加载指定路径的xml文档
xmlDocument.Load(filePath);
//通过结点名来获取结点,返回值为XmlNodeList类型(存储XmlNode的List)
XmlNodeList targets = xmlDocument.GetElementsByTagName("target");
//当存在target时进行遍历
if (targets.Count != 0)
{
//foreach遍历获得的list -- targets
foreach (XmlNode target in targets)
{
//获得list中的结点(对应位置参考存储时的位置)
XmlNode targetPosition = target.ChildNodes[0];
//直接获得该结点的innerText值(注意转化类型)
int targetPositionIndex = int.Parse(targetPosition.InnerText);
//给save对象赋值
save.activeMonsterPosition.Add(targetPositionIndex);
//获得list中的结点(对应位置参考存储时的位置)
XmlNode monsterType = target.ChildNodes[1];
//直接获得该结点的innerText值(注意转化类型)
int monsterTypeIndex = int.Parse(monsterType.InnerText);
//给save对象赋值
save.activeMonsterType.Add(monsterTypeIndex);
}
}
//通过结点名来获取结点,返回值为XmlNodeList类型(存储XmlNode的List)
XmlNodeList shootNumCount = xmlDocument.GetElementsByTagName("shootNum");
//注意当XmlNodeList中的XmlNode没有子节点的时候可以直接获得XmlNode的InnerText
int shootNum = int.Parse(shootNumCount[0].InnerText);
//给save对象赋值
save.shootNum = shootNum;
//通过结点名来获取结点,返回值为XmlNodeList类型(存储XmlNode的List)
XmlNodeList scoreCount = xmlDocument.GetElementsByTagName("score");
//注意当XmlNodeList中的XmlNode没有子节点的时候可以直接获得XmlNode的InnerText
int score = int.Parse(scoreCount[0].InnerText);
//给save对象赋值
save.score = score;
//将读取到的游戏属性设置为游戏当前属性(根据游戏需要自行编写)
SetGame(save);
}
}
private Save CreateSaveGo()
{
Save save = new Save();
//遍历每一个目前存活的目标,逐个保存
foreach (GameObject targetGo in targetGos)
{
TargetManager targetManager = targetGo.GetComponent<TargetManager>();
if (targetManager.activeMonster != null)
{
save.activeMonsterPosition.Add(targetManager.targetPosition);
int type = targetManager.activeMonster.GetComponent<MonsterManager>().monsterType;
save.activeMonsterType.Add(type);
}
}
//保存UI属性
save.shootNum = UIManager._instance.shootNum;
save.score = UIManager._instance.score;
return save;
}
private void SetGame(Save save)
{
foreach (GameObject target in targetGos)
{
target.GetComponent<TargetManager>().UpdateMonster();//先将所有怪物清空
}
for (int i = 0; i < save.activeMonsterPosition.Count; i++)
{
int position = save.activeMonsterPosition[i];
int type = save.activeMonsterType[i];
targetGos[position].GetComponent<TargetManager>().ActiveMonsterByType(type);//激活指定位置的怪物
}
//设置UI
UIManager._instance.shootNum = save.shootNum;
UIManager._instance.score = save.score;
DePause();//取消暂停状态、关闭菜单
}