BinaryFormatter 保存和加载游戏数据 For Unity

本文介绍了如何在Unity中利用C#的BinaryFormatter进行游戏数据的保存和加载。通过创建GameData管理器类和使用System.Serializable,实现了数据的序列化和反序列化,同时强调了BinaryFormatter的安全性和作为数据存储起点的角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/

C# 中BinaryFormatter执行二进制数据的“序列化”和“反序列化”操作。它采用简单的数据结构,例如整数 ( int )、十进制数 ( float ) 以及字母和数字的集合 ( string ),并且可以将它们转换为二进制格式。这意味着更复杂的结构(如类)可以将其字段编码为二进制格式,以便保存到文件中,然后由程序读取。在 Unity 中,通过使用这个 C# 类,可以保存数据,例如可以使用PlayerPrefs完成,但在更复杂的级别上。

使用BinaryFormatter不是为游戏创建保存文件的插入式解决方案。该类可以将一些 C# 数据结构编码为序列化格式并再次读取它们。这意味着其他用户、程序和系统可以“看到”这种编码形式的数据。BinaryFormatter 不会以任何方式加密或隐藏数据,只是将一种形式转换为另一种形式,以便于读取和写入文件。始终验证从其使用中接受的所有数据。

使用FileStream s

处理文件时,无法知道文件是在离用户几英寸远的本地硬盘上、在另一个房间的硬盘上,还是在数千英里外的云存储中。为了解决这个问题,C# 提供了一个名为FileStream的类。它读取或写入文件并让操作系统处理进程的低级管理。对于开发人员来说,它提供了一种在不知道物理位置的情况下访问文件的方法,并使用“流”(无论是本地连接还是远程连接)来处理文件。

注意FileStreamSystem.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>

结合GameDataManagerGameData

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类对二进制数据进行序列化和反序列化。由于用户在处理文件时可以随时编辑此数据,因此不应完全信任它。它可以作为使用其他工具创建数据格式的起点,但应验证从文件中读取的任何内容的可接受值范围。

Unity保存加载玩家的进度通常涉及到使用 PlayerPrefs 或者 Unity's built-in serialization system 以及存档管理功能。以下是一种基本的实现方法: 1. **使用 PlayerPrefs**: PlayerPrefs 是一种简单的键值对存储系统,可以保存单个游戏会话内的数据。如果你的数据量不大,比如玩家的生命值、等级、得分等基本信息,可以直接用 PlayerPrefs 存储。 ```csharp public class SaveManager { public static void SavePlayerData(Player player) { PlayerPrefs.SetInt("PlayerHealth", player.Health); PlayerPrefs.Save(); } public static Player LoadPlayerData() { int health = PlayerPrefs.GetInt("PlayerHealth"); return new Player(health); } } ``` 2. **序列化对象**: 对于更复杂的数据结构,如玩家角色对象,你应该考虑序列化整个对象。Unity 提供了 DataContract BinaryFormatter 类来完成这项工作。首先,标记需要序列化的字段为 `[SerializeField]` `[HideInInspector]`。 ```csharp [Serializable] public class PlayerData : ScriptableObject { public int Health; [HideInInspector] public Player player; } SaveManager.SavePlayerData(player.ToPlayerData()); PlayerData data = SaveManager.LoadPlayerData(); Player loadedPlayer = data.player; ``` 3. **存档管理(AssetDatabase/Prefabs)**: 如果你有大型的游戏数据集,可能会考虑使用存档系统。Unity 的 AssetDatabase 提供了更高级的功能,允许你在运行时保存加载完整的 prefab 或场景文件。 ```csharp using UnityEngine; using UnityEditor; public class SaveLoadScene : MonoBehaviour { [MenuItem("Tools/Save Scene")] static void SaveScene() { EditorUtility.SaveScene(Editor.CurrentScene); } [MenuItem("Tools/Load Scene")] static void LoadScene() { string[] sceneNames = System.IO.Directory.GetFiles(Application.dataPath, "*.unity"); string sceneToLoad = EditorUtility.OpenFilePanel("Load Scene", "", "*.unity"); if (sceneToLoad.Length > 0) EditorSceneManager.LoadScene(sceneToLoad); } } ``` 记住,保存加载操作通常应该放在游戏的适当时间点(比如关卡开始或游戏暂停),并且最好将这些逻辑封装到专门的管理类中,以保持代码整洁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

警醒与鞭策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值