在許多游戲中當我們因為一些問題無法接着進行游玩,我們都會選擇保存,以便后面有空時,接着游玩。接下來,我們會學習一些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("存檔文件不存在"); } } }