【问题标题】:How to save game settings into file in Unity games without returning serialization error?如何在 Unity 游戏中将游戏设置保存到文件中而不返回序列化错误?
【发布时间】:2020-05-18 12:12:10
【问题描述】:

我正在构建一个 Android 游戏,“选项”菜单提供 3 种不同的设置:背景音乐音量、音效音量和射击按钮的屏幕侧位置。还有应该保存的高分。

我尝试根据某人提供的答案创建脚本,但是当我使用音乐音量滑块对其进行测试时,它不断返回有关无法序列化的内容的错误。起初它说“SerializationException: Type 'GameData' in Assembly 'Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' 未标记为可序列化”,所以我尝试添加“[System.Serializable]”在某处建议的类代码上方,但现在它返回错误“SerializationException: Type 'UnityEngine.MonoBehaviour' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is notmarked as serializable ”。

此外,在序列化错误之后,它还会写入错误“IOException:在路径 C:\Users\UserName\AppData\LocalLow\CompanyName\AppName\save.dat 上共享冲突”。

代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

[System.Serializable]
public class GameData : MonoBehaviour
{
    public int highScore = 0;
    public float musicVol = 1;
    public float sfxVol = 1;
    public int shootSide = 1;
    // Start is called before the first frame update
    private void Start()
    {
        LoadFile();
    }
    public void HighScoreValue(int highS)
    {
        highScore = highS;
        SaveData();
    }
    public void MusicValue(float musicV)
    {
        musicVol = musicV;
        SaveData();
    }
    public void SfxValue(float sfxV)
    {
        sfxVol = sfxV;
        SaveData();
    }
    public void ShootSideValue(int shootS)
    {
        shootSide = shootS;
        SaveData();
    }
    private void SaveData()
    {
        string destination = Application.persistentDataPath + "/save.dat";
        FileStream file;

        if (File.Exists(destination))
        {
            file = File.OpenWrite(destination);
        }
        else
        {
            file = File.Create(destination);
        }
        GameData data = gameObject.AddComponent<GameData>();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(file, data);
        file.Close();
    }
    private void LoadFile()
    {
        string destination = Application.persistentDataPath + "/save.dat";
        FileStream file;

        if (File.Exists(destination))
        {
            file = File.OpenRead(destination);
        }
        else
        {
            Debug.Log("File not found");
            return;
        }
        BinaryFormatter formatter = new BinaryFormatter();
        GameData data = (GameData)formatter.Deserialize(file);
        file.Close();
        highScore = data.highScore;
        musicVol = data.musicVol;
        sfxVol = data.sfxVol;
        shootSide = data.shootSide;
    }
}

代码有什么问题?提前致谢。

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    MonoBehaviour 可能无法使用反序列化程序所需的 new 关键字来实例化。不需要额外的 AddComponentGetComponent,因为此脚本已经您要在 GameObject 上使用的组件。


    您应该(至少我会)将可序列化类型分离为纯数据类/结构,并在实现逻辑的 MonoBehaviour 中使用它的实例。

    可能看起来像例如

    [Serializable]
    public class GameDataContainer
    {
        public int highScore = 0;
        public float musicVol = 1;
        public float sfxVol = 1;
        public int shootSide = 1;
    }
    
    public class GameData : MonoBehaviour
    {
        // since this is a serialized, 
        // it will be initialized with a valid instance by default
        public GameDataContainer data;
    
        // NEVER use +"/" for system paths!
        // initilaize this only once
        private string destination => Path.Combine(Application.persistentDataPath, "save.dat");
    
        // Start is called before the first frame update
        private void Start()
        {
            LoadFile();
        }
    
        public void HighScoreValue(int highS)
        {
            data.highScore = highS;
            SaveData();
        }
    
        public void MusicValue(float musicV)
        {
            data.musicVol = musicV;
            SaveData();
        }
    
        public void SfxValue(float sfxV)
        {
            data.sfxVol = sfxV;
            SaveData();
        }
    
        public void ShootSideValue(int shootS)
        {
            data.shootSide = shootS;
            SaveData();
        }
    
        private void SaveData()
        {
            using (var file = File.Open(destination, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(file, data);
            }
        }
    
        private void LoadFile()
        {
            if (!File.Exists(destination))
            {
                Debug.Log("File not found");
                return;
            }
    
            using(var file = File.Open(destination, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var formatter = new BinaryFormatter();
                data = (GameDataContainer)formatter.Deserialize(file);
            }
        }
    }
    

    【讨论】:

    • 谢谢!实际上有人已经在 Unity 论坛上建议将其分为数据类和处理程序类,我认为现在无需进一步更改即可工作(即使使用“/”,所以我不确定它为什么重要),但你提供一些进一步的调整可以使它更好地工作。
    • + "/" 非常危险,如果您使用 Unity 并可能使用不同的操作系统构建到不同的目标平台(例如 / 在 Unix 上与 \ 或 :\ 用于 Windows 上的驱动器路径)... Path.Combine 只需根据您的代码运行的操作系统插入正确的路径分隔符
    • 谢谢!很高兴知道。
    • 虽然看起来你不能使用 MonoBehaviour 构造函数中的 persistentDataPath,但它告诉我把它放在 Start 或 Awake 中(而且它也不需要是只读的)。 编辑: 使用您的方法时,它似乎也没有添加任何斜线。它将文件保存为公司文件夹中的“Appnamesave.dat”,而不是 CompanyName/AppName/save.dat
    • 糟糕,我对斜线缺失部分的错误。我保留了 + 并且只在第一个字段中写了它,而不是使用逗号。
    【解决方案2】:

    由于您的 GameData 类继承自 Unity 的 MonoBehaviour,因此序列化需要 MonoBehaviour 也可序列化;它似乎不是。

    也许这会有所帮助:Serialize & Deserialize Unity3D MonoBehaviour script

    【讨论】:

    • 似乎 Start 方法和将脚本附加到的 Unity 游戏对象拖到 UI 按钮字段的能力都需要 MonoBehaviour。我会看看你提供的链接。
    猜你喜欢
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    相关资源
    最近更新 更多