/
C# 中的BinaryFormatter类执行二进制数据的“序列化”和“反序列化”操作。它采用简单的数据结构,例如整数 ( int )、十进制数 ( float ) 以及字母和数字的集合 ( string ),并且可以将它们转换为二进制格式。这意味着更复杂的结构(如类)可以将其字段编码为二进制格式,以便保存到文件中,然后由程序读取。在 Unity 中,通过使用这个 C# 类,可以保存数据,例如可以使用PlayerPrefs完成,但在更复杂的级别上。
使用BinaryFormatter不是为游戏创建保存文件的插入式解决方案。该类可以将一些 C# 数据结构编码为序列化格式并再次读取它们。这意味着其他用户、程序和系统可以“看到”这种编码形式的数据。BinaryFormatter 不会以任何方式加密或隐藏数据,只是将一种形式转换为另一种形式,以便于读取和写入文件。始终验证从其使用中接受的所有数据。
使用FileStream s
处理文件时,无法知道文件是在离用户几英寸远的本地硬盘上、在另一个房间的硬盘上,还是在数千英里外的云存储中。为了解决这个问题,C# 提供了一个名为FileStream的类。它读取或写入文件并让操作系统处理进程的低级管理。对于开发人员来说,它提供了一种在不知道物理位置的情况下访问文件的方法,并使用“流”(无论是本地连接还是远程连接)来处理文件。
注意:FileStream是System.IO命名空间的一部分,它是与处理输入(“I”)和输出(“O”)相关的类和方法的集合。在 Unity 中创建新的脚本组件时,需要添加命名空间。
Unity 中的文件在哪里?
为了创建新的FileStream,需要文件的路径。然而,在使用 Unity 时,开发人员可能不知道玩家的硬盘驱动器或存储系统的结构。为了帮助解决这个问题,Unity 提供了Application类的静态字段,称为persistentDataPath。值Application.persistentDataPath将始终包含一个“持久”路径,用于在 Unity 中处理文件,无论是在使用编辑器时,还是在使用其 Player 运行项目时。
由于Application.persistentDataPath值指向一个路径,这意味着需要一个额外的细节,即文件。“数据路径”是目录,而不是文件。
文件示例
<span style="background-color:#eeeeee"><span style="color:#111111"><code> Application.persistentDataPath + "/gamedata.data"</code></span></span>
注意:此示例选择“数据”文件类型,因为它不是其他程序使用的已知格式。
这个游戏数据文件存在吗?
考虑到用户可以在会话之间更改或删除文件,工作文件的第一步是检查它是否存在。File类提供了Exists()方法来检查特定文件是否存在。使用Application.persistentDataPath时,这是一个很好的第一步,可以在尝试读取文件之前确定文件是否存在(这将失败),或者是否需要第一次创建文件。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>// Save the full path to the file.
string saveFile = Application.persistentDataPath + "/gamedata.data";
// Does it exist?
if(File.Exists(saveFile))
{
// File exists!
}
else
{
// File does not exist.
//
// This could mean it was deleted or has not been created yet.
}</code></span></span>
创建游戏数据
如果文件不存在,这可能意味着它已被删除或未被创建。无论哪种情况,这都意味着需要创建它。当创建一个新的FileStream时,它的构造函数需要知道两件事:文件的位置和它应该在什么“模式”下工作。通过使用Application.persistentDataPath,文件的位置是已知的。FileStream的模式决定了它应该如何工作。它是在创建文件吗?读取文件?附加到文件?
创建文件时,模式为Create。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>// File does not exist.
//
// This could mean it was deleted or have been created yet.
// Create a FileStream connected to the saveFile path and set the file mode to "Create".
FileStream dataStream = new FileStream(saveFile, FileMode.Create);</code></span></span>
读取游戏数据
就像使用“Create”的FileMode创建文件的过程一样,“Open”的模式打开文件以读取其数据的模式。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>// Save the full path to the file
string saveFile = Application.persistentDataPath + "/gamedata.data";
// Does it exist?
if(File.Exists(saveFile))
{
// File exists!
//
// Create a FileStream connected to the saveFile and set to the file mode of "Open"
FileStream inputStream = new FileStream(saveFile, FileMode.Open);
}
else
{
// File does not exist.
//
// This could mean it was deleted or have been created yet.
// Create a FileStream connected to the saveFile path and set the file mode to "Create".
FileStream outputStream = new FileStream(saveFile, FileMode.Create);
}</code></span></span>
使用GameData管理器类
编程应该使开发人员的任务更容易。在与前面的代码一样,在多个任务中使用相同类型的数据的情况下,这可能是更好地组织其使用的好机会。因为FileStream类在两个地方用于执行非常相似的任务,所以应该将它创建为类的自己的字段。这将允许根据需要访问它。
在使用类时,将任务分解为类的方法通常很有帮助。在上述情况下,在同一块代码中存在“写入文件”和“读取文件”的任务。分解这些任务将有助于使用与任务本身匹配的新方法更好地使用和测试它们。
使用GameDataManager类,可以管理“游戏数据”。(GameData类将在下一节中创建。)
<span style="background-color:#eeeeee"><span style="color:#111111"><code>public class GameDataManager
{
// Create a field of this class for the file to write
string saveFile = Application.persistentDataPath + "/gamedata.data";
// Create a single FileStream to be overwritten as needed in the class.
FileStream dataStream;
void readFile()
{
// Does the file exist?
if (File.Exists(saveFile))
{
// Use the file mode "Open".
dataStream = new FileStream(saveFile, FileMode.Open);
}
}
void writeFile()
{
// use the file mode "Create".
dataStream = new FileStream(saveFile, FileMode.Create);
}
}</code></span></span>
使用GameData类
创建一个用于保存其他类或进程使用的值集合的类通常很有用。一个新类GameData将用于此目的。
注意:可以通过 Create -> C# Script 在 Project View 中创建一个新的 C# 脚本文件。右键单击文件以将其重命名为默认名称以外的名称。
新的GameData类不需要很复杂,因为它只会保存值。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>public class GameData
{
// Public lives
public int lives;
// Public highScore
public int highScore;
}</code></span></span>
System.Serializable
类是一种复杂的数据结构。为了让它被“序列化”,需要额外的信息让 Unity 和 C# 知道它的字段应该被序列化。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>[System.Serializable]
public class GameData
{
// Public lives
public int lives;
// Public highScore
public int highScore;
}
</code></span></span>
结合GameDataManager和GameData
GameData类包含值,GameDataManager类“管理”这些值,与FileStream一起读取和写入文件。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>// Add System.IO to work with files!
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameDataManager : MonoBehaviour
{
// Create a GameData field.
GameData gameData;
// Create a field of this class for the file to write
string saveFile = Application.persistentDataPath + "/gamedata.data";
// Create a single FileStream to be overwritten as needed in the class.
FileStream dataStream;
void readFile()
{
// Does the file exist?
if (File.Exists(saveFile))
{
// Create a FileStream connected to the saveFile.
// Set to the file mode to "Open"
dataStream = new FileStream(saveFile, FileMode.Open);
}
}
void writeFile()
{
// Create a FileStream connected to the saveFile path.
// Set the file mode to "Create".
dataStream = new FileStream(saveFile, FileMode.Create);
}
}
</code></span></span>
添加BinaryFormatter
BinaryFormatter类处理将数据序列化和反序列化为二进制格式的工作。到目前为止,已经使用FileStream进行“创建”或“打开”,但没有使用任何数据。
像FileStream一样,它将用于读取和写入操作。但是,它是不同命名空间的一部分:System.Runtime.Serialization.Formatters.Binary。
它提供了两种方法,Serialize()和Deserialize()。这些对InputStream对象(其中FileStream继承自)起作用并转换成和转换出二进制数据。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>// Add System.IO to work with files!
using System.IO;
// Add System.Runtime.Serialization.Formatters.Binary to work with BinaryFormatter!
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameDataManager : MonoBehaviour
{
// Create a GameData field.
GameData gameData;
// Create a field of this class for the file to write
string saveFile = Application.persistentDataPath + "/gamedata.data";
// Create a single FileStream to be overwritten as needed in the class.
FileStream dataStream;
// Create a single BinaryFormatter to be used across methods.
BinaryFormatter converter = new BinaryFormatter();
void readFile()
{
// Does the file exist?
if (File.Exists(saveFile))
{
// Create a FileStream connected to the saveFile.
// Set to the file mode to "Open".
dataStream = new FileStream(saveFile, FileMode.Open);
// Serialize GameData into binary data
// and add it to an existing input stream.
converter.Serialize(dataStream, gameData);
// Close the stream
dataStream.Close();
}
}
void writeFile()
{
// Create a FileStream connected to the saveFile path.
// Set the file mode to "Create".
dataStream = new FileStream(saveFile, FileMode.Create);
// Deserialize binary data
// and convert it into GameData, saving it as part of gameData.
gameData = converter.Deserialize(dataStream) as GameData;
// Close stream.
dataStream.Close();
}
}</code></span></span>
转换为 Unity 概念
仅使用 C#,值Application.persistentDataPath似乎被用作其字段的一部分。但是,Unity 仅在启动其他系统后才创建此值。换句话说,它必须用作另一个方法的一部分,比如Awake()。
<span style="background-color:#eeeeee"><span style="color:#111111"><code>// Add System.IO to work with files!
using System.IO;
// Add System.Runtime.Serialization.Formatters.Binary to work with BinaryFormatter!
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameDataManager : MonoBehaviour
{
// Create a GameData field.
GameData gameData;
// Create a field of this class for the file to write
string saveFile;
// Create a single FileStream to be overwritten as needed in the class.
FileStream dataStream;
// Create a single BinaryFormatter to be used across methods.
BinaryFormatter converter = new BinaryFormatter();
void Awake()
{
// Update the path once the persistent path exists.
saveFile = Application.persistentDataPath + "/gamedata.data";
}
public void readFile()
{
// Does the file exist?
if (File.Exists(saveFile))
{
// Create a FileStream connected to the saveFile.
// Set to the file mode to "Open".
dataStream = new FileStream(saveFile, FileMode.Open);
// Deserialize binary data
// and convert it into GameData, saving it as part of gameData.
gameData = converter.Deserialize(dataStream) as GameData;
// Close the stream.
dataStream.Close();
}
}
public void writeFile()
{
// Create a FileStream connected to the saveFile path.
// Set the file mode to "Create".
dataStream = new FileStream(saveFile, FileMode.Create);
// Serialize GameData into binary data
// and add it to an existing input stream.
converter.Serialize(dataStream, gameData);
// Close stream.
dataStream.Close();
}
}
</code></span></span>
公共领域和方法
为了让其他类能够访问 GameDataManager 的内部GameData,它需要是public的。readFile()和writeFile()方法也一样。通过使用这个关键字,这些可以被其他类使用,调用或更改GameDataManager的内部数据,以读取或写入类中使用的保存文件。
BinaryFormatter作为开始,而不是终点
BinaryFormatter类对二进制数据进行序列化和反序列化。由于用户在处理文件时可以随时编辑此数据,因此不应完全信任它。它可以作为使用其他工具创建数据格式的起点,但应验证从文件中读取的任何内容的可接受值范围。