1. 程式人生 > >Unity3D學習筆記(10)—— 遊戲序列化

Unity3D學習筆記(10)—— 遊戲序列化

        這期內容有關遊戲的序列化,什麼是序列化呢疑問?額...就是遊戲的內容可以輸出成文字格式,或者遊戲的內容可以從文字中解析獲得。現在的遊戲幾乎離不開序列化,只要有存檔機制的遊戲必然會序列化,並且遊戲的每次啟動都會讀取序列文字。另外遊戲的更新也和序列化緊密相關,比如LOL、DOTA,它們每次更新的都是資源而非程式,exe檔案是不會變的,它們能這麼做的資本是遊戲的高度序列化。

        那麼就拿我之前寫的飛碟遊戲實現初步的序列化吧。負責任的的連結 微笑

        實現序列化後,遊戲的版本資訊以及每個關卡的資訊都可以由 json 文字儲存在遊戲根目錄的 Data 資料夾下(這裡是為了方便測試,要釋出的話可不能用這個路徑):

        

        

        啟動遊戲後,遊戲會自動讀取該目錄下的 json 檔案,並解析出遊戲資料:

        

程式碼解釋:

        Unity有自帶的Json檔案處理類,因此選用Json作為序列文字格式。首先在Scripts資料夾下新建一個FileManager.cs檔案,專門負責處理文字讀寫。

        

        FileManager主要有兩個工作:

        1)讀取遊戲版本檔案並返回讀到的 json 字串。

        2)在遊戲進行過程中讀取遊戲關卡檔案,同樣返回讀到的 json 字串。

        一言不合就貼程式碼:

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
        }
    }
}
        Application.dataPath是根目錄,另外注意協程是偽執行緒,不要犯多執行緒程式設計的一些錯誤,最好使用回撥的方式返回資料。

        原先的關卡資料我都寫在了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) 把使用者當前的遊戲狀態寫入文字。感覺這些讀寫操作其實都是大同小異的。