游戏中常常需要保存数据就是存档,unity已经提供一种非常方便的数据存储的方式:
PlayerPrefs:数据持久化方案 采用键值对的方式对数据进行存储,可以存储Int,Float,String类型的数据,这是unity提供用来存储简单的数据,如果需要存储复杂和大量数据的话一般通过序列化(Serialize)来保存数据
序列化(Serialize),可以用来将对象转化为字节流,
反序列化(Deserialize),可以用来将字节流转化为对象
常见的数据序列化方法:二进制方法,XML方法,JSON方法
三者之间对比:
- 二进制方法:简单,但可读性差
- XML方法:可读性强,但是文件庞大,冗余信息多
- JSON方法:数据格式比较简单,易于读写,但是不直观,可读性比XML差
实例:
比如飞行射击游戏《飞机大战》中需要保存玩家数据包括生命值,得分,技能数以及当前关卡敌机位置和类型,现在这些数据都要保存在本地, 就需要用到序列化存储这些数据,下面分别介绍二进制,XML和JSON三种方法实现游戏的存档和读档
首先写好一个存储游戏数据类,定义好需要保存的数据
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System;
/// <summary>
/// 存储游戏数据类
/// </summary>
[Serializable] //可序列化标志
public class SaveGameData{
public List<SerializableVector3> livingEnemyPostions = new List<SerializableVector3>();
public List<int> livingEnemyTypes = new List<int>();
public int score = 0;
public int hp = 0;
public int skillCount = 0;
}
/*
* 序列化在unity中的注意点
* 不可以直接序列化Unity特有的数据类型(例如Vector3, Quaternion),必须要转换一下
*/
//在Vector3 和 SerializableVector3之间自动转换
[Serializable]
public struct SerializableVector3
{
public float x;
public float y;
public float z;
//构造函数
public SerializableVector3(float rX, float rY, float rZ)
{
x = rX;
y = rY;
z = rZ;
}
// 以字符串形式返回,方便调试查看
public override string ToString()
{
return String.Format("[{0}, {1}, {2}]", x, y, z);
}
// 隐式转换:将SerializableVector3 转换成 Vector3
//implicit关键字属于转换运算符,表示隐式的类型转换,可以让我们自定义的类型支持相互交换。
public static implicit operator Vector3(SerializableVector3 rValue)
{
return new Vector3(rValue.x, rValue.y, rValue.z);
}
// 隐式转换:将Vector3 转成 SerializableVector3
public static implicit operator SerializableVector3(Vector3 rValue)
{
return new SerializableVector3(rValue.x, rValue.y, rValue.z);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO; //实现文件流所必需的库
using System.Runtime.Serialization.Formatters.Binary; //实现二进制串行化必需的库
using LitJson;
using System.Xml;
public enum GameState
{
playing,
gameOver,
pause
}
public class GameManager : MonoBehaviour {
public static GameManager Instance;
public GameObject gameoverPanel;
public GameObject saveMenu;
public Transform spawn;
//左上角显示分数
public Text txt_Score;
//游戏结束面板当前分数
public Text txt_CurScore;
//历史分数
public Text txt_HistoryScore;
//玩家当前生命值
public Text txt_CurHP;
public Text messageText;
public Transform pause;
private int score;
private GameObject player;
private string path;
private GameState gs;
public GameState GS
{
get { return gs; }
}
private void Awake()
{
Instance = this;
//清空对象池
BulletPool.ClearPool();
path = Application.dataPath + "/StreamingFile"; //路径为根目录自定义StreamingFile文件夹下
//path = Application.streamingAssetsPath; 路径为根目录StreamingAssets文件夹下
}
void Start () {
UpdateScore(0);
player = GameObject.FindGameObjectWithTag("Player");
gs = GameState.playing;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{//按下ESC键调出Menu菜单,并将游戏状态改为暂停
Pause();
}
}
/// <summary>
/// 切换游戏状态
/// </summary>
/// <param name="_gs"></param>
public void SetGameState(GameState _gs)
{
gs = _gs;
if (gs==GameState.gameOver)
{
Debug.Log("game over!");
}
}
private void Pause()
{
gs = GameState.pause;
Time.timeScale = 0;
saveMenu.SetActive(true);
pause.GetComponent<Image>().sprite = Resources.Load<Sprite>("ButtonIcon/game_resume");
}
private void UnPause()
{
gs = GameState.playing;
Time.timeScale = 1;
saveMenu.SetActive(false);
pause.GetComponent<Image>().sprite = Resources.Load<Sprite>("ButtonIcon/game_pause");
}
public void ContinueGame()
{
UnPause();
ShowMessage("");
}
/// <summary>
/// 点击按钮控制是否暂停游戏
/// </summary>
public void GamePause()
{
if (gs == GameState.playing)
{
Pause();
}
else
{
UnPause();
}
}
public void UpdateScore(int _score)
{
score += _score;
//更新当前得分
txt_Score.text = score.ToString();
}
public void UpdateHP(int hp)
{
txt_CurHP.text = hp.ToString();
}
//显示提示信息
public void ShowMessage(string str)
{
messageText.text = str;
}
void SaveDate(int historyScore)
{
//先取出历史数据
if (score > historyScore)
{//如果当前得分大于历史分数则覆盖历史最高分
PlayerPrefs.SetInt("score", score);
}
}
public void GameOver()
{
gs = GameState.gameOver;
//显示当前分数
txt_CurScore.text = score.ToString();
//取出历史最高分
int historyScore = PlayerPrefs.GetInt("score", 0);
//显示最高分
txt_HistoryScore.text = historyScore.ToString();
//每次游戏结束需要把最高分保存下来
SaveDate(historyScore);
gameoverPanel.SetActive(true);
}
/// <summary>
/// 重新开始游戏
/// </summary>
public void Restart()
{
UnityEngine.SceneManagement.SceneManager.LoadScene("01_play");
UnPause();
}
/// <summary>
/// 退出游戏
/// </summary>
public void Quit()
{
Application.Quit();
}
/// <summary>
/// 保存游戏
/// </summary>
public void SaveGame()
{
//SaveByBinary();
//SaveByJson();
SaveByXml();
}
/// <summary>
/// 加载游戏
/// </summary>
public void LoadGame()
{
//LoadByBinary();
//LoadByJson();
LoadByXml();
}
/// <summary>
/// 创建SaveGameData对象并存储当前游戏状态信息数据
/// </summary>
/// <returns></returns>
private SaveGameData CreateSaveObject()
{
SaveGameData save = new SaveGameData();
if (spawn.childCount > 0)
{
//如果有敌机就把敌机的位置信息和类型添加到List中
GameObject[] enemys = new GameObject[spawn.childCount];
//List<GameObject> enemys = new List<GameObject>();
for (int i = 0; i < spawn.childCount; i++)
{
//enemys.Add(spawn.GetChild(i).gameObject);
enemys[i] = spawn.GetChild(i).gameObject;
}
foreach (GameObject enemyGO in enemys)
{
EnemyControl enemyControl = enemyGO.GetComponent<EnemyControl>();
save.livingEnemyPostions.Add(enemyGO.transform.localPosition);
int type = enemyControl.enemyType;
save.livingEnemyTypes.Add(type);
}
}
//把score,hp和skillCount保存在save对象中
save.score = score;
save.hp = player.GetComponent<PlayerHealth>().curHP;
save.skillCount = player.GetComponent<PlayerControl>().skillCount;
return save;
}
/// <summary>
/// 通过读档信息将游戏重置为保存游戏时的状态
/// </summary>
/// <param name="save"></param>
private void SetGame(SaveGameData save)
{
//GameObject[] enemys = new GameObject[spawn.childCount];
//通过反序列化的得到的对象中存储的信息在指定位置生成对应类型的敌人
for (int i = 0; i < save.livingEnemyPostions.Count; i++)
{
Vector3 pos = save.livingEnemyPostions[i];
int type = save.livingEnemyTypes[i];
GameObject enemy = Instantiate(Resources.Load<GameObject>("prefabs/enemy" + type.ToString()), spawn);
enemy.transform.localPosition = pos;
}
//更新UI显示
score = save.score;
txt_Score.text = score.ToString();
player.GetComponent<PlayerHealth>().curHP = save.hp;
UpdateHP(player.GetComponent<PlayerHealth>().curHP);
player.GetComponent<PlayerControl>().skillCount = save.skillCount;
player.GetComponent<PlayerControl>().UpdateSkillCount();
UnPause();
}
//二进制方法:存档和读档
private void SaveByBinary()
{
//序列化过程——(将SaveGameData对象转化为字节流)
//创建SaveGameData对象并保存当前游戏状态信息
SaveGameData save = CreateSaveObject();
//创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
//创建一个文件流
FileStream fileStream = File.Create(path + "/savebyBin.txt");
//调用二进制格式化程序的序列化方法来序列化save对象 参数:创建的文件流和需要序列化的对象
bf.Serialize(fileStream, save);
//关闭流
fileStream.Close();
//如果文件存在,则显示保存成功
if(File.Exists(path+ "/savebyBin.txt"))
{
ShowMessage("保存成功");
}
}
private void LoadByBinary()
{
if(File.Exists(path + "/savebyBin.txt"))
{
//反序列化过程——(将字节流转化为对象)
//创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
//打开一个文件流
FileStream fileStream = File.Open(path + "/savebyBin.txt", FileMode.Open);
//调用二进制格式化程序的反序列化方法,将文件流转化为对象
SaveGameData save = (SaveGameData)bf.Deserialize(fileStream);
//关闭文件流
fileStream.Close();
SetGame(save);
ShowMessage("");
}
else
{
ShowMessage("存档文件不存在");
}
}
//Xml:存档和读档
private void SaveByXml()
{
SaveGameData save = CreateSaveObject();
//创建Xml文件的存储路径
string filePath = Application.dataPath + "/StreamingFile" + "/savebyXML.txt";
//创建XML文档实例
XmlDocument xmlDoc = new XmlDocument();
//创建root根节点,也就是最上一层节点
XmlElement root = xmlDoc.CreateElement("save");
//设置根节点中的值
root.SetAttribute("name", "saveFile");
//创建下一层XmlElement节点元素
//XmlElement enemy;
//XmlElement enemyPosition;
//XmlElement enemyType;
//遍历save中存储的数据,并将数据转换为XML格式
for (int i = 0; i < save.livingEnemyPostions.Count; i++)
{
//创建下一层XmlElement节点元素
XmlElement enemy = xmlDoc.CreateElement("enemy");
XmlElement enemyPosition = xmlDoc.CreateElement("enemyPosition");
//设置节点中的值
enemyPosition.InnerText = save.livingEnemyPostions[i].ToString();
XmlElement enemyType = xmlDoc.CreateElement("enemyType");
enemyType.InnerText = save.livingEnemyTypes[i].ToString();
//设置节点的层级关系(把节点一层一层的添加至XMLDoc中 ,注意先后顺序,这将是生成XML文件的顺序)
enemy.AppendChild(enemyPosition);
enemy.AppendChild(enemyType);
root.AppendChild(enemy);
}
XmlElement score = xmlDoc.CreateElement("score");
score.InnerText = save.score.ToString();
root.AppendChild(score);
XmlElement hp = xmlDoc.CreateElement("hp");
hp.InnerText = save.hp.ToString();
root.AppendChild(hp);
XmlElement skillCount = xmlDoc.CreateElement("skillCount");
skillCount.InnerText = save.skillCount.ToString();
root.AppendChild(skillCount);
xmlDoc.AppendChild(root);
//把XML文件保存至本地
xmlDoc.Save(filePath);
if(File.Exists(filePath))
{
ShowMessage("保存成功");
}
}
private void LoadByXml()
{
string filePath = Application.dataPath + "/StreamingFile" + "/savebyXML.txt";
if(File.Exists(filePath))
{
//加载XML文档
XmlDocument xmlDoc = new XmlDocument();
//根据路径将XML读取出来
xmlDoc.Load(filePath);
SaveGameData save = new SaveGameData();
//通过节点名称来获取元素,结果为XmlNodeList类型
//XmlNodeList enemylist = xmlDoc.GetElementsByTagName("enemy");
XmlNodeList enemylist = xmlDoc.SelectNodes("enemy");
//遍历所有子节点并获得子节点的InnerText值
if(enemylist.Count>0)
{
foreach (XmlNode enemy in enemylist)
{
XmlNode enemyPosition = enemy.ChildNodes[0];
string posStr = enemyPosition.InnerText;
//删除首尾的字符
posStr = posStr.TrimStart('[');
posStr = posStr.TrimEnd(']');
string[] strArray = posStr.Split(',');
Vector3 enemyPos;
enemyPos.x = float.Parse(strArray[0]);
enemyPos.y = float.Parse(strArray[1]);
enemyPos.z = float.Parse(strArray[2]);
//将的得到的位置信息存储到save中
save.livingEnemyPostions.Add(enemyPos);
XmlNode enemyType = enemy.ChildNodes[1];
int enemyTypeIndex = int.Parse(enemyType.InnerText);
save.livingEnemyTypes.Add(enemyTypeIndex);
}
}
//XmlNodeList scoreList = xmlDoc.GetElementsByTagName("score");
//int score= int.Parse(scoreList[0].InnerText);
//SelectSingleNode只能寻找单个子节点
XmlNode scoreNode = xmlDoc.SelectSingleNode("save").SelectSingleNode("score");
int score = int.Parse(scoreNode.InnerText);
save.score =score;
XmlNodeList hpList = xmlDoc.GetElementsByTagName("hp");
int hp = int.Parse(hpList[0].InnerText);
save.hp = hp;
XmlNodeList skillCountList = xmlDoc.GetElementsByTagName("skillCount");
int skillCount = int.Parse(skillCountList[0].InnerText);
save.skillCount = skillCount;
SetGame(save);
ShowMessage("");
}
else
{
ShowMessage("存档文件不存在");
}
}
//Json:存档和读档
private void SaveByJson()
{
//将save对象转换为Json格式的字符串
//创建SaveGameData对象并保存当前游戏状态信息
SaveGameData save = CreateSaveObject();
string filePath = Application.dataPath + "/StreamingFile" + "/savebyJson.json";
//方法1:利用第三方LitJson库中的JsonMapper将save对象转换为Json格式的字符串
//string saveJsonStr = JsonMapper.ToJson(save);
/*备注
* 注意::用LitJson调用JsonMapper在进行类转json字符串时,如果类的属性中有json不能识别的数据类型,例如float类型会报错JsonException:
*原因:用于序列化或者反序列化的数据,其类型必须是下面几种
LiteJosn的数据类型支持下面几种,否则会报错
public JsonData(bool boolean);
public JsonData(double number);
public JsonData(int number);
public JsonData(long number);
public JsonData(object obj);
public JsonData(string str);
*/
//方法2:利用系统自带JsonUtility将save对象转换为Json格式的字符串
string saveJsonStr = JsonUtility.ToJson(save);
//将字符串写入到文件中进行存储
//创建StreamWriter并将字符串写入
StreamWriter sw = new StreamWriter(filePath);
sw.Write(saveJsonStr);
//关闭StreamWriter
sw.Close();
ShowMessage("保存成功");
}
private void LoadByJson()
{
string filePath = Application.dataPath + "/StreamingFile" + "/savebyJson.json";
if (File.Exists(filePath))
{
//创建StreamReader用来读取流
StreamReader sr = new StreamReader(filePath);
//将读取到的流赋值给Json格式的字符串
string jsonStr = sr.ReadToEnd();
//关闭
sr.Close();
//将字符串jsonStr转换为SaveGameData对象(解析)
//方法1:利用第三方LitJson库中的JsonMapper解析
//SaveGameData save = JsonMapper.ToObject<SaveGameData>(jsonStr);
//方法2:利用系统自带JsonUtility解析
SaveGameData save =JsonUtility.FromJson<SaveGameData>(jsonStr);
SetGame(save);
ShowMessage("");
}
else
{
ShowMessage("存档文件不存在");
}
}
}