Unity Error: "End of Stream encountered before parsing was completed" with an own save and load system

Unity Error: "End of Stream encountered before parsing was completed" with an own save and load system

问题

你好,我有一个问题:当我使用 LoadParty 方法时,它在 for 循环中使用 JsonUltility,它只是抛出一个错误:

"End of Stream encountered before parsing was completed"

for 循环沿着名为 Player 的 class 运行,其中包含玩家的所有统计数据。所以我认为问题可能与此有关。

我试过的

我试图对 for 循环进行更改,以便程序可以反序列化数据,但我无法找到解决方案。

代码

Class 保存和加载系统的完整代码:

public void SaveParty(Player[] players, string nameFile)
{
    Debug.Log("Saving!");
    FileStream file = new FileStream(Application.persistentDataPath + nameFile, FileMode.OpenOrCreate);

    try
    {
        // Binary Formatter
        BinaryFormatter formatter = new BinaryFormatter();
        // Serialize
        for(int x = 0; x < players.Length; x++)
        {
            var json = JsonUtility.ToJson(players[x]);
            formatter.Serialize(file, json);
        }
    }
    catch (SerializationException e)
    {
        Debug.LogError("Error Saving: " + e.Message);
    }
    finally
    {
        file.Close();
        Debug.Log(Application.persistentDataPath);
    }
}

// Save Current Party Data
public void SaveCurrentParty()
{
    // If exist will delete, and save again
    if(FirstTimeSaving)
    {
        File.Delete(Application.persistentDataPath + NameFile[1]);
        SaveParty(gameManager.CurrentParty, NameFile[1]);
    }
    // If not exist will save
    if(!FirstTimeSaving)
    {
        SaveParty(gameManager.CurrentParty, NameFile[1]);
        FirstTimeSaving = true;
    }
}

// Load Party Data
public void LoadParty(Player[] party, string nameFile)
{
    Debug.Log("Loading!");
    FileStream file = new FileStream(Application.persistentDataPath + nameFile, FileMode.Open);
    
    try
    {
        BinaryFormatter formatter = new BinaryFormatter();
        // Error Here ↓
        for(int i = 0; i < party.Length; i++)
        {
            JsonUtility.FromJsonOverwrite((string)formatter.Deserialize(file), party[i]);
        }
    }
    catch (SerializationException e)
    {
        Debug.Log("Error Load: " + e.Message);
    }
    finally
    {
        file.Close();
    }
}

播放器的完整代码class:

[System.Serializable]
public class Player : MonoBehaviour
{
    // Stats
    public int Hp;
    public int Sp;
    public int level;
    public int St;
    public int Ma;
    public int Ag;
    public int En;
    public int Lu;

    // Items
    public MeleeWeapon Weapon;

    // Skills
    public List<Skills> skills = new List<Skills>();
}

提前致谢!

您正在尝试循环读取同一个 FileStream。第一个操作读取文件并将位置前进到流的末尾。之后抛出异常,因为流中没有任何内容可供读取。

这发生在这里 - 它与 JsonUtility 无关:

(string)formatter.Deserialize(file)

将整个流读取到最后。

这在您编写时不是问题,因为您序列化的每个对象都是附加到流中的。

一种解决方案是序列化和反序列化整个 Player[]。这样你只能从流中读取一次。当您打开文件时,您位于位置 0 - 流的开头。您读取整个文件并反序列化 Player[],而不是单个 Player 对象。完成后,您已经阅读了整个流,并且拥有了所有数据。

我正在回避关于是否甚至使用二进制序列化的担忧。无论数据如何序列化,基本原则都适用。

除了

一般来说Don't use BinaryFormatter anymore at all! ..在你的例子中你已经一个序列化的JSON string ...为什么要使用BinaryFormatter 就可以了吗?

首先我会为像

这样的玩家创建一个合适的数据容器class
[Serializable]
public class PlayerStats
{
    public int Hp;
    public int Sp;
    public int level;
    public int St;
    public int Ma;
    public int Ag;
    public int En;
    public int Lu;
    
    // Assuming here that both MeleeWepaon and Skills are Serializable types
    public MeleeWeapon Weapon;

    // Skills
    public List<Skills> skills = new List<Skills>();
}

public class Player : MonoBehaviour
{
    public PlayerStats Stats;
}

为什么? -> 因为不允许通过从 JSON 反序列化来创建 MonoBehaviour 实例,这就是我们将在下面进一步做的事情;)

然后简单地使用包装器class,例如

using System.Linq;

[Serializable]
public class PlayersData
{ 
    public PlayerStats[] Data; 

    // Empty constructor is needed by serializer
    public PlayersData(){}

    public PlayersData(IEnumerable<Player> data)
    {
        Data = data.Select(p => p.Stats).ToArray();
    }
}

为什么? -> 内置 JsonUtility 不支持 array/list 类型的直接(反)序列化。

因此改为序列化此类型,然后直接将生成的 JSON 字符串写入文件,例如

public void SaveParty(Player[] players, string nameFile)
{
    var path = Path.Combine(Application.persistentDataPath, nameFile);
    Debug.Log($"Saving!");
          
    try
    {
        var json = JsonUtility.ToJson(new PlayersData (players));
        File.WriteAllText(path, json);
        Debug.Log($"Saved to \"{path}\n"); 
    }
    catch (Exception e)
    {
        Debug.LogError($"{e.GetType} while saving: {e.Message}\n{e.StackTrace}");
    }
}

而对于加载,则相反,只需加载整个内容并将其反序列化到包装容器 class 中。然后将数据分配给相应的播放器实例

public void LoadParty(Player[] party, string nameFile)
{
    Debug.Log("Loading!");
    var path = Path.Combine(Application.persistentDataPath, nameFile);
    
    try
    {
        var json = File.ReadAllText(path);
        
        var data = JsonUtility.FromJson<PlayersData>(JSON);
        for(var i = 0; i < Mathf.Min(data.Length, party.Length); i++)
        {
            party[i].Stats = data.Data[i];
        }
    }
    catch (Exception e)
    {
        Debug.LogError($"{e.GetType} while loading: {e.Message}\n{e.StackTrace}");
    }
}

提示:如果您的 Player 有任何您想要(反)序列化的 [SerializeField] private ... 字段,那么您宁愿必须在 Player 获取 PlayerStats 参数并相应地“手动”分配它们