在许多游戏中当我们因为一些问题无法接着进行游玩,我们都会选择保存,以便后面有空时,接着游玩。接下来,我们会学习一些Unity有关的存储方法。
一:Unity-PlayerPrefs(数据持久化)
这是Unity自带的用于本地持久化保存与读取的类,采用的是键值对的方式来进行存储,一般通过键名来进行获取。PlayerPrefs有Int,float,string类型。
保存数据
PlayerPrefs.SetString(“Color”, color);
PlayerPrefs.SetInt(“Number”, number);
PlayerPrefs.SetFloat(“Class”, class);
读取数据
color = PlayerPrefs.GetString(“Color”, “red”);
number = PlayerPrefs.GetInt(“Number”, 20);
class = PlayerPrefs.GetFloat(“Class”, 1);
其他一些方法
PlayerPrefs.HasKey();//是否包含该键值对
PlayerPrefs.DeleteAll();//删除所有键值对
PlayerPrefs.DeleteKey();//删除某一个键值对
二:Unity-Serialization(序列化)和Deserialization(反序列化)
在Unity中我们也可以通过使用序列化来实现存储,我们将要存储的对象用序列化转化为字节流来进行存储,当我们需要用时我们通过反序列化将字节流转化为对象来进行读取。
常见的序列化方法:二进制,XML,Json.
这三种方法各有千秋,以下是对这三种方法对比
二进制方法:简单,但可读性差
XML方法:可读性强,但是文件庞大,冗余信息多
JSON方法:数据格式比较简单,易于读写,但是不直观,可读性比XML差
接下来我会讲解一些关于二进制的方法,关于XML以及Json方法我会放在下次来讲。
二进制序列方法(Binary Formatter)
序列化:新建或打开一个二进制文件,通过二进制格式器将对象写入该二进制文件。
反序列化:打开待反序列化的二进制文件,通过二进制格式器将文件解析成对象。
在进行存储之前我们要定义一个类用来存储我们需要保存的东西
实例:比如飞行射击游戏《飞机大战》中需要保存玩家数据包括生命值,得分,技能数以及当前关卡敌机位置和类型,现在这些数据都要保存在本地, 就需要用到序列化存储这些数据,下面分别介绍二进制,XML和JSON三种方法实现游戏的存档和读档首先写好一个存储游戏数据类,定义好需要保存的数据
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.Serialization; using System; /// <summary> /// 存储游戏数据类 /// </summary> [Serializable] //可序列化标志 public class SaveGameData{undefined 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 {undefined public float x; public float y; public float z; //构造函数 public SerializableVector3(float rX, float rY, float rZ) {undefined x = rX; y = rY; z = rZ; } // 以字符串形式返回,方便调试查看 public override string ToString() {undefined return String.Format("[{0}, {1}, {2}]", x, y, z); } // 隐式转换:将SerializableVector3 转换成 Vector3 //implicit关键字属于转换运算符,表示隐式的类型转换,可以让我们自定义的类型支持相互交换。 public static implicit operator Vector3(SerializableVector3 rValue) {undefined return new Vector3(rValue.x, rValue.y, rValue.z); } // 隐式转换:将Vector3 转成 SerializableVector3 public static implicit operator SerializableVector3(Vector3 rValue) {undefined 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 {undefined playing, gameOver, pause } public class GameManager : MonoBehaviour {undefined 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 {undefined get { return gs; } } private void Awake() {undefined Instance = this; //清空对象池 BulletPool.ClearPool(); path = Application.dataPath + "/StreamingFile"; //路径为根目录自定义StreamingFile文件夹下 //path = Application.streamingAssetsPath; 路径为根目录StreamingAssets文件夹下 } void Start () {undefined UpdateScore(0); player = GameObject.FindGameObjectWithTag("Player"); gs = GameState.playing; } private void Update() {undefined if (Input.GetKeyDown(KeyCode.Escape)) {//按下ESC键调出Menu菜单,并将游戏状态改为暂停 Pause(); } } /// <summary> /// 切换游戏状态 /// </summary> /// <param name="_gs"></param> public void SetGameState(GameState _gs) {undefined gs = _gs; if (gs==GameState.gameOver) {undefined Debug.Log("game over!"); } } private void Pause() {undefined gs = GameState.pause; Time.timeScale = 0; saveMenu.SetActive(true); pause.GetComponent<Image>().sprite = Resources.Load<Sprite>("ButtonIcon/game_resume"); } private void UnPause() {undefined gs = GameState.playing; Time.timeScale = 1; saveMenu.SetActive(false); pause.GetComponent<Image>().sprite = Resources.Load<Sprite>("ButtonIcon/game_pause"); } public void ContinueGame() {undefined UnPause(); ShowMessage(""); } /// <summary> /// 点击按钮控制是否暂停游戏 /// </summary> public void GamePause() {undefined if (gs == GameState.playing) {undefined Pause(); } else {undefined UnPause(); } } public void UpdateScore(int _score) {undefined score += _score; //更新当前得分 txt_Score.text = score.ToString(); } public void UpdateHP(int hp) {undefined txt_CurHP.text = hp.ToString(); } //显示提示信息 public void ShowMessage(string str) {undefined messageText.text = str; } void SaveDate(int historyScore) {undefined //先取出历史数据 if (score > historyScore) {//如果当前得分大于历史分数则覆盖历史最高分 PlayerPrefs.SetInt("score", score); } } public void GameOver() {undefined 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() {undefined UnityEngine.SceneManagement.SceneManager.LoadScene("01_play"); UnPause(); } /// <summary> /// 退出游戏 /// </summary> public void Quit() {undefined Application.Quit(); } /// <summary> /// 保存游戏 /// </summary> public void SaveGame() {undefined //SaveByBinary(); //SaveByJson(); SaveByXml(); } /// <summary> /// 加载游戏 /// </summary> public void LoadGame() {undefined //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) {undefined //GameObject[] enemys = new GameObject[spawn.childCount]; //通过反序列化的得到的对象中存储的信息在指定位置生成对应类型的敌人 for (int i = 0; i < save.livingEnemyPostions.Count; i++) {undefined 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() {undefined //序列化过程——(将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")) {undefined ShowMessage("保存成功"); } } private void LoadByBinary() {undefined if(File.Exists(path + "/savebyBin.txt")) {undefined //反序列化过程——(将字节流转化为对象) //创建一个二进制格式化程序 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 {undefined ShowMessage("存档文件不存在"); } } //Xml:存档和读档 private void SaveByXml() {undefined 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++) {undefined //创建下一层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)) {undefined ShowMessage("保存成功"); } } private void LoadByXml() {undefined string filePath = Application.dataPath + "/StreamingFile" + "/savebyXML.txt"; if(File.Exists(filePath)) {undefined //加载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) {undefined foreach (XmlNode enemy in enemylist) {undefined 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 {undefined ShowMessage("存档文件不存在"); } } //Json:存档和读档 private void SaveByJson() {undefined //将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() {undefined string filePath = Application.dataPath + "/StreamingFile" + "/savebyJson.json"; if (File.Exists(filePath)) {undefined //创建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 {undefined ShowMessage("存档文件不存在"); } } }