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) 把用户当前的游戏状态写入文本。感觉这些读写操作其实都是大同小异的。


没有更多推荐了,返回首页