Unity3D學習筆記(10)—— 遊戲序列化
這期內容有關遊戲的序列化,什麼是序列化呢?額...就是遊戲的內容可以輸出成文字格式,或者遊戲的內容可以從文字中解析獲得。現在的遊戲幾乎離不開序列化,只要有存檔機制的遊戲必然會序列化,並且遊戲的每次啟動都會讀取序列文字。另外遊戲的更新也和序列化緊密相關,比如LOL、DOTA,它們每次更新的都是資源而非程式,exe檔案是不會變的,它們能這麼做的資本是遊戲的高度序列化。
那麼就拿我之前寫的飛碟遊戲實現初步的序列化吧。負責任的的連結
實現序列化後,遊戲的版本資訊以及每個關卡的資訊都可以由 json 文字儲存在遊戲根目錄的 Data 資料夾下(這裡是為了方便測試,要釋出的話可不能用這個路徑):
啟動遊戲後,遊戲會自動讀取該目錄下的 json 檔案,並解析出遊戲資料:
程式碼解釋:
Unity有自帶的Json檔案處理類,因此選用Json作為序列文字格式。首先在Scripts資料夾下新建一個FileManager.cs檔案,專門負責處理文字讀寫。
FileManager主要有兩個工作:
1)讀取遊戲版本檔案並返回讀到的 json 字串。
2)在遊戲進行過程中讀取遊戲關卡檔案,同樣返回讀到的 json 字串。
一言不合就貼程式碼:
Application.dataPath是根目錄,另外注意協程是偽執行緒,不要犯多執行緒程式設計的一些錯誤,最好使用回撥的方式返回資料。using UnityEngine; using System.Collections; using Com.Mygame; public class FileManager : MonoBehaviour { public string url; SceneController scene = SceneController.getInstance(); void Awake() { scene.setFileManager(this); // 註冊到場景控制器 LoadGameInfoJson("game_info.json"); // 獲取遊戲版本等資訊 } // 輸入關卡檔名,啟動協程讀取檔案 public void loadLevelJson(string name) { url = "file://" + Application.dataPath + "/Data/" + name; StartCoroutine(LoadLevel()); } IEnumerator LoadLevel() { if (url.Length > 0) { WWW www = new WWW(url); yield return www; if (!string.IsNullOrEmpty(www.error)) Debug.Log(www.error); else scene.stageLevel(www.text.ToString()); // 返回json字串給scene } } // 輸入遊戲資訊檔名,啟動協程讀取檔案 public void LoadGameInfoJson(string name) { url = "file://" + Application.dataPath + "/Data/" + name; StartCoroutine(LoadGameInfo()); } IEnumerator LoadGameInfo() { if (url.Length > 0) { WWW www = new WWW(url); yield return www; if (!string.IsNullOrEmpty(www.error)) Debug.Log(www.error); else scene.stageGameInfo(www.text.ToString()); // 返回json字串給scene } } }
原先的關卡資料我都寫在了SceneControllerBC中(SceneControllerBC.cs):
public class SceneControllerBC : MonoBehaviour {
private Color color;
private Vector3 emitPos;
private Vector3 emitDir;
private float speed;
void Awake() {
SceneController.getInstance().setSceneControllerBC(this);
}
public void loadRoundData(int round) {
switch(round) {
case 1: // 第一關
color = Color.green;
emitPos = new Vector3(-2.5f, 0.2f, -5f);
emitDir = new Vector3(24.5f, 40.0f, 67f);
speed = 4;
SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);
break;
case 2: // 第二關
color = Color.red;
emitPos = new Vector3(2.5f, 0.2f, -5f);
emitDir = new Vector3(-24.5f, 35.0f, 67f);
speed = 4;
SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 2);
break;
}
}
}
現在既然使用了序列化,那麼這個類就沒什麼用了,清空(我可沒說刪掉啊):
public class SceneControllerBC : MonoBehaviour {
void Start()
{
}
}
在相同的腳本里(SceneControllerBC.cs)新增兩個類(純類):
[SerializeField]
public class GameInfo
{
public string version;
public int totalRound;
public static GameInfo CreateFromJSON(string json)
{
return JsonUtility.FromJson<GameInfo>(json);
}
}
[SerializeField]
public class LevelData
{
public string color;
public int emitNum;
public float emitPosX, emitPosY, emitPosZ;
public float emitDirX, emitDirY, emitDirZ;
public float speed;
public int round;
public static LevelData CreateFromJSON(string json)
{
return JsonUtility.FromJson<LevelData>(json);
}
}
我看了一些教程都是用[Serializable]表示類的可序列化,可是不知道是不是Unity版本原因,5.3.4只有[SerializeField]。通過在類的定義前使用[SerializeField],表示該類是要被序列化的,換句話說就是要和 json 等文字打交道的。(變數型別要能轉換成文字才行,Vector3、Color 什麼的應該是不行的...應該吧)
這裡GameInfo有兩個私有變數version和totalRound,分別記錄版本號和遊戲的關卡數。LevelData依次類推。
在這兩個類裡都有一個CreateFromJSON的方法,輸入為一條 json 字串。該方法使用了JsonUtility.FromJson<type>(jsonString)系統方法,返回的是解析 json 字串後生成的該類物件。明白了吧,類的私有變數和 json 的變數是一一對應的。
由於我的遊戲沒有什麼要記錄的,所以只需要能讀檔案就好了,如果要寫資料到 json 文字中,可以使用JsonUtility.ToJson(obj)。參考連結:API
為了顯示版本資訊以及儲存總關卡數,在sceneController中新增兩個私有變數來儲存:
private string _version;
private int _totalRound;
由於FileManager讀取了版本資訊後呼叫了scene.stageGameInfo,所以相應地在sceneController中新增該方法,接收 json 字串:
public void stageGameInfo(string json) {
GameInfo data = GameInfo.CreateFromJSON(json);
_version = data.version;
_totalRound = data.totalRound;
}
為了把版本資訊顯示在螢幕上,需要修改UserInterface.cs,新增新的 Text 和查詢介面函式,這裡就不細說了。
另外,遊戲的關卡是遊戲過程中讀取的,所以修改sceneController的 nextRound() 方法:
public void nextRound() {
_point = 0;
if (++_round > _totalRound) {
_round = 1; // 迴圈
}
string file = "disk_level_" + _round.ToString() + ".json";
_fileManager.loadLevelJson(file);
}
每次進入下一個關卡分數置零,然後判斷是否已經是最後一關,是則迴圈關卡。_fileManager是原先註冊到sceneController中的FileManager物件,呼叫loadLevelJson讀取關卡文字。
_fileManager讀取完關卡文字後會呼叫 scene.stageLevel(json) 返回 json 字串,所以要在sceneController中新增 stageLevel 方法接收json字串:
public void stageLevel(string json) {
LevelData data = LevelData.CreateFromJSON(json);
Color color;
if (!ColorUtility.TryParseHtmlString(data.color, out color)) {
color = Color.gray;
}
int emitNum = data.emitNum;
Vector3 emitPos = new Vector3(data.emitPosX, data.emitPosY, data.emitPosZ);
Vector3 emitDir = new Vector3(data.emitDirX, data.emitDirY, data.emitDirZ);
float speed = data.speed;
_gameModel.setting(1, color, emitPos, emitDir.normalized, speed, emitNum);
_judge.disksEachRound = emitNum;
_judge.round = data.round;
}
把讀到的字串轉換為例項物件,然後通過_gameModel的setting方法初始化關卡設定,下一次發射就是新的關卡了。另外裁判類也需要了解一些資訊,所以也會有一些賦值操作。
OK!到此飛碟遊戲的初步序列化已經完成了,以後想修改遊戲的關卡內容就只需要編輯Json文字就好了。
如果要更像主流遊戲那樣,可以與伺服器版本同步,那麼在每次遊戲啟動時不僅要讀取本地的遊戲版本,還要訪問遠端伺服器,讀取遠端伺服器上的遊戲版本,並作比較。讀取的方式同樣可以使用WWW類。如果版本不一樣,就提示使用者更新,然後下載遠端伺服器端的關卡文字到本地,文字通常儲存在 Application.PersistentData 路徑下。
另外,如果使用者需要儲存當前的遊戲進度,那麼可以使用 JsonUtility.ToJson(obj) 把使用者當前的遊戲狀態寫入文字。感覺這些讀寫操作其實都是大同小異的。