导语
序列化就是把一个内存对象变为与地址无关的可传输的数据格式,通常是文本格式;反序列化反之。那么游戏的序列化对一款游戏有什么意义呢?众所周知,一款成熟的商业游戏更新的速度是很快的。一般一个月就会更新好几次。如果每一次游戏都更新代码,需要进行重编译成可执行文件的话。用户就要重新下载一次游戏重新安装,耗费大量时间。像英雄联盟这种游戏,一个游戏安装包就好几个G,如果需要这样子,必定流失大量用户。所以游戏的序列化就显得很有必要了,通过与服务器的文本数据进行比较,看是否有发生更新。若有更新的话会重新下载服务器的资源到本地,更新一些游戏参数。并不会更改整个可执行文件。通过比较本地的游戏版本数据和服务器的版本数据完成游戏数据的加载。
所以这次,我就尝试着把序列化加入之前自己写过的飞碟游戏中,用于游戏关卡信息的读取加载。由于Unity对json的支持比较好,所以这次就选用json来完成游戏序列化。
完成之后大致效果如下:
过程
先创建了一个存放游戏关卡数据和版本信息的本地资源文件夹Data,在下面有三个版本V1.0,V1.1,V1.2
添加可序列化的游戏版本和关卡类
原先的关卡信息都存放在了SceneControllerBaseCode.cs里面。现在我们可以清空SceneControllerBaseCode这个基础的类的内容,添加两个带有[SerializeField] 符号的类,用来对json数据进行序列化。在一个 monoBehavior 中,添加上述类,[Serializable]标签,说明这个类可以被序列化。一个用来存放关卡信息,一个用来存放版本信息。json文件的数据要与这个类的内容对应起来
1 | [ ] |
2 | public class GameInfo |
3 | { |
4 | public string version; |
5 | public int totalRound; |
6 | |
7 | public static GameInfo CreateFromJSON(string json) |
8 | { |
9 | return JsonUtility.FromJson<GameInfo>(json); |
10 | } |
11 | } |
12 | |
13 | [ ] |
14 | public class LevelData |
15 | { |
16 | public string color; |
17 | public int emitNum; |
18 | public float emitPosX, emitPosY, emitPosZ; |
19 | public float emitDirX, emitDirY, emitDirZ; |
20 | public float speed; |
21 | public int round; |
22 | |
23 | public static LevelData CreateFromJSON(string json) |
24 | { |
25 | return JsonUtility.FromJson<LevelData>(json); |
26 | } |
27 | } |
读取json文件的类FileManager
为了继续后面的步骤,我们还需要有一个类完成一些方法。负责文本读写。
1 | using UnityEngine; |
2 | using System.Collections; |
3 | using Com.Mygame; |
4 | |
5 | public class FileManager : MonoBehaviour { |
6 | public string url; |
7 | |
8 | SceneController scene = SceneController.getInstance(); |
9 | |
10 | void Awake() |
11 | { |
12 | scene.setFileManager(this); // 注册到场景控制器 |
13 | LoadGameInfoJson("game_info.json"); // 获取游戏版本等信息 |
14 | } |
15 | |
16 | // 输入关卡文件名,启动协程读取文件 |
17 | public void loadLevelJson(string name) |
18 | { |
19 | url = "file://" + Application.dataPath + "/Data/" + name; |
20 | StartCoroutine(LoadLevel()); |
21 | } |
22 | |
23 | IEnumerator LoadLevel() |
24 | { |
25 | if (url.Length > 0) |
26 | { |
27 | WWW www = new WWW(url); |
28 | yield return www; |
29 | if (!string.IsNullOrEmpty(www.error)) |
30 | Debug.Log(www.error); |
31 | else |
32 | scene.stageLevel(www.text.ToString()); // 返回json字符串给scene |
33 | } |
34 | } |
35 | |
36 | // 输入游戏信息文件名,启动协程读取文件 |
37 | public void LoadGameInfoJson(string name) |
38 | { |
39 | url = "file://" + Application.dataPath + "/Data/" + name; |
40 | StartCoroutine(LoadGameInfo()); |
41 | } |
42 | |
43 | IEnumerator LoadGameInfo() |
44 | { |
45 | if (url.Length > 0) |
46 | { |
47 | WWW www = new WWW(url); |
48 | yield return www; |
49 | if (!string.IsNullOrEmpty(www.error)) |
50 | Debug.Log(www.error); |
51 | else |
52 | scene.stageGameInfo(www.text.ToString()); // 返回json字符串给scene |
53 | } |
54 | } |
55 | } |
在场景控制器sceneController中还需要添加相应的filemanager注册函数
1 | private FileManager _fileManager; |
2 | public void setFileManager (FileManager fileManager) { |
3 | _fileManager = fileManager; |
4 | } |
注意记得将新建的这个脚本挂载在主摄像机上。
修改场景控制器接受json并序列化
为了显示版本信息以及保存总关卡数,在sceneController中添加两个私有变量来保存,以及在场景控制器中添加相应的接口函数,以便于后面在UserInterface中可以调用,以显示在界面上。
1 | private string _version; |
2 | private int _totalRound; |
3 | |
4 | // 修改接口 |
5 | public interface IQueryStatus { |
6 | bool isCounting(); |
7 | bool isShooting(); |
8 | int getRound(); |
9 | int getPoint(); |
10 | int getEmitTime(); |
11 | int getTotalRound (); |
12 | string getVersion (); |
13 | } |
14 | |
15 | public int getTotalRound () { |
16 | return _totalRound; |
17 | } |
18 | public string getVersion () { |
19 | return _version; |
20 | } |
另外,游戏的关卡是游戏过程中读取的,所以修改sceneController的 nextRound() 方法:
1 | public void nextRound() { |
2 | _point = 0; |
3 | if (++_round > _totalRound) { |
4 | _round = 1; // 循环 |
5 | } |
6 | string file = "disk_level_" + _round.ToString() + ".json"; |
7 | _fileManager.loadLevelJson(file); |
8 | } |
接下来,就是要在SceneController中补全stageLevel和stageGameInfo两个序列化json实例后转化为相应的游戏参数的方法了。
1 | public void stageLevel(string json) { |
2 | LevelData data = LevelData.CreateFromJSON(json); |
3 | |
4 | Color color; |
5 | if (!ColorUtility.TryParseHtmlString(data.color, out color)) { |
6 | color = Color.gray; |
7 | } |
8 | |
9 | int emitNum = data.emitNum; |
10 | Vector3 emitPos = new Vector3(data.emitPosX, data.emitPosY, data.emitPosZ); |
11 | Vector3 emitDir = new Vector3(data.emitDirX, data.emitDirY, data.emitDirZ); |
12 | float speed = data.speed; |
13 | |
14 | _gameModel.setting(1, color, emitPos, emitDir.normalized, speed, emitNum); |
15 | } |
16 | |
17 | public void stageGameInfo(string json) { |
18 | GameInfo info = GameInfo.CreateFromJSON(json); |
19 | |
20 | _version = info.version; |
21 | _totalRound = info.totalRound; |
22 | |
23 | } |
上述函数的总体思路就是:FileManager执行LoadGameInfoJson(“game_info.json”)在函数中读取指定位置文件的内容,以string的形式传给stageGameInfo,在场景控制器的stageGameInfo完成json对象的序列化。在游戏关卡执行下一关的函数nextRound()的时候会执行loadLevelJson,类似上面完成读取文件和在stageLevel中完成游戏关卡信息的序列化。后续的实现跟原本的游戏是类似的。
修改UserInterface 相应显示函数
为了让总局数和版本能够显示在界面上,需要对UserInterface这个类进行重新修改。
1 | // 在UserInterface 中添加文本变量versionText来显示版本号 |
2 | public Text versionText; |
3 | // 修改update函数中的roundText增添总局数 |
4 | roundText.text = " Round: " + queryInt.getRound ().ToString () + "/" + queryInt.getTotalRound().ToString(); |
5 | // 在update函数中添加,显示版本号 |
6 | versionText.text = "Version: " + queryInt.getVersion ().ToString (); |
当然,在上述设置之后,还需要在游戏的场景里新建一个Text,并将versionText选定为它,设置好它的位置来显示版本信息。
效果
在完成了上述步骤之后,游戏的序列化就完成了。以后可以通过更新json文件来更新游戏的版本和关卡信息了。在网络游戏中,还需要将本地的版本库和服务器的版本库进行比较。如果不同,经用户允许之后,要进行更新。