保存游戏状态的最佳方法是啥?

Posted

技术标签:

【中文标题】保存游戏状态的最佳方法是啥?【英文标题】:What is the best way to save game state?保存游戏状态的最佳方法是什么? 【发布时间】:2018-08-14 23:17:59 【问题描述】:

我找到了在 Unity3D 游戏引擎中保存游戏数据的最佳方式。 一开始,我使用BinaryFormatter序列化对象。

但我听说这种方式有一些问题,不适合保存。 那么,保存游戏状态的最佳或推荐方式是什么?

在我的情况下,保存格式必须是字节数组。

【问题讨论】:

为什么你的格式必须是字节数组?为什么不将其保存到 PlayerPrefs? 使用序列化有什么问题? 一般STOP using BinaryFormatter at all! 【参考方案1】:

但我听说这种方式有一些问题,不适合保存。

没错。在某些设备上,BinaryFormatter 存在问题。当您更新或更改课程时,情况会变得更糟。由于类不再匹配,您的旧设置可能会丢失。有时,您在读取保存的数据时会因此而出现异常。

另外,在 ios 上,你必须添加 Environment.SetEnvironmentVariable("MONO_REFLECTION_SERIALIZER", "yes"); 否则你会遇到 BinaryFormatter 的问题。

最好的保存方法是使用PlayerPrefsJson。你可以学习如何做到这一点here。

在我的情况下,保存格式必须是字节数组

在这种情况下,您可以将其转换为 json,然后将 json string 转换为 byte 数组。然后您可以使用File.WriteAllBytesFile.ReadAllBytes 来保存和读取字节数组。

这是一个可用于保存数据的通用类。几乎与this 相同,但它使用PlayerPrefs。它使用文件来保存 json 数据。

DataSaver类:

public class DataSaver

    //Save Data
    public static void saveData<T>(T dataToSave, string dataFileName)
    
        string tempPath = Path.Combine(Application.persistentDataPath, "data");
        tempPath = Path.Combine(tempPath, dataFileName + ".txt");

        //Convert To Json then to bytes
        string jsonData = JsonUtility.ToJson(dataToSave, true);
        byte[] jsonByte = Encoding.ASCII.GetBytes(jsonData);

        //Create Directory if it does not exist
        if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
        
            Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
        
        //Debug.Log(path);

        try
        
            File.WriteAllBytes(tempPath, jsonByte);
            Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
        
        catch (Exception e)
        
            Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
            Debug.LogWarning("Error: " + e.Message);
        
    

    //Load Data
    public static T loadData<T>(string dataFileName)
    
        string tempPath = Path.Combine(Application.persistentDataPath, "data");
        tempPath = Path.Combine(tempPath, dataFileName + ".txt");

        //Exit if Directory or File does not exist
        if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
        
            Debug.LogWarning("Directory does not exist");
            return default(T);
        

        if (!File.Exists(tempPath))
        
            Debug.Log("File does not exist");
            return default(T);
        

        //Load saved Json
        byte[] jsonByte = null;
        try
        
            jsonByte = File.ReadAllBytes(tempPath);
            Debug.Log("Loaded Data from: " + tempPath.Replace("/", "\\"));
        
        catch (Exception e)
        
            Debug.LogWarning("Failed To Load Data from: " + tempPath.Replace("/", "\\"));
            Debug.LogWarning("Error: " + e.Message);
        

        //Convert to json string
        string jsonData = Encoding.ASCII.GetString(jsonByte);

        //Convert to Object
        object resultValue = JsonUtility.FromJson<T>(jsonData);
        return (T)Convert.ChangeType(resultValue, typeof(T));
    

    public static bool deleteData(string dataFileName)
    
        bool success = false;

        //Load Data
        string tempPath = Path.Combine(Application.persistentDataPath, "data");
        tempPath = Path.Combine(tempPath, dataFileName + ".txt");

        //Exit if Directory or File does not exist
        if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
        
            Debug.LogWarning("Directory does not exist");
            return false;
        

        if (!File.Exists(tempPath))
        
            Debug.Log("File does not exist");
            return false;
        

        try
        
            File.Delete(tempPath);
            Debug.Log("Data deleted from: " + tempPath.Replace("/", "\\"));
            success = true;
        
        catch (Exception e)
        
            Debug.LogWarning("Failed To Delete Data: " + e.Message);
        

        return success;
    

用法

要保存的示例类

[Serializable]
public class PlayerInfo

    public List<int> ID = new List<int>();
    public List<int> Amounts = new List<int>();
    public int life = 0;
    public float highScore = 0;

保存数据:

PlayerInfo saveData = new PlayerInfo();
saveData.life = 99;
saveData.highScore = 40;

//Save data from PlayerInfo to a file named players
DataSaver.saveData(saveData, "players");

加载数据:

PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
if (loadedData == null)

    return;


//Display loaded Data
Debug.Log("Life: " + loadedData.life);
Debug.Log("High Score: " + loadedData.highScore);

for (int i = 0; i < loadedData.ID.Count; i++)

    Debug.Log("ID: " + loadedData.ID[i]);

for (int i = 0; i < loadedData.Amounts.Count; i++)

    Debug.Log("Amounts: " + loadedData.Amounts[i]);

删除数据:

DataSaver.deleteData("players");

【讨论】:

谢谢!因为我们的项目需要 mono2x 设置,所以我们未能通过环境设置。我会尝试 JsonUtility 和您的评论! 您好,我使用了您的 DataSaver,但它似乎没有保存字典?我检查了本地文件。每个变量及其值都被保存,但不是为我的 Dictionary 保存的。我认为这可能是显示问题,但是当我加载它时,dict 变量仍然为空。 @SeakyLone 没错。它不会保存Dictionary。这是因为 Unity 的 JsonUtility 无法序列化字典。我建议您编写一个扩展,将字典扁平化为数组或列表,然后对其进行序列化/反序列化。如果你不能这样做,那么在代码中使用为 Unity 制作的this 版本的Newtonsoft.Json 而不是JsonUtility,你应该能够序列化/反序列化Dictionary 非常感谢!!!我想我需要下载那个 repo,我想这怎么能行。谢谢!! 我意识到它已经有 3 年了,但不要使用 PlayerPrefs 来存储游戏状态。使用常规文件 IO。【参考方案2】:

我知道这篇文章很旧,但如果其他用户在搜索保存策略时也发现它,请记住:

PlayerPrefs 不是用于存储游戏状态。 明确命名为“PlayerPrefs”以表明其用途:存储玩家偏好。它本质上是纯文本。任何玩家都可以轻松找到、打开和编辑它。这可能不是所有开发者都关心的问题,但对于许多游戏具有竞争力的人来说却很重要。

将 PlayerPrefs 用于选项菜单设置,例如音量滑块和图形设置:您不关心的内容,播放器可以随意设置和更改。

使用 I/O 和序列化来保存游戏数据,或将其作为 Json 发送到服务器。这些方法比 PlayerPrefs 更安全,即使您在保存之前对数据进行了加密。

【讨论】:

以上是关于保存游戏状态的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在java中存储状态变量的最佳实践是啥? [关闭]

保存 iphone 游戏状态的最佳位置在哪里 - xcode 4 中的 txt 文件

为啥不能在渲染方法中改变状态。在调用渲染方法之前更改状态的最佳位置是啥

临时维护页面的最佳实践方法和状态代码是啥?

取消处于阻塞状态的任务的最佳方法是啥?

管理 GUI 状态(创建、编辑、读取模式...)的最佳方法是啥