这期内容有关游戏的序列化,什么是序列化呢?额...就是游戏的内容可以输出成文本格式,或者游戏的内容可以从文本中解析获得。现在的游戏几乎离不开序列化,只要有存档机制的游戏必然会序列化,并且游戏的每次启动都会读取序列文本。另外游戏的更新也和序列化紧密相关,比如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) 把用户当前的游戏状态写入文本。感觉这些读写操作其实都是大同小异的。