【问题标题】:Save and Load Data from Application.persistentDataPath works in Unity, but not on iOS从 Application.persistentDataPath 保存和加载数据适用于 Unity,但不适用于 iOS
【发布时间】:2021-11-07 20:25:55
【问题描述】:

我正在构建一个需要将用户创建的数据保存并加载到持久数据路径的应用。当我在 Unity 编辑器中播放它时,它运行良好,但是当我在 iOS 上尝试它时,我得到了这个错误:

Uploading Crash Report
NullReferenceException: Object reference not set to an instance of an object.
  at CustomModeScript.LoadSavedModes () [0x00000] in <00000000000000000000000000000000>:0 
  at CustomModeScript.Initialize (System.String fromPanel) [0x00000] in <00000000000000000000000000000000>:0 
  at MainSettingsPanelScript.OnControlModes () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.Events.UnityAction.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.Events.UnityEvent.Invoke () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1].Invoke (T1 handler, UnityEngine.EventSystems.BaseEventData eventData) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchPress (UnityEngine.EventSystems.PointerEventData pointerEvent, System.Boolean pressed, System.Boolean released) [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.StandaloneInputModule.ProcessTouchEvents () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.EventSystems.StandaloneInputModule.Process () [0x00000] in <00000000000000000000000000000000>:0 
UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchPress(PointerEventData, Boolean, Boolean)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchEvents()
UnityEngine.EventSystems.StandaloneInputModule:Process()

以前是ioexception:Sharing violation on path 错误,现在是我重构Load函数后出现上面的错误。当我尝试加载数据时,当前正在发生此问题(我无法确定它是否正在保存)

这里是保存和加载(检索)的函数

public void SaveModes()
    {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath + "/UserCustomModes.jl");
        List<Mode> data = new List<Mode>();

        data = savedModes;
        
        bf.Serialize(file , data);
        file.Close();
    }

    // Retrieve saved modes if they exist 
    public List<Mode> RetrieveSavedModes()
    {
        if(File.Exists(Application.persistentDataPath + "/UserCustomModes.jl"))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "/UserCustomModes.jl", FileMode.Open);
            List<Mode> storedModesFile = bf.Deserialize(file) as List<Mode>;

            savedModes = storedModesFile;
            file.Close();
        }

        return savedModes;
    }

下面是调用加载函数的代码:

// Populate mode view with saved modes
    private void PopulateModeView()
    {
        ClearCustomModes();
        Debug.Log("Loading Modes: " + Manager.savedModes.Count);
        if (Manager.savedModes.Count > 0)
        {
            //Debug.Log("More than 0 modes present");
            var index = 0;
            foreach (var savedMode in Manager.savedModes)
            {
                var modeItem = Instantiate(customModeItemPrefab, customModeSectionTransform);
                modeItem.GetComponent<CustomModeItemScript>().Initialize(savedMode, index, this, mainSettingsPanelScript);
                generatedModeButtons.Add(modeItem);
            }
        }
    }

这里是 LoadSavedModes 函数:

private void LoadSavedModes()
    {
        RemoveAllModeButtons();
        Manager.savedModes = Manager.RetrieveSavedModes();
        Debug.Log("[UNITY] Retrieve Modes Count: " + Manager.savedModes.Count);
        PopulateModeView();
    }

提前谢谢你!

【问题讨论】:

  • 我不确定为什么代码还不能工作,但是not 使用BinaryFormatter 因为它不安全。另一种选择是ReadAllBytesWriteAllBytes
  • 错误信息很清楚。 PersistentPath 应该没有问题,因为这看起来是正确的。错误是说在 Initialize 调用之一中尝试调用方法 LoadSavedModes 时存在 null 引用。可以分享一下这个方法吗?如果用户没有保存数据,你会默认你的数据吗?你在保存/加载什么?是原始数据类型还是您尝试保存/加载更复杂的类型,例如GameObjects
  • 刚刚意识到我为我的原始评论链接了错误的文档,here 是避免使用BinaryFormatter 的原因。我之前已经链接了原始用例文档 - 我的错。
  • 感谢您查看@TEEBQNE。我已经用 LoadSavedModes 函数更新了我的问题。在声明变量时以及在调用 RetrieveSavedModes 函数之前,我还将 savedModes 对象设置为新的 List。数据是原始数据,而不是游戏对象
  • 您是否能够在连接到XCode 时运行您的应用程序?这将使您的调试变得更加容易,因为XCode 将显示来自您的应用程序的实时打印输出。查找打印输出,您可以根据在null 参考之前和/或之后打印出的内容准确找到它中断的位置。我仍然建议将使用 BinaryFormatter 换成替代方案。我看到那里有几个地方可能null 参考,但很难说准确。

标签: c# ios unity3d persistent-storage


【解决方案1】:

这是我个人的模板Save/Load 脚本。我知道它适用于当前在函数GetFilePath 中列出的所有平台。

using UnityEngine;
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections.Generic;

/// <summary>
/// Saves, loads and deletes all data in the game
/// </summary>
/// <typeparam name="T"></typeparam>
public static class SaveLoad<T>
{
    /// <summary>
    /// Save data to a file (overwrite completely)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="data"></param>
    /// <param name="folder"></param>
    /// <param name="file"></param>
    public static void Save(T data, string folder, string file)
    {
        // get the data path of this save data
        string dataPath = GetFilePath(folder, file);

        string jsonData = JsonUtility.ToJson(data, true);
        byte[] byteData;
        
        byteData = Encoding.ASCII.GetBytes(jsonData);

        // create the file in the path if it doesn't exist
        // if the file path or name does not exist, return the default SO
        if (!Directory.Exists(Path.GetDirectoryName(dataPath)))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(dataPath));
        }

        // attempt to save here data
        try
        {
            // save datahere
            File.WriteAllBytes(dataPath, byteData);
            Debug.Log("Save data to: " + dataPath);
        }
        catch (Exception e)
        {
            // write out error here
            Debug.LogError("Failed to save data to: " + dataPath);
            Debug.LogError("Error " + e.Message);
        }
    }
    
    /// <summary>
    /// Load all data at a specified file and folder location
    /// </summary>
    /// <param name="folder"></param>
    /// <param name="file"></param>
    /// <returns></returns>
    public static T Load(string folder, string file)
    {
        // get the data path of this save data
        string dataPath = GetFilePath(folder, file);

        // if the file path or name does not exist, return the default SO
        if (!Directory.Exists(Path.GetDirectoryName(dataPath)))
        {
            Debug.LogWarning("File or path does not exist! " + dataPath);
            return default(T);
        }

        // load in the save data as byte array
        byte[] jsonDataAsBytes = null;

        try
        {
            jsonDataAsBytes = File.ReadAllBytes(dataPath);
            Debug.Log("<color=green>Loaded all data from: </color>" + dataPath);
        }
        catch (Exception e)
        {
            Debug.LogWarning("Failed to load data from: " + dataPath);
            Debug.LogWarning("Error: " + e.Message);
            return default(T);
        }

        if (jsonDataAsBytes == null)
            return default(T);

        // convert the byte array to json
        string jsonData;

        // convert the byte array to json
        jsonData = Encoding.ASCII.GetString(jsonDataAsBytes);

        // convert to the specified object type
        T returnedData = JsonUtility.FromJson<T>(jsonData);

        // return the casted json object to use
        return (T)Convert.ChangeType(returnedData, typeof(T));
    }
    
    /// <summary>
    /// Create file path for where a file is stored on the specific platform given a folder name and file name
    /// </summary>
    /// <param name="FolderName"></param>
    /// <param name="FileName"></param>
    /// <returns></returns>
    private static string GetFilePath(string FolderName, string FileName = "")
    {
        string filePath;
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
        // mac
        filePath = Path.Combine(Application.streamingAssetsPath, ("data/" + FolderName));

        if (FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
        // windows
        filePath = Path.Combine(Application.persistentDataPath, ("data/" + FolderName));

        if(FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#elif UNITY_ANDROID
        // android
        filePath = Path.Combine(Application.persistentDataPath, ("data/" + FolderName));

        if(FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#elif UNITY_IOS
        // ios
        filePath = Path.Combine(Application.persistentDataPath, ("data/" + FolderName));

        if(FileName != "")
            filePath = Path.Combine(filePath, (FileName + ".txt"));
#endif
        return filePath;
    }
}

这里是一个使用脚本的例子

[System.Serializable]
public class TestClass
{
    public TestClass()
    {
         testData = new List<int>();
    }

    public List<int> testData = new List<int>();
}

public class TestMono : MonoBehaviour
{
     private string testFolder = "folder";
     private string testFile = "file";

     private List<int> myList = new List<int>();

     private void Awake()
     {
          // the ?? is a null comparison so if the value returns null, it generates new data
          TestClass loadedData = SaveLoad<TestClass>.Load(folder, file) ?? new TestClass();

          myList = loadedData.testData;
     }

     private void SaveData()
     {
         TestClass dataToSave = new TestClass
         {
              testData = myList
         };
          
         SaveLoad<TestClass>.Save(dataToSave, folder, file);
     }
}

上面的 sn-p 未经测试,但它是 Save/Load 脚本的一般用例。

【讨论】:

  • 谢谢@TEEBQNE,非常感谢。您认为这可能是 Unity3D 中的错误吗?因为此代码在 2019.4 中可以使用,但现在在 2020.3 中无法使用
  • 由于BinaryFormatter 正在慢慢被弃用或已经被弃用,它可能只是在较新的版本中,一些对它的支持被删除以支持较新的方法。这可能是一个错误,也可能是有目的的推动将其全部删除。
  • 我一开始就应该接受你的建议,这样会更快:)。非常感谢
  • @RobertH 随时!很高兴它对你有用。
猜你喜欢
  • 2021-11-24
  • 2019-08-31
  • 1970-01-01
  • 2020-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-07
相关资源
最近更新 更多