Unity Scene 数据迁移研究

来源:互联网 发布:网络陷阱有哪些 编辑:程序博客网 时间:2024/06/04 01:16

背景

项目(手游)进行到中后期,在进行代码重构过程中,难免会遇到一些与数据相关的问题。

例如我今天遇到的一个:Unity Scene 中存在了一些关卡数据,这些关卡数据是由关卡设计者放在场景中的物体上配置的(Monobehavior),借由场景进行数据的保存。

当然,如果关卡设计不是使用 unity scene 作为媒介来保存,可能就不存在这个问题了。

言归正传,重构(Refactor)过程中,我需要将 Monobehavior 组件中散乱存储的数据项,集中到一个数据结构中,做一个聚合,让代码结构更加优化。此重构的优化目标此处不做讨论。

假设原先有 十几个场景,每个场景有十几个保存数据的 Monobehavior,如何将原来的数据迁移到新的结构中呢?

Unity 中对于重构的支持

Unity 通过 Monobehavior 中的 SerializeFiled 属性标记需要序列化存储的成员。然而成员变量名字的修改,会导致序列化数据的丢失。原因是识别序列化数据和对应成员是使用成员的名字来进行的,改名会导致 Unity 认为你删除了一个旧的成员,而添加了一个新的成员。

简单重命名可以使用 以下方法来避免数据丢失:

_playerPrefab 修改为 _newPlayerPrefab

//改名之前[SerializeField]private GameObject _playerPrefab;//改名之后[SerializeField][UnityEngine.Serialization.FormerlySerializedAs("_playerPrefab")]private GameObject _newPlayerPrefab;

然而对于这样的修改:

[SerializeField]private float _a;[SerializeField]private float _b;

_a 和 _b 字段聚集到一个类 AB 中

[System.Serializable]public class AB{    public float A;    public float B;}

最后需要这样的存储方式:

[SerializeField]private AB _ab;

暂时还未找到方便的 Unity 内置提供解决方案。
各位看官如果知道,可以指出来~(虚心求教)

解决思路

  • 遍历所有场景
  • 遍历所有目标脚本
  • 将存储在原来位置的数据放到目标结构中
  • 存储修改后数据

过程

思路很清楚了,不过过程是艰辛的。(似乎是似曾相识是吗?)

遍历所有场景

    public static string[] FindAllScene(string scenePath)    {        return  System.IO.Directory.GetFiles(scenePath, "*.unity");    }

遍历所有目标脚本

    public static T[] FindAllComponent<T>() where T : Object    {        return Resources.FindObjectsOfTypeAll<T>();    }

将存储在原来位置的数据放入目标结构中

    public static void MoveData(MonoBehaviour ab)    {        SerializedObject serializedObject = new SerializedObject(ab);        serializedObject.FindProperty("_ab.A").floatValue = serializedObject.FindProperty("_a").floatValue;        serializedObject.FindProperty("_ab.B").floatValue = serializedObject.FindProperty("_b").floatValue;    }

存储修改后数据

这个部分包含了两个部分,一个是在数据移动的方法中添加一个调用:

serializedObject.ApplyModifiedProperties();

添加后:

    public static void MoveData(MonoBehaviour ab)    {        SerializedObject serializedObject = new SerializedObject(ab);        serializedObject.FindProperty("_ab.A").floatValue = serializedObject.FindProperty("_a").floatValue;        serializedObject.FindProperty("_ab.B").floatValue = serializedObject.FindProperty("_b").floatValue;        serializedObject.ApplyModifiedProperties();    }

另一部分则是场景的保存:

EditorSceneManager.SaveScene(scene);

最后给出最终的完成代码:

    public static void Move()    {        EditorSceneManager.SaveOpenScenes();        string[] paths = System.IO.Directory.GetFiles("Assets/Maps", "*.unity");        for (int i = 0; i < paths.Length; i++)        {            Scene scene = EditorSceneManager.OpenScene(paths[i]);            BoostArea[] scripts = Resources.FindObjectsOfTypeAll<BoostArea>();            for (int j = 0; j < scripts.Length; j++)            {                MoveData(scripts[j]);            }            EditorSceneManager.SaveScene(scene);        }    }

注意几点:

  1. 使用 EditorUtility.SetDirty() 是不能正确标记场景被修改过的。具体看文档
  2. 使用 serializedObject.ApplyModifiedProperties(); 会使得脚本所属的 prefab 也会被修改保存
  3. 经过测试 EditorSceneManager.MarkSceneDirty 也不能完成这个多场景遍历同时保存修改的操作。(没有使用 serializedObject.ApplyModifiedProperties(),而是直接使用脚本的实例相互赋值)
0 0