前言
存档作为游戏开发中,必不可少的一环,毕竟要求玩家一次性通关游戏太难了。
本章主要介绍在Unity中如何实现游戏的存档、读档,有若干实现方法,可以通过Unity自带的PlayerPreb,亦或者通过JSON、xml存储。
对比
- PlayerPreb:Unity自带的键值对持久化存储方法,用于存储一些简单的数据。
- XML:可读性强,但是文件内容会很庞大,文件格式复杂。
- JSON:数据格式比较简单,易于读写,但是不直观,可读性比XML差。
准备
存档类
准备一个游戏存档类,存档类只有几个关键属性(实际内容结合需要),注意必须加上[System.Serializable]用于序列化
[System.Serializable]
public class Save
{
public int currentHealth; //当前生命值
public int maxHealth; //最大生命值
public int curBulletCount; //当前子弹数量
public Vector2 position; //人物位置
public Save(int currentHealth, int maxHealth, int curBulletCount, Vector2 position)
{
this.currentHealth = currentHealth;
this.maxHealth = maxHealth;
this.curBulletCount = curBulletCount;
this.position = position;
}
public Save()
{
}
public override string ToString()
{
return "最大生命值:"+ maxHealth + ",当前生命值:"+currentHealth;
}
}
其他说明
文章中提及的以下两个方法根据自己的实际需要实现
-
CreaseSave():快捷创建存档类,将需要的属性存储到Save对象,便于存储到json或者xml
-
LoadSave(Save save):读取save对象信息,应用到游戏去
PlayerPrefs
PlayerPrefs是Unity提供的本地持久化保存与读取的类,通过键值对的形式存储。 建议通过该类存储一些简单的数据,比如全局bgm是否开启等,不建议将复杂的存档信息通过 PlayerPrefs 存储
PlayerPrefs.SetString("Name",value); //储存string类变量
PlayerPrefs.SetFloat("money",value); //储存float类变量
PlayerPrefs.SetInt("age",value); //储存int类变量
PlayerPrefs.GetString("Name"); //读取string类变量
PlayerPrefs.GetFloat("money"); //读取float类变量
PlayerPrefs.GetInt("age"); //读取int类变量
PlayerPrefs.DeleteKey (key : string) //删除指定数据;
PlayerPrefs.DeleteAll() //删除全部键 ;
PlayerPrefs.HasKey (key : string) //判断数据是否存在的方法;
存储位置
在windows下,playerPrefs被存储在注册表的HKEY_CURRENT_USER\Software\Unity\UnityEditor\公司名\项目名 下,这里公司名和项目名是在project setting中设置的
- 例子:
XML
通过xml格式来保存存档,可读性强,文件格式复杂,需要花费更多代码来解析xml
这里提供了两种方式来操作xml,一种是手动创建结点(代码较复杂,但是更灵活),一种是通过序列化(代码少,简约)
保存存档
手动创建结点
public void SaveGameByXml1()
{
XmlDocument xmlDoc = new XmlDocument();
//继续判断当前路径下是否有该文件,如果存在就加载(直接清空文件内容)
if (File.Exists(filePath))
{
xmlDoc.Load(filePath);
xmlDoc.RemoveAll();//清空文件结点
}
//创建root节点,也就是最上一层节点
XmlElement root = xmlDoc.CreateElement("player");
root.SetAttribute("name", "save_1");
//记录玩家属性
XmlElement currentHealth = xmlDoc.CreateElement("currentHealth");//当前生命值
currentHealth.InnerText = player.MyCurrentHealth+ "";
XmlElement maxHealth = xmlDoc.CreateElement("maxHealth");//最大生命值
maxHealth.InnerText = player.MyMaxHealth+"";
XmlElement curBulletCount = xmlDoc.CreateElement("curBulletCount");//子弹
curBulletCount.InnerText = player.curBulletCount+"";
XmlElement position = xmlDoc.CreateElement("position"); //当前位置
XmlElement positionX = xmlDoc.CreateElement("positionX");
positionX.InnerText = player.transform.position.x + "";
XmlElement positionY = xmlDoc.CreateElement("positionY");
positionY.InnerText = player.transform.position.y + "";
position.AppendChild(positionX);
position.AppendChild(positionY);
//把节点一层一层的添加至XMLDoc中 ,请仔细看它们之间的先后顺序,这将是生成XML文件的顺序
root.AppendChild(currentHealth);
root.AppendChild(maxHealth);
root.AppendChild(curBulletCount);
root.AppendChild(position);
xmlDoc.AppendChild(root);
//把XML文件保存至本地
xmlDoc.Save(filePath);
}
最终保存结构如下
序列化
public void SaveGameByXml2()
{
FileStream fileStream = new FileStream(filePath, FileMode.Create);
StreamWriter sw = new StreamWriter(fileStream, new System.Text.UTF8Encoding(false));//编码utf8格式
XmlSerializer x = new XmlSerializer(typeof(Save));
Save save = CreateSave(); //创建存档
x.Serialize(sw, save);
sw.Close();
fileStream.Close();
Debug.Log(save.ToString());
}
最终保存结构如下
读取存档
读取存档也对照着保存,有两个对应方式
手动一个个读取结点
public void LoadGameByXml1()
{
if (File.Exists(filePath))
{
XmlDocument xmlDoc = new XmlDocument();
//根据路径将XML读取出来
xmlDoc.Load(filePath);
XmlElement playerXml = (XmlElement)xmlDoc.SelectSingleNode("player");
XmlNode currentHealth = playerXml.SelectSingleNode("currentHealth");
XmlNode maxHealth = playerXml.SelectSingleNode("maxHealth");
XmlNode curBulletCount = playerXml.SelectSingleNode("curBulletCount");
XmlNode position = playerXml.SelectSingleNode("position");
XmlNode positionX = position.SelectSingleNode("positionX");
XmlNode positionY = position.SelectSingleNode("positionY");
Vector2 temp = new Vector2(float.Parse( positionX.InnerText), float.Parse(positionY.InnerText));
Save save = new Save(int.Parse( currentHealth.InnerText), int.Parse(maxHealth.InnerText), int.Parse(curBulletCount.InnerText), temp);
Debug.Log(save.ToString());
player.MyCurrentHealth = int.Parse(currentHealth.InnerText);
player.MyMaxHealth = int.Parse(maxHealth.InnerText);
player.curBulletCount = int.Parse(curBulletCount.InnerText);
player.transform.position = temp;
TipManager.instance.ShowTip("读取成功");
MenuManager.instance.ResumeGame();//继续游戏
}
else
{
TipManager.instance.ShowTip("没存档啊,读个锤子");
}
}
反序列化
public void LoadGameByXml2()
{
if (File.Exists(filePath))
{
FileStream fileStream = new FileStream(filePath, FileMode.Open,FileAccess.Read);
StreamReader sr = new StreamReader(fileStream, true);//跳过xml文件开通两个字节BOM
XmlSerializer x = new XmlSerializer(typeof(Save));
Save save = new Save();
save = (Save)x.Deserialize(sr);
sr.Close();
fileStream.Close();
Debug.Log(save.ToString());
LoadSave(save);//加载存档
TipManager.instance.ShowTip("读取成功");
MenuManager.instance.ResumeGame();//继续游戏
}
else
{
TipManager.instance.ShowTip("没存档啊,读个锤子");
}
}
JSON
通过JSON格式来保存存档,具有一定的可读性,数据格式简单,易于读写
保存存档
public void SaveByJson()
{
print("开始存档,存档位置:"+ Application.dataPath + "/Save/DataByJson.txt");
Save save = CreateSave();//创建存档类对象
print("转化前:" + save.ToString());
//将对象save转化为json字符串,注意,只有Save里面为public才会被序列化到,private不会
string jsonString = JsonUtility.ToJson(save);
print("转化化后:"+ jsonString);
//上面说了Json是string类型,所以命名string
StreamWriter sw = new StreamWriter(Application.dataPath + "/Save/DataByJson.txt");
sw.Write(jsonString); //将json字符串写入流参数
sw.Close();
}
读取存档
public void LoadByJson()
{
if (File.Exists(Application.dataPath + "/Save/DataByJson.txt"))
//判断文件是否创建
{
StreamReader sr = new StreamReader(Application.dataPath + "/Save/DataByJson.txt");
//从流中读取字符串
string jsonString = sr.ReadToEnd();
sr.Close();
//转化string为Save对象
Save save = JsonUtility.FromJson<Save>(jsonString);
//TODO 读取save对象
}
else
{
print("没存档啊,读个锤子");
}
}
存档位置
存档查看
结束语
关于Unity的存档、读档方法就介绍到这里了,在开发中还需要结合实际,使用合适的方法来做,还有一种二进制存储方法,本文章未介绍。