如何避免因ScriptableObject数据结构更改而导致数据丢失

时间:2019-02-05 21:20:17

标签: c# unity3d

我正在使用ScriptableObject资产来存储我的多语言应用程序项目的数据。除我要更改现有数据结构的情况外,此方法效果很好。

就我而言,数据结构如下:

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/I18NData", order = 1)]
public class I18NData : ScriptableObject
{
    public I18NSpriteData[] Sprites;
    public I18NTextData[] Texts;
}

[System.Serializable]
public class I18NSpriteData
{
    public string Label;
    public Sprite SpriteEN;
    public Sprite SpriteFR;
    public Sprite SpriteSG;
    public Sprite SpriteES;
    public Sprite SpriteDE;
    public Sprite SpriteIT;
}

[System.Serializable]
public class I18NTextData
{
    public string Label;

    [TextArea]
    public string TextEN;

    [TextArea]
    public string TextFR;

    [TextArea]
    public string TextSG;

    [TextArea]
    public string TextES;

    [TextArea]
    public string TextDE;

    [TextArea]
    public string TextIT;
}

现在,我在Unity Editor中添加数据,并且一切正常。但是,一旦我对现有数据结构进行了更改,编辑器中的所有数据就会丢失。 ScriptableObject似乎已重置为空状态。

这特别令人讨厌,因为我们正处于开发过程中,并且在此阶段不可避免地要更改数据结构...

您如何避免这种情况?您是否有一些脚本可基于其他文件(例如JSON)生成ScriptableObjects?还是在Unity中有一种简单的方法?

感谢任何提示!

3 个答案:

答案 0 :(得分:2)

如果您想重命名序列化字段,但保留数据,则Unity具有一个名为FormerlySerializedAs的属性,它将保留旧的序列化字段名称。

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/I18NData", order = 1)]
public class I18NData : ScriptableObject
{

    [FormerlySerializedAs("Sprites")]
    public I18NSpriteData[] Sprites2;
    public I18NTextData[] Texts;
}

另一个选择是,您可以将字段设为私有并使用SerializeField属性。然后,您可以随时间自由更改用于访问或修改字段的属性,而不会影响序列化:

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/I18NData", order = 1)]
public class I18NData : ScriptableObject
{
    [SerializeField]
    private I18NSpriteData[] Sprites;
    public I18NSpriteData[] Sprites2 {
            get { return Sprites; }
            set { Sprites = value; }
    }

    [SerializeField]
    private I18NTextData[] Texts;
    public I18NTextData[] Texts2 {
            get { return Texts; }
            set { Texts= value; }
    }
}

您可能要考虑的另一件事是使用ISerializationCallbackReceiver界面来更好地控制Unity的序列化过程。它提供两种方法,OnBeforeSerialize和OnAfterDeserialize,它们在Unity的序列化和反序列化过程中被调用。

当序列化Unity通常无法处理的复杂结构时(例如字典),这可能更有用,但是根据您要进行的更改的类型,这可能会有所帮助。您可能会用它来在版本之间迁移数据。

答案 1 :(得分:1)

对结构的某些更改总是会导致数据丢失,因此,在确定数据的最终布局之前,您不应填充数据,至少在主要人员方面,统一序列化应支持添加新字段,但无论如何,我都不会依赖它。


您可以使用几种技术来改善工作流程。

  1. 创建数据资产的层次结构。例如,将单个翻译实例作为单独的可编写脚本的对象来封装您的结构(顺便说一句,对诸如“ I18NSpriteData”或“ I18NTextData”之类的值对象使用结构而不是类),然后链接“ I18NData”内的所有资产。
  2. 使用外部数据存储,例如JSON或数据库。这意味着要为编辑器开发一个导入/导出工具,但是在许多情况下还是值得这样做,尤其是当您打算将来实现动态翻译(例如,通过从服务器下载)时。您还可以考虑使用一种更可扩展的语言方法,而不是将它们硬编码为一个struct字段,每个元素可能会有一系列翻译,例如:
[System.Serializable]
public struct I18NTextData
{
    public string Label;
    public I18NTextDataTranslation[] translations;
}
[System.Serializable]
public struct I18NTextDataTranslation
{
    public string lang;
    public string content;
}

答案 2 :(得分:1)

您可能正在以使Unity无法以所需的方式解包序列化数据的方式更改数据结构。因此,缺少数据。

如果选择了Force Text序列化模式,则所有ScriptableObject的序列化都将被序列化为人类可读的格式YAML。

在文本编辑器中读取时,ScriptableObject将看起来像这样:

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 9228bfb9e457c5341920079380c382ba, type: 3}
  m_Name: Data
  m_EditorClassIdentifier:
  Sprites:
  - Label:
    SpriteEN: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0}
    SpriteFR: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
    SpriteSG: {fileID: 10915, guid: 0000000000000000f000000000000000, type: 0}
    SpriteES: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0}
    SpriteDE: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0}
    SpriteIT: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
  Texts:
  - Label:
    TextEN: "Hello\t"
    TextFR: Salut!
    TextSG: ????
    TextES: Holla!
    TextDE: Bratwurst
    TextIT: Pizza!

只要掌握了YAML,您就可以手动更改数据以适应新的数据结构。