【游戏开发】用二进制形式安全存档

在Unity中存档机制实现的三种方式

在Unity中搭建一个存档机制,可以借助Unity自带的PlayerPrefs类,或者使用xml及json此类格式化文档。而除此之外,一种更安全、也并不复杂的方式是转化为二进制格式的文件进行存储。

二进制存档机制实现原理

此类方法主要借助一个被称为BinaryFormatter的二进制转换器,先将游戏内的玩家数据转换为二进制数据,再通过文件流写入存档文件。读档则进行相反的操作。

示例的实现过程

为了简化过程,我创建了一个最简单的示例,即只有一个整型游戏数值需要保存的情况。如果需要保存Vector3或Color等复杂的引用类型,需要将其拆为一个一维数组。

创建示例场景

在U3D中新建工程,添加一个Text文本框和三个按钮组件。文本框显示玩家数值。三个按钮组件中,其中两个是用于存档和读档的,另一个单击一次就能使文本框中的数值减一。
场景中新建一个名为Player的空物体,添加脚本Player.cs,代码如下:

Player
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
using UnityEngine.UI; //使用UI组件相关语句必用的

public class Player : MonoBehaviour
{
static public Player S; //单例模式,方便其他脚本查找此脚本的数据
public int score; //此次示例的玩家数据
public Text scoreRec; //对文本框组件的引用
// Start is called before the first frame update
void Start()
{
S = this;
score = 100;
}

// Update is called once per frame
void Update()
{
scoreRec.text = "" + score; //随着玩家数据改变,文本框数据也更新
}
}

脚本写完后,需要将文本框拖拽赋值给scoreRec变量。
再新建一个脚本Minus.cs,用于手动减小score的值,代码如下:

Minus
1
2
3
4
5
6
7
8
9
using UnityEngine;

public class Minus : MonoBehaviour
{
public void MinusOne()
{
Player.S.score -= 1;
}
}

将此脚本挂在Main Camera上(或者随便什么地方,只要你觉得方便操作就行),然后将其中的MinusOne函数赋给减一按钮的On Click。
此时运行游戏,文本框将显示100,每单击一次减一的按钮,数值就会变小。准备工作完成。

实现二进制文件存读档

现在新建一个脚本PlayerData.cs。
这个脚本是一个类,方便将所有玩家数据打包。我们的示例只有一个数据,所以无法体现这一点。不过我们还是新建它。代码如下:

PlayerData
1
2
3
4
5
6
7
8
9
[System.Serializable]   //可序列化,详细意义可以自行百度,相信你会有所收获
public class PlayerData
{
public int score;
public PlayerData(Player player) //构造函数,传入一个Player类,即我们之前写好的那个Player.cs的实例
{
score = player.score; //将Player类中的数据传给PlayerData
}
}

之后新建脚本SaveSystem.cs,和PlayerData类一样,它不需要挂在任何物体上,所以也不需要继承自MonoBehaviour。
在这个类中,我们完成新建用于存储数据的文档、打包玩家数据、使用formatter进行序列化的存档操作;以及读取数据存储文档、使用formatter反序列化、将玩家数据返回的读档操作。代码如下:

SaveSystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using UnityEngine;
using System.IO; //管理文件数据流
using System.Runtime.Serialization.Formatters.Binary; //二进制转换器

public static class SaveSystem
{
public static void SavePlayer(Player player) //存档函数
{
BinaryFormatter formatter = new BinaryFormatter(); //新建二进制转换器
string path = Application.persistentDataPath + "/player.pl"; //路径字符串,引号中是文件名,由于此文件是二进制文件,故后缀名可以随意
FileStream stream = new FileStream(path, FileMode.Create); //使用刚才的路径新建文件输入流

PlayerData data = new PlayerData(player); //将传入的Player类数据打包入PlayerData类

formatter.Serialize(stream, data); //序列化PlayerData类,并存入文件player.pl
stream.Close();
}

public static PlayerData LoadPlayer() //读档函数
{
string path = Application.persistentDataPath + "/player.pl"; //路径字符串,用于寻找数据存储文件
if (File.Exists(path)) //判断此文件是否存在,如果存在
{
BinaryFormatter formatter = new BinaryFormatter(); //新建二进制转换器
FileStream stream = new FileStream(path, FileMode.Open); //对路径path建立文件输出流

PlayerData data = formatter.Deserialize(stream) as PlayerData; //用二进制转换器对此输出流中的数据反序列化,并映射为PlayerData类实例
stream.Close();

return data; //返回类实例
}
else //如果文件不存在,报错
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}

现在要做的最后一步就是将SavePlayer函数和LoadPlayer函数赋给两个按钮,使我们可以通过点击来存档、读档。
首先在Player类中加入以下代码:

Player
1
2
3
4
5
6
7
8
9
public void SavePlayer()
{
SaveSystem.SavePlayer(this);
}
public void LoadPlayer()
{
PlayerData data = SaveSystem.LoadPlayer();
score = data.score;
}

之后将Player拖拽给存档和读档按钮的On Click,并分别选择SavePlayer和LoadPlayer函数。
完成,现在我们运行游戏,例如我们点击减一按钮使数值变成88,点击存档之后,不论是否退出过游戏,再点击读档,都可以使数值重新变成88。