Unity中游戲的存檔與讀檔


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM